/*
 * $Id$
 *
 * :ts=4
 *
 * This implementation is based upon the 'dossh' code which is
 * Copyright (c) 2000 by Nagy Daniel. It was 'condensed' and
 * adapted for use with the Amiga port of CVS 1.11 by
 * Olaf Barthel <olsen@sourcery.han.de>.
 *
 * While some of this code is Amiga-specific, namely the use of the
 * shared 'bsdsocket.library', it is quite portable all in itself.
 * Just remove those Amiga-specific header files and you should be
 * able to use this code in just about any other configuration.
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef AMIGA

#include <clib/socket_protos.h>
#include <pragmas/socket_pragmas.h>

extern struct Library *SocketBase;

#include <devices/timer.h>

#define select(num_fds,read_fds,write_fsd,except_fsd,timeval) \
	WaitSelect(num_fds,read_fds,write_fsd,except_fsd,timeval,NULL)

char *amiga_strerror(int n);

#define strerror(n) amiga_strerror(n)

#endif /* AMIGA */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/ioctl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <errno.h>
#include <time.h>

#include <netdb.h>

/****************************************************************************/

#include "ssh.h"
#include "md5.h"
#include "rsa.h"
#include "des.h"
#include "crc.h"
#include "macros.h"
#include "blowfish.h"
#include "ssh_protocol.h"

/****************************************************************************/

#define SSH_VERSION "0.8"

/****************************************************************************/

static int recv_all(int socket, void *data, int len);
static int sock_gets(int sock, char *buf, int n);
static int check_emulation(int remote_major, int remote_minor, int *return_major, int *return_minor);
static int exchange_identification(struct ssh_protocol_context *spc);
static int create_keys(struct ssh_protocol_context *spc);
static int send_password(struct ssh_protocol_context *spc, char *password);
static int set_maximum_packet_size(struct ssh_protocol_context *spc);
static int convert_received_data(struct ssh_protocol_context *spc);
static void prepare_packet_header(struct ssh_protocol_context *spc, int type, int len);
static int write_packet(struct ssh_protocol_context *spc);
static int read_packet(struct ssh_protocol_context *spc);
static int read_packet_and_check_type(struct ssh_protocol_context *spc, int type);
static int send_ssh_data(struct ssh_protocol_context *spc, int type, void *data, int len);

/****************************************************************************/

/* Parameters of a pseudo-random-number generator from Knuth's
 * "The Art of Computer Programming, Volume 2: Seminumerical
 *  Algorithms" (3rd edition), pp. 185-186.
 */
#define MM 2147483647	/* a Mersenne prime */
#define AA 48271		/* this does well in the spectral test */
#define QQ 44488		/* (long)(MM/AA) */
#define RR 3399			/* MM % AA; it is important that RR < QQ */

long
get_random_number(void)
{
	static long X;

	/* This seeds the random number generator; X is never zero. */
	if(X == 0)
		X = (long)time(NULL);

	X = AA * (X % QQ) - RR * (long)(X / QQ);
	if(X < 0)
		X += MM;

	return(X);
}

/****************************************************************************/

/* A variation of 'recv()' which will attempt to satisfy the
 * entire read request.
 */
static int
recv_all(int socket,void * data,int len)
{
	unsigned char * m = data;
	int total = 0;
	int n;

	while(len > 0)
	{
		n = recv(socket,m,len,0);
		if(n < 0)
		{
			total = n;
			break;
		}
    else if(n == 0) break;

		m += n;
		len -= n;
		total += n;
	}

	return(total);
}

/****************************************************************************/

/* Similar to 'fgets()', but operates on a socket. */
static int
sock_gets(int sock, char *buf, int n)
{
	int result = 0;
	int len;
	char c;

	n--;
	while(n > 0)
	{
		len = recv(sock, &c, 1, 0);
		if(len > 0)
		{
			(*buf++) = c;
			result++;
			n--;

			if(c == '\n')
				break;
		}
		else
		{
			break;
		}
	}

	(*buf) = '\0';

	return(result);
}

/****************************************************************************/

/* check_emulation: take the remote party's version number as
 * arguments and return our possibly modified version number back
 * (relevant only for clients).
 *
 * Return values:
 * EMULATE_VERSION_OK means we can work together
 *
 * EMULATE_VERSION_TOO_OLD if the other party has too old version
 * which we cannot emulate,
 *
 * EMULATE_MAJOR_VERSION_MISMATCH if the other party has different
 * major version and thus will probably not understand anything we
 * say, and
 *
 * EMULATE_VERSION_NEWER if the other party has never code than we
 * have.
 */
static int
check_emulation(int remote_major, int remote_minor, int *return_major, int *return_minor)
{
	(*return_major) = PROTOCOL_MAJOR;

	if(remote_minor < PROTOCOL_MINOR)
		(*return_minor) = remote_minor;
	else
		(*return_minor) = PROTOCOL_MINOR;

	if(remote_major < PROTOCOL_MAJOR)
	{
		return(EMULATE_MAJOR_VERSION_MISMATCH);
	}
	else if (remote_major == 1 && remote_minor == 0)
	{
		return(EMULATE_VERSION_TOO_OLD);  /* We no longer support 1.0. */
	}
	else if (remote_major > PROTOCOL_MAJOR || (remote_major == PROTOCOL_MAJOR && remote_minor > PROTOCOL_MINOR))
	{
		/* The remote software is newer than we. If we are the client,
		 * no matter - the server will decide. If we are the server, we
		 * cannot emulate a newer client and decide to stop.
		 */
		return(EMULATE_VERSION_NEWER);
	}
	else
	{
		return(EMULATE_VERSION_OK);
	}
}

/****************************************************************************/

/* SSH version string exchange */
static int
exchange_identification(struct ssh_protocol_context *spc)
{
	char buf[256], remote_version[256];
	int remote_major, remote_minor, n;
	int my_major, my_minor;
	int result = -1;
	int len;

	/* Read other side's version identification. */
	n = sock_gets(spc->spc_Socket, buf, sizeof(buf));
	if(n == 0)
	{
		fprintf(stderr,"SSH: Bad remote protocol identification.\n");
		goto out;
	}

	buf[n] = '\n';

	/* Check that the versions match.  In future this might accept several
	 * versions and set appropriate flags to handle them.
	 */
	if(sscanf(buf, "SSH-%d.%d-%[^\n]\n", &remote_major, &remote_minor, remote_version) != 3)
	{
		fprintf(stderr,"SSH: Bad remote protocol version identification.\n");
		goto out;
	}

	switch(check_emulation(remote_major, remote_minor, &my_major, &my_minor))
	{
		case EMULATE_VERSION_TOO_OLD:

			/* Remote machine has too old SSH software version */
			fprintf(stderr,"SSH: Remote secure shell version %d.%d is too old.\n",remote_major,remote_major);
			goto out;

		case EMULATE_MAJOR_VERSION_MISMATCH:

			/* Major protocol versions incompatible */
			fprintf(stderr,"SSH: Major secure shell version %d.%d is incompatible with local implementation.\n",remote_major,remote_major);
			goto out;

		case EMULATE_VERSION_NEWER:

			/* We will emulate the old version. */
			break;

		case EMULATE_VERSION_OK:

			break;
	}

	sprintf(buf, "SSH-%d.%d-%s\n", my_major, my_minor, SSH_VERSION);

	len = strlen(buf);

	n = send(spc->spc_Socket, buf, len, 0);
	if(n != len)
	{
		fprintf(stderr,"SSH: Error sending protocol identification (%d, %s).\n",errno,strerror(errno));
		result = n;
		goto out;
	}

	/* Wait for a public key packet from the server. */
	n = read_packet_and_check_type(spc,SSH_SMSG_PUBLIC_KEY);
	if(n == -2)
	{
		fprintf(stderr,"SSH: Corrupt packet data received.\n");
		result = -1;
		goto out;
	}
	else if (n != 0)
	{
		fprintf(stderr,"SSH: Error reading public key packet (%d, %s).\n",errno,strerror(errno));
		result = n;
		goto out;
	}

	result = 0;

 out:

	return(result);
}

/****************************************************************************/

/* create session key and ID */
static int
create_keys(struct ssh_protocol_context *spc)
{
	int i, n, len;
	unsigned char session_key[32];
	unsigned char session_id[16];
	unsigned char *RSAblock, *keystr1, *keystr2;
	unsigned char cookie[8];
	R_RSAKey servkey, hostkey;
	MD5Context md5c;
	int result = 0;

	memcpy(cookie, spc->spc_PacketIn.body, 8);

	i = makekey(spc->spc_PacketIn.body + 8, &servkey, &keystr1);
	makekey(spc->spc_PacketIn.body + 8 + i, &hostkey, &keystr2);

	MD5Init(&md5c);
	MD5Update(&md5c, keystr2, hostkey.bytes);
	MD5Update(&md5c, keystr1, servkey.bytes);
	MD5Update(&md5c, cookie, 8);
	MD5Final(session_id, &md5c);

	for(i = 0; i < 32; i++)
		session_key[i] = get_random_number() % 256;

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

	RSAblock = malloc(len);
	if(RSAblock == NULL)
	{
		/* Out of memory */
		fprintf(stderr,"SSH: Not enough memory for RSA key.\n");
		result = -1;

		goto out;
	}

	memset(RSAblock, 0, len);

	for(i = 0; i < 32; i++)
	{
		RSAblock[i] = session_key[i];
		if(i < 16)
			RSAblock[i] ^= session_id[i];
	}

	if(hostkey.bytes > servkey.bytes)
	{
		rsaencrypt(RSAblock, 32, &servkey);
		rsaencrypt(RSAblock, servkey.bytes, &hostkey);
	}
	else
	{
		rsaencrypt(RSAblock, 32, &hostkey);
		rsaencrypt(RSAblock, hostkey.bytes, &servkey);
	}

	prepare_packet_header(spc, SSH_CMSG_SESSION_KEY, len + 15);
	spc->spc_PacketOut.body[0] = spc->spc_CipherType;
	memcpy(spc->spc_PacketOut.body + 1, cookie, 8);
	spc->spc_PacketOut.body[9] = (len * 8) >> 8;
	spc->spc_PacketOut.body[10] = (len * 8) & 0xFF;
	memcpy(spc->spc_PacketOut.body + 11, RSAblock, len);
	spc->spc_PacketOut.body[len + 11] = spc->spc_PacketOut.body[len + 12] = 0;
	spc->spc_PacketOut.body[len + 13] = spc->spc_PacketOut.body[len + 14] = 0;

	n = write_packet(spc);
	if(n != 0)
	{
		fprintf(stderr,"SSH: Error sending public key packet (%d, %s).\n",errno,strerror(errno));
		result = n;
		goto out;
	}

	free(RSAblock);
	RSAblock = NULL;

	switch(spc->spc_CipherType)
	{
		case SSH_CIPHER_3DES:

			des_set_key(session_key, &spc->spc_Keys[0]);
			des_set_key(session_key + 8, &spc->spc_Keys[1]);
			des_set_key(session_key + 16, &spc->spc_Keys[2]);

			break;

		case SSH_CIPHER_BLOWFISH:

			blowfish_setkey(&spc->spc_EncryptContext, session_key, SSH_SESSION_KEY_LENGTH);
			spc->spc_EncryptContext.biv0 = 0;
			spc->spc_EncryptContext.biv1 = 0;
			spc->spc_DecryptContext = spc->spc_EncryptContext;

			break;
	}

	spc->spc_UseCipher = TRUE;

	/* Wait for key confirmation */
	n = read_packet_and_check_type(spc,SSH_SMSG_SUCCESS);
	if(n == -2)
	{
		fprintf(stderr,"SSH: Corrupt packet data received.\n");
		result = -1;
		goto out;
	}
	if(n != 0)
	{
		fprintf(stderr,"SSH: Remote failed to accept key exchange information (%d, %s).\n",errno,strerror(errno));
		result = n;
		goto out;
	}

 out:

	if(RSAblock != NULL)
		free(RSAblock);

	return(result);
}

/****************************************************************************/

/* Send password and look at the response. */
static int
send_password(struct ssh_protocol_context *spc,char *password)
{
	int result;
	int n;

	n = send_ssh_data(spc,SSH_CMSG_AUTH_PASSWORD,password,strlen(password));
	if(n < 0)
	{
		fprintf(stderr,"SSH: Error sending password (%d, %s).\n",errno,strerror(errno));
		result = n;
		goto out;
	}

	n = read_packet_and_check_type(spc,SSH_SMSG_SUCCESS);
	if(n == -2)
	{
		fprintf(stderr,"SSH: Corrupt packet data received.\n");
		result = -1;
		goto out;
	}
	else if (n != 0)
	{
		fprintf(stderr,"SSH: Remote failed to accept password (%d, %s).\n",errno,strerror(errno));
		result = n;
		goto out;
	}

	result = 0;

 out:

	return(result);
}

/****************************************************************************/

static int
set_maximum_packet_size(struct ssh_protocol_context *spc)
{
	int size = sizeof(spc->spc_InBuf);
	int result = 0;
	int n;

	prepare_packet_header(spc,SSH_CMSG_MAX_PACKET_SIZE,4);

	spc->spc_PacketOut.body[0] = 0;
	spc->spc_PacketOut.body[1] = 0;
	spc->spc_PacketOut.body[2] = size >> 8;
	spc->spc_PacketOut.body[3] = size & 0xFF;

	n = write_packet(spc);
	if(n != 0)
	{
		fprintf(stderr,"SSH: Failed to send packet size configuration packet (%d, %s).\n",errno,strerror(errno));
		result = n;
		goto out;
	}

	n = read_packet_and_check_type(spc,SSH_SMSG_SUCCESS);
	if(n == -2)
	{
		fprintf(stderr,"SSH: Corrupt packet data received.\n");
		result = -1;
		goto out;
	}
	else if (n != 0)
	{
		fprintf(stderr,"SSH: Remote failed to accept packet size (%d, %s).\n",errno,strerror(errno));
		result = n;
		goto out;
	}

 out:

	return(result);
}

/****************************************************************************/

void
ssh_disconnect(struct ssh_protocol_context *spc)
{
	if(spc != NULL)
	{
		if(spc->spc_Socket != -1)
			CloseSocket(spc->spc_Socket);

		free(spc);
	}
}

/****************************************************************************/

struct ssh_protocol_context *
ssh_connect(char *remote_host_name,int port_number,char *user_name,char *password,int cipher_type)
{
	struct ssh_protocol_context *result = NULL;
	struct ssh_protocol_context *spc;
	struct sockaddr_in sin;
	struct hostent *phe;
	int n;

	spc = malloc(sizeof(*spc));
	if(spc == NULL)
	{
		fprintf(stderr,"SSH: Not enough memory for connection information.\n");
		/* Not enough memory */
		goto out;
	}

	memset(spc,0,sizeof(*spc));
	spc->spc_Socket = -1;
	spc->spc_CipherType = cipher_type;
	spc->spc_UseCipher = FALSE;

	memset(&sin, 0, sizeof(sin));

	sin.sin_family = AF_INET;
	sin.sin_port = htons(port_number);

	phe = gethostbyname(remote_host_name);
	if(phe != NULL)
	{
		memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
	}
	else
	{
		sin.sin_addr.s_addr = inet_addr(remote_host_name);
		if(sin.sin_addr.s_addr == -1)
		{
			fprintf(stderr,"SSH: Could not resolve host name '%s'.\n",remote_host_name);
			/* Cannot resolve this host name */
			goto out;
		}
	}

	spc->spc_Socket = socket(AF_INET, SOCK_STREAM, 0);
	if(spc->spc_Socket < 0)
	{
		fprintf(stderr,"SSH: Could not create socket (%d, %s).\n",errno,strerror(errno));
		/* Could not create socket */
		goto out;
	}

	if(connect(spc->spc_Socket,(struct sockaddr *) &sin, sizeof(sin)) < 0)
	{
		fprintf(stderr,"SSH: Could not connect socket (%d, %s).\n",errno,strerror(errno));
		/* Unable to open connection */
		goto out;
	}

	/* Start negotiation on network */
	n = exchange_identification(spc);
	if(n != 0)
		goto out;

	/* Create SSH keys */
	n = create_keys(spc);
	if(n != 0)
		goto out;

	/* Send the user name. */
	n = send_ssh_data(spc,SSH_CMSG_USER,user_name,strlen(user_name));
	if(n < 0)
	{
		fprintf(stderr,"SSH: Could not send user name (%d, %s).\n",errno,strerror(errno));
		goto out;
	}

	n = read_packet(spc);
	if(n == -2)
	{
		fprintf(stderr,"SSH: Corrupt packet data received.\n");
		goto out;
	}
	else if (n < 0)
	{
		fprintf(stderr,"SSH: Could not read response to user name (%d, %s).\n",errno,strerror(errno));
		goto out;
	}

	switch(spc->spc_PacketIn.type)
	{
		case SSH_SMSG_SUCCESS:	/* no authentication needed */

			break;

		case SSH_SMSG_FAILURE:	/* send password */

			n = send_password(spc,password);
			if(n != 0)
				goto out;

			break;

		default:

			/* Invalid packet received. */
			goto out;
	}

	n = set_maximum_packet_size(spc);
	if(n != 0)
		goto out;

	result = spc;

 out:

	if(result == NULL)
		ssh_disconnect(spc);

	return(result);
}

/****************************************************************************/

/* Convert raw, encrypted packet to readable structure,
 * return type of packet received.
 */
static int
convert_received_data(struct ssh_protocol_context *spc)
{
	unsigned char *data = spc->spc_InBuf;
	unsigned long crc;
	long len, biglen;
	int i, pad;
	int result;

	for(i = len = 0; i < 4; i++)
		len = (len << 8) + data[i];

	pad = 8 - (len % 8);
	biglen = len + pad;

	spc->spc_PacketIn.length = biglen - 4 - pad;

	if(spc->spc_UseCipher)
	{
		switch(spc->spc_CipherType)
		{
			case SSH_CIPHER_3DES:

				des_3cbc_decrypt(data + 4, data + 4, biglen, spc->spc_Keys);
				break;

			case SSH_CIPHER_BLOWFISH:

				blowfish_decrypt_cbc(data + 4, biglen, &spc->spc_DecryptContext);
				break;
		}
	}

	for(i = crc = 0; i < 4; i++)
		crc = (crc << 8) + data[biglen + i];

	if(crc == crc32(data + 4, biglen - 4))
	{
		memcpy(spc->spc_PacketIn.data, data + 4, biglen);

		spc->spc_PacketIn.type = spc->spc_PacketIn.data[pad];
		spc->spc_PacketIn.body = spc->spc_PacketIn.data + pad + 1;

		result = spc->spc_PacketIn.type;
	}
	else
	{
		/* Packet checksum does not match packet data! */
		result = -1;
	}

	return(result);
}

/****************************************************************************/

/* create header for raw outgoing packet */
static void 
prepare_packet_header(struct ssh_protocol_context *spc, int type, int len)
{
	int pad;

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

	spc->spc_PacketOut.length = len - 5;

	spc->spc_PacketOut.type = type;
	spc->spc_PacketOut.body = spc->spc_PacketOut.data + 4 + pad + 1;
}

/****************************************************************************/

/* create outgoing packet */
static int
write_packet(struct ssh_protocol_context *spc)
{
	int pad, len, biglen, n, i;
	unsigned long crc;
	int result = 0;

	len = spc->spc_PacketOut.length + 5; /* type and CRC */
	pad = 8 - (len % 8);
	biglen = len + pad;

	spc->spc_PacketOut.body[-1] = spc->spc_PacketOut.type;
	for(i = 0; i < pad; i++)
		spc->spc_PacketOut.data[i + 4] = get_random_number() % 256;

	crc = crc32(spc->spc_PacketOut.data + 4, biglen - 4);

	spc->spc_PacketOut.data[biglen + 0] = (unsigned char) ((crc >> 24) & 0xFF);
	spc->spc_PacketOut.data[biglen + 1] = (unsigned char) ((crc >> 16) & 0xFF);
	spc->spc_PacketOut.data[biglen + 2] = (unsigned char) ((crc >>  8) & 0xFF);
	spc->spc_PacketOut.data[biglen + 3] = (unsigned char) ( crc        & 0xFF);

	spc->spc_PacketOut.data[0] = (len >> 24) & 0xFF;
	spc->spc_PacketOut.data[1] = (len >> 16) & 0xFF;
	spc->spc_PacketOut.data[2] = (len >>  8) & 0xFF;
	spc->spc_PacketOut.data[3] =  len        & 0xFF;

	if(spc->spc_UseCipher)
	{
		switch(spc->spc_CipherType)
		{
			case SSH_CIPHER_3DES:

				des_3cbc_encrypt(spc->spc_PacketOut.data + 4, spc->spc_PacketOut.data + 4, biglen, spc->spc_Keys);
				break;

			case SSH_CIPHER_BLOWFISH:

				blowfish_encrypt_cbc(spc->spc_PacketOut.data + 4, biglen, &spc->spc_EncryptContext);
				break;
		}
	}

	n = send(spc->spc_Socket, spc->spc_PacketOut.data, biglen + 4, 0);
	if(n != biglen + 4)
		result = n;

	return(result);
}

/****************************************************************************/

/* Read a packet with blocking */
static int 
read_packet(struct ssh_protocol_context *spc)
{
	int result = 0;
	int biglen;
	int type;
	int len;
	int n;
	int i;

	do
	{
		n = recv_all(spc->spc_Socket, spc->spc_InBuf, 4);
		if(n != 4)
		{
			result = n;
			break;
		}

		for(i = len = 0; i < 4; i++)
			len = (len << 8) + spc->spc_InBuf[i];  /* Get packet size */

		biglen = len + 8 - (len % 8);

		n = recv_all(spc->spc_Socket, spc->spc_InBuf + 4, biglen);  /* Read it */
		if(n != biglen)
		{
			result = n;
			break;
		}

		type = convert_received_data(spc);
		if(type < 0)
		{
			result = -2;
			break;
		}
		else if (type == SSH_MSG_DISCONNECT)
		{
			result = -1;
			break;
		}
	}
	while(spc->spc_PacketIn.type == SSH_MSG_DEBUG ||
	      spc->spc_PacketIn.type == SSH_MSG_IGNORE);

	return(result);
}

/****************************************************************************/

/* Read a packet and check its type against the one expected. */
static int
read_packet_and_check_type(struct ssh_protocol_context *spc,int type)
{
	int n;

	n = read_packet(spc);
	if(n == 0)
	{
		if(spc->spc_PacketIn.type != type) /* Invalid answer from server */
		{
			errno = EACCES;
			n = -1;
		}
	}

	return(n);
}

/****************************************************************************/

/* Send data using the SSH protocol, in several steps if necessary. */
static int
send_ssh_data(struct ssh_protocol_context *spc,int type,void *data,int len)
{
	unsigned char * m = data;
	int total = 0;
	int l,n;

	while(len > 0)
	{
		if(len > sizeof(spc->spc_InBuf)-(4+8+1+4))
			n = sizeof(spc->spc_InBuf)-(4+8+1+4);
		else
			n = len;

		prepare_packet_header(spc,type,n+4);

		spc->spc_PacketOut.body[0] = (n >> 24) & 0xFF;
		spc->spc_PacketOut.body[1] = (n >> 16) & 0xFF;
		spc->spc_PacketOut.body[2] = (n >>  8) & 0xFF;
		spc->spc_PacketOut.body[3] =  n        & 0xFF;

		memcpy(&spc->spc_PacketOut.body[4],m,n);

		l = write_packet(spc);
		if(l < 0)
		{
			total = l;
			break;
		}

		total += n;
		len -= n;
		m += n;
	}

	return(total);
}

/****************************************************************************/

int
ssh_execute_cmd(struct ssh_protocol_context *spc,char *command)
{
	int result;
	int len;

	if(command != NULL)
		len = strlen(command);
	else
		len = 0;

	if(len > 0)
	{
		result = send_ssh_data(spc,SSH_CMSG_EXEC_CMD,command,len);
		if(result < 0)
			fprintf(stderr,"SSH: Failed to execute command '%s' (%d, %s).\n",command,errno,strerror(errno));
	}
	else
	{
		result = 0;
	}

	return(result);
}

/****************************************************************************/

int
ssh_write(struct ssh_protocol_context *spc, void *data, int len)
{
	int result;

	result = send_ssh_data(spc,SSH_CMSG_STDIN_DATA,data,len);

	return(result);
}

/****************************************************************************/

int
ssh_read(struct ssh_protocol_context *spc, void *data, int len)
{
	int total = 0;

	if(spc->spc_BytesLeft >= 0)
	{
		unsigned char * m = data;

		while(len > 0)
		{
			if(spc->spc_BytesLeft == 0)
			{
				int payload_size,packet_size,n,i;

				/* Careful there: we don't want to linger in this
				 * routine any longer than is really necessary. If
				 * the previous iteration managed to obtain some
				 * data already, do not attempt to read any more
				 * data as that might cause this routine to block
				 * for no good reason at all.
				 */
				if(total > 0)
				{
					fd_set read_set;
					struct timeval tv;

					FD_ZERO(&read_set);
					FD_SET(spc->spc_Socket,&read_set);
					memset(&tv,0,sizeof(tv));

					/* If there is no additional data waiting,
					 * call it a day and return what we've already
					 * got.
					 */
					if(select(spc->spc_Socket+1,&read_set,NULL,NULL,&tv) <= 0)
						break;
				}

				n = recv_all(spc->spc_Socket,spc->spc_InBuf,4);
				if(n != 4)
				{
					spc->spc_BytesLeft = -1;

					if(n < 0)
						total = n;

					break;
				}

				for(i = payload_size = 0; i < 4; i++)
					payload_size = (payload_size << 8) + spc->spc_InBuf[i];

				packet_size = payload_size + (8 - (payload_size % 8));

				n = recv_all(spc->spc_Socket,&spc->spc_InBuf[4],packet_size);
				if(n != packet_size)
				{
					spc->spc_BytesLeft = -1;

					if(n < 0)
						total = n;

					break;
				}

				switch(convert_received_data(spc))
				{
					case SSH_SMSG_STDERR_DATA:

						fwrite(&spc->spc_PacketIn.body[4],spc->spc_PacketIn.length-5,1,stderr);
						break;

					case SSH_SMSG_STDOUT_DATA:

						spc->spc_BytesLeft = spc->spc_PacketIn.length-5;
						spc->spc_Ptr = &spc->spc_PacketIn.body[4];

						break;

					case SSH_SMSG_EXITSTATUS:

						prepare_packet_header(spc,SSH_CMSG_EXIT_CONFIRMATION,0);
						write_packet(spc);

						spc->spc_BytesLeft = -1;
						goto out;

					case SSH_MSG_DISCONNECT:

						spc->spc_BytesLeft = -1;
						goto out;

					case SSH_SMSG_SUCCESS:
					case SSH_MSG_IGNORE:

						break;

					case -1:

						fprintf(stderr,"SSH: Corrupt packet data received.\n");
						spc->spc_BytesLeft = -1;
						goto out;

					default:

						break;
				}
			}

			if(spc->spc_BytesLeft > 0)
			{
				int n;

				if(len > spc->spc_BytesLeft)
					n = spc->spc_BytesLeft;
				else
					n = len;

				memcpy(m,spc->spc_Ptr,n);
				m += n;
				total += n;
				len -= n;

				spc->spc_Ptr += n;
				spc->spc_BytesLeft -= n;
			}
		}
	}

 out:

	return(total);
}
