/****************************************************************************
*																			*
*							cryptlib SSH Test Routines						*
*						Copyright Peter Gutmann 1998-2005					*
*																			*
****************************************************************************/

#include "cryptlib.h"
#include "test/test.h"

#if defined( __MVS__ ) || defined( __VMCMS__ )
  /* Suspend conversion of literals to ASCII. */
  #pragma convlit( suspend )
#endif /* IBM big iron */
#if defined( __ILEC400__ )
  #pragma convert( 0 )
#endif /* IBM medium iron */

/* Uncomment the following to ask the user for a password rather than using
   a hardcoded password when testing against live accounts */

/* #define USER_SUPPLIED_PASSWORD */
#ifdef USER_SUPPLIED_PASSWORD
  #undef SSH2_SERVER_NAME
  #undef SSH_USER_NAME
  #define SSH2_SERVER_NAME	"testserver"
  #define SSH_USER_NAME		"testname"
#endif /* USER_SUPPLIED_PASSWORD */

/* We can run the SSH self-test with a large variety of options, rather than
   using dozens of boolean option flags to control them all we define
   various test classes that exercise each option type */

typedef enum {
	SSH_TEST_NORMAL,			/* Standard SSHv2 test */
	SSH_TEST_SSH1,				/* SSHv1 test */
	SSH_TEST_CLIENTCERT,		/* Use client public-key for auth */
	SSH_TEST_SUBSYSTEM,			/* Test SFTP subsystem */
	SSH_TEST_PORTFORWARDING,	/* Test port forwarding */
	SSH_TEST_EXEC,				/* Test rexec rather than rsh functionality */
	SSH_TEST_MULTICHANNEL,		/* Test multi-channel handling */
	SSH_TEST_FINGERPRINT,		/* Test (invalid) key fingerprint */
	SSH_TEST_CONFIRMAUTH		/* Test manual server confirmation of auth.*/
	} SSH_TEST_TYPE;

#ifdef TEST_SESSION

/****************************************************************************
*																			*
*								Utility Functions							*
*																			*
****************************************************************************/

/* Test the ability to parse URLs */

static const struct {
	const C_STR url;			/* Server URL */
	const C_STR name;			/* Parsed server name */
	const int port;				/* Parsed server port */
	const C_STR userInfo;		/* Parsed user info */
	} FAR_BSS urlParseInfo[] = {
	/* IP address forms */
	{ TEXT( "1.2.3.4" ), TEXT( "1.2.3.4" ), 0, NULL },
	{ TEXT( "1.2.3.4:80" ), TEXT( "1.2.3.4" ), 80, NULL },
	{ TEXT( "user@1.2.3.4" ), TEXT( "1.2.3.4" ), 0, TEXT( "user" ) },
	{ TEXT( "[1:2:3:4]" ), TEXT( "1:2:3:4" ), 0, NULL },
	{ TEXT( "[1:2:3:4]:80" ), TEXT( "1:2:3:4" ), 80, NULL },
	{ TEXT( "user@[1:2:3:4]" ), TEXT( "1:2:3:4" ), 0, TEXT( "user" ) },

	/* General URI forms */
	{ TEXT( "www.server.com" ), TEXT( "www.server.com" ), 0, NULL },
	{ TEXT( "www.server.com:80" ), TEXT( "www.server.com" ), 80, NULL },
	{ TEXT( "http://www.server.com:80" ), TEXT( "www.server.com" ), 80, NULL },
	{ TEXT( "http://user@www.server.com:80" ), TEXT( "www.server.com" ), 80, TEXT( "user" ) },

	/* Spurious whitespace */
	{ TEXT( "  www.server.com  :   80 " ), TEXT( "www.server.com" ), 80, NULL },
	{ TEXT( "http:// user  @ www.server.com  :   80 " ), TEXT( "www.server.com" ), 80, TEXT( "user" ) },
	{ NULL, NULL, 0, NULL }
	};

int testSessionUrlParse( void )
	{
	CRYPT_SESSION cryptSession;
	int i, status;

	puts( "Testing session URL parsing..." );

	/* Create a session of the most generic type */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED, CRYPT_SESSION_SSL );
	if( status == CRYPT_ERROR_PARAM3 )	/* SSL session access not available */
		return( CRYPT_ERROR_NOTAVAIL );
	if( cryptStatusError( status ) )
		{
		printf( "cryptCreateSession() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}

	/* Set various URLs as the server name and retrieve the parsed form */
	for( i = 0; urlParseInfo[ i ].url != NULL; i++ )
		{
		C_CHR nameBuffer[ 256 ], userInfoBuffer[ 256 ];
		int lengthLength, userInfoLength, port;

		/* Clear any leftover attributes from previous tests */
		cryptDeleteAttribute( cryptSession, CRYPT_SESSINFO_SERVER_NAME );
		cryptDeleteAttribute( cryptSession, CRYPT_SESSINFO_SERVER_PORT );
		cryptDeleteAttribute( cryptSession, CRYPT_SESSINFO_USERNAME );

		/* Set the URL */
		status = cryptSetAttributeString( cryptSession,
										  CRYPT_SESSINFO_SERVER_NAME,
										  urlParseInfo[ i ].url,
										  paramStrlen( urlParseInfo[ i ].url ) );
		if( cryptStatusError( status ) )
			{
			printf( "Couldn't set URL '%s', line %d.\n",
					urlParseInfo[ i ].url, __LINE__ );
			return( FALSE );
			}

		/* Make sure that the parsed form is OK */
		status = cryptGetAttributeString( cryptSession,
										  CRYPT_SESSINFO_SERVER_NAME,
										  nameBuffer, &lengthLength );
		if( cryptStatusOK( status ) && urlParseInfo[ i ].port )
			status = cryptGetAttribute( cryptSession,
										CRYPT_SESSINFO_SERVER_PORT, &port );
		if( cryptStatusOK( status ) && urlParseInfo[ i ].userInfo != NULL )
			status = cryptGetAttributeString( cryptSession,
											  CRYPT_SESSINFO_USERNAME,
											  userInfoBuffer,
											  &userInfoLength );
		if( cryptStatusError( status ) )
			{
			printf( "Couldn't get parsed URL info for '%s', line %d.\n",
					urlParseInfo[ i ].url, __LINE__ );
			return( FALSE );
			}
		if( memcmp( nameBuffer, urlParseInfo[ i ].name, lengthLength ) || \
			( urlParseInfo[ i ].port && port != urlParseInfo[ i ].port ) || \
			( urlParseInfo[ i ].userInfo != NULL && \
			  memcmp( userInfoBuffer, urlParseInfo[ i ].userInfo,
			  userInfoLength ) ) )
			{
			printf( "Parsed URL info for '%s' is incorrect, line %d.\n",
					urlParseInfo[ i ].url, __LINE__ );
			return( FALSE );
			}
		}

	/* Clean up */
	status = cryptDestroySession( cryptSession );
	if( cryptStatusError( status ) )
		{
		printf( "cryptDestroySession() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	puts( "Session URL parsing succeeded.\n" );
	return( TRUE );
	}

/* Test session attribute handling */

int testSessionAttributes( void )
	{
	CRYPT_SESSION cryptSession;
	int status;

	puts( "Testing session attribute handling..." );

	/* Create a server session of the most generic type */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_SSL_SERVER );
	if( status == CRYPT_ERROR_PARAM3 )	/* SSL server session access not avail.*/
		return( CRYPT_ERROR_NOTAVAIL );
	if( cryptStatusError( status ) )
		{
		printf( "cryptCreateSession() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}

	/* Add an initial attribute */
	status = cryptSetAttributeString( cryptSession,
								CRYPT_SESSINFO_SERVER_NAME, TEXT( "servername" ),
								paramStrlen( TEXT( "servername" ) ) );
	if( cryptStatusError( status ) )
		{
		printf( "cryptSetAttributeString() failed with error code %d, "
				"line %d.\n", status, __LINE__ );
		return( FALSE );
		}

	/* Add several username/password pairs */
	status = cryptSetAttributeString( cryptSession,
								CRYPT_SESSINFO_USERNAME, TEXT( "test1" ),
								paramStrlen( TEXT( "test1" ) ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
								CRYPT_SESSINFO_PASSWORD, TEXT( "test1" ),
								paramStrlen( TEXT( "test1" ) ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
								CRYPT_SESSINFO_USERNAME, TEXT( "test2" ),
								paramStrlen( TEXT( "test2" ) ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
								CRYPT_SESSINFO_PASSWORD, TEXT( "test2" ),
								paramStrlen( TEXT( "test2" ) ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
								CRYPT_SESSINFO_USERNAME, TEXT( "test3" ),
								paramStrlen( TEXT( "test3" ) ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
								CRYPT_SESSINFO_PASSWORD, TEXT( "test3" ),
								paramStrlen( TEXT( "test3" ) ) );
	if( cryptStatusError( status ) )
		{
		printf( "cryptSetAttributeString() for username/password pairs "
				"failed with error code %d, line %d.\n", status, __LINE__ );
		return( FALSE );
		}

	/* Add a duplicate entry and make sure that it's detected */
	status = cryptSetAttributeString( cryptSession,
								CRYPT_SESSINFO_USERNAME, TEXT( "test2" ),
								paramStrlen( TEXT( "test2" ) ) );
	if( status != CRYPT_ERROR_DUPLICATE )
		{
		printf( "Addition of duplicate user/password entry wasn't detected, "
				"line %d.\n", __LINE__ );
		return( FALSE );
		}

	/* Add a password without a preceding username and make sure that it's
	   detected */
	status = cryptSetAttributeString( cryptSession,
								CRYPT_SESSINFO_PASSWORD, TEXT( "invalid_pw" ),
								paramStrlen( TEXT( "invalid_pw" ) ) );
	if( status != CRYPT_ERROR_NOTINITED )
		{
		printf( "Addition of password without username wasn't detected, "
				"line %d.\n", __LINE__ );
		return( FALSE );
		}

	/* Add a username without a password and make sure that it's detected */
	status = cryptSetAttributeString( cryptSession,
								CRYPT_SESSINFO_USERNAME, TEXT( "valid_name" ),
								paramStrlen( TEXT( "valid_name" ) ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
								CRYPT_SESSINFO_USERNAME, TEXT( "invalid_name" ),
								paramStrlen( TEXT( "invalid_name" ) ) );
	if( status != CRYPT_ERROR_PARAM2 )
		{
		printf( "Addition of username without password wasn't detected, "
				"line %d.\n", __LINE__ );
		return( FALSE );
		}

	/* Clean up */
	status = cryptDestroySession( cryptSession );
	if( cryptStatusError( status ) )
		{
		printf( "cryptDestroySession() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}
	puts( "Session attribute handling succeeded.\n" );
	return( TRUE );
	}

/* Test the ability to have multiple server threads waiting on a session.
   Since this requries (OS-specific) threading, we just use two sample
   systems, Win32 (Windows threads) and Linux (pthreads).  Since Linux's
   somewhat strange not-quite-a-thread/not-quite-a-process implementation
   can be a bit buggy, we also use another sample pthreads implementation
   (FreeBSD/NetBSD) as a sanity check */

#define NO_SERVER_THREADS	4

#if defined( WINDOWS_THREADS ) || defined( __linux ) || \
	defined( __FreeBSD__ ) || defined( __NetBSD__ )

#if defined( __FreeBSD__ ) || defined( __NetBSD__ )
  #include <pthread.h>
#endif /* FreeBSD || NetBSD */

#ifdef WINDOWS_THREADS
  #define THREAD_HANDLE		HANDLE
  #define THREAD_EXIT()		_endthreadex( 0 ); return( 0 )
  #define THREAD_SELF()		GetCurrentThreadId()
#else
  #define THREAD_HANDLE		pthread_t
  #define THREAD_EXIT()		pthread_exit( ( void * ) 0 ); return( 0 )
  #define THREAD_SELF()		pthread_self()
#endif /* Windows vs. pthreads */

#ifdef WINDOWS_THREADS
  unsigned __stdcall sshMultiServerThread( void *dummy )
#else
  void *sshMultiServerThread( void *dummy )
#endif /* Windows vs. pthreads */
	{
	CRYPT_SESSION cryptSession;
	CRYPT_CONTEXT privateKey;
	int status;

	printf( "Server thread %lX activated.\n", THREAD_SELF() );

	/* Create the session and try to activate it.  We don't do anything
	   beyond that point since this is a test of multi-thread handling
	   capability, not session handling */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_SSH_SERVER );
	if( cryptStatusError( status ) )
		{
		printf( "cryptCreateSession() failed with error code %d, line %d.\n",
				status, __LINE__ );
		THREAD_EXIT();
		}
	if( !setLocalConnect( cryptSession, 22 ) )
		{
		THREAD_EXIT();
		}
	status = getPrivateKey( &privateKey, SSH_PRIVKEY_FILE,
							USER_PRIVKEY_LABEL, TEST_PRIVKEY_PASSWORD );
	if( cryptStatusOK( status ) )
		{
		status = cryptSetAttribute( cryptSession,
									CRYPT_SESSINFO_PRIVATEKEY, privateKey );
		cryptDestroyContext( privateKey );
		}
	if( cryptStatusError( status ) )
		{
		printf( "cryptSetAttribute/AttributeString() failed with error code "
				"%d, line %d.\n", status, __LINE__ );
		THREAD_EXIT();
		}
	printf( "Server for thread %lX activated.\n", THREAD_SELF() );
	status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE, TRUE );
	printConnectInfo( cryptSession );
	if( cryptStatusError( status ) )
		printExtError( cryptSession,
					   "Attempt to activate SSH server session", status,
					   __LINE__ );
	cryptDestroySession( cryptSession );
	printf( "Server for thread %lX has exited.\n", THREAD_SELF() );

	THREAD_EXIT();
	}

#ifdef WINDOWS_THREADS
  unsigned __stdcall sshMultiClientThread( void *dummy )
#else
  void *sshMultiClientThread( void *dummy )
#endif /* Windows vs. pthreads */
	{
	CRYPT_SESSION cryptSession;
	int status;

	printf( "Client thread %lX activated.\n", THREAD_SELF() );

	/* Create the session and try to activate it.  We don't do anything
	   beyond that point since this is a test of multi-thread handling
	   capability, not session handling */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED,
								 CRYPT_SESSION_SSH );
	if( cryptStatusError( status ) )
		{
		printf( "cryptCreateSession() failed with error code %d, line %d.\n",
				status, __LINE__ );
		THREAD_EXIT();
		}
	if( !setLocalConnect( cryptSession, 22 ) )
		{
		THREAD_EXIT();
		}
	status = cryptSetAttribute( cryptSession,
								CRYPT_OPTION_NET_CONNECTTIMEOUT, 10 );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
										  CRYPT_SESSINFO_USERNAME,
										  SSH_USER_NAME,
										  paramStrlen( SSH_USER_NAME ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
										  CRYPT_SESSINFO_PASSWORD,
										  SSH_PASSWORD,
										  paramStrlen( SSH_PASSWORD ) );
	if( cryptStatusError( status ) )
		{
		printf( "cryptSetAttribute/AttributeString() failed with error code "
				"%d, line %d.\n", status, __LINE__ );
		THREAD_EXIT();
		}
	printf( "Client for thread %lX activated.\n", THREAD_SELF() );
	status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE, TRUE );
	printConnectInfo( cryptSession );
	if( cryptStatusError( status ) )
		printExtError( cryptSession,
					   "Attempt to activate SSH client session", status,
					   __LINE__ );
	cryptDestroySession( cryptSession );
	printf( "Client for thread %lX has exited.\n", THREAD_SELF() );

	THREAD_EXIT();
	}

int testSessionSSHMultiServer( void )
	{
	THREAD_HANDLE hClientThreads[ NO_SERVER_THREADS ];
	THREAD_HANDLE hServerThreads[ NO_SERVER_THREADS ];
	int i;

	/* Start the sessions and wait for them initialise.  We have to wait for
	   some time since the multiple private key reads can take awhile */
	for( i = 0; i < NO_SERVER_THREADS; i++ )
		{
#ifdef WINDOWS_THREADS
		unsigned int threadID;

		hServerThreads[ i ] = ( HANDLE ) \
						_beginthreadex( NULL, 0, sshMultiServerThread,
										NULL, 0, &threadID );
#else
		pthread_t threadHandle;

		hServerThreads[ i ] = 0;
		if( pthread_create( &threadHandle, NULL, sshMultiServerThread,
							NULL ) == 0 )
			hServerThreads[ i ] = threadHandle;
#endif /* Windows vs. pthreads */
		}
	delayThread( 3 );

	/* Connect to the local server */
	for( i = 0; i < NO_SERVER_THREADS; i++ )
		{
#ifdef WINDOWS_THREADS
		unsigned int threadID;

		hClientThreads[ i ] = ( HANDLE ) \
						_beginthreadex( NULL, 0, sshMultiClientThread,
										NULL, 0, &threadID );
#else
		pthread_t threadHandle;

		hServerThreads[ i ] = 0;
		if( pthread_create( &threadHandle, NULL, sshMultiClientThread,
							NULL ) == 0 )
			hClientThreads[ i ] = threadHandle;
#endif /* Windows vs. pthreads */
		}
#ifdef WINDOWS_THREADS
	if( WaitForMultipleObjects( NO_SERVER_THREADS, hServerThreads, TRUE,
								60000 ) == WAIT_TIMEOUT )
#else
	/* Posix doesn't have an ability to wait for multiple threads for mostly
	   religious reasons ("That's not how we do things around here") so we
	   just wait for two token threads */
	pthread_join( hServerThreads[ 0 ], NULL );
	pthread_join( hClientThreads[ 0 ], NULL );
#endif /* Windows vs. pthreads */
		{
		puts( "Warning: Server threads are still active due to session "
			  "negotiation failure,\n         this will cause an error "
			  "condition when cryptEnd() is called due\n         to "
			  "resources remaining allocated.  Press a key to continue." );
		getchar();
		}
#ifdef WINDOWS_THREADS
	for( i = 0; i < NO_SERVER_THREADS; i++ )
		if( hServerThreads[ i ] != 0 )
			CloseHandle( hServerThreads[ i ] );
	for( i = 0; i < NO_SERVER_THREADS; i++ )
		if( hClientThreads[ i ] != 0 )
			CloseHandle( hClientThreads[ i ] );
#endif /* Windows vs. pthreads */

	return( TRUE );
	}
#endif /* OS-specific threading functions */

/* Create an SSH channel */

static int createChannel( const CRYPT_SESSION cryptSession,
						  const C_STR type, const C_STR arg1 )
	{
	int status;

	status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_SSH_CHANNEL,
								CRYPT_UNUSED );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
										  CRYPT_SESSINFO_SSH_CHANNEL_TYPE,
										  type, paramStrlen( type ) );
	if( cryptStatusOK( status ) )
		status = cryptSetAttributeString( cryptSession,
										  CRYPT_SESSINFO_SSH_CHANNEL_ARG1,
										  arg1, paramStrlen( arg1 ) );
	return( status );
	}

/* Print information on an SSH channel */

static int printChannelInfo( const CRYPT_SESSION cryptSession,
							 const SSH_TEST_TYPE testType,
							 const BOOLEAN isServer )
	{
	C_CHR stringBuffer[ CRYPT_MAX_TEXTSIZE + 1 ];
	C_CHR argBuffer[ CRYPT_MAX_TEXTSIZE + 1 ];
	int channel, stringLength, argLength = 0, status;

	status = cryptGetAttribute( cryptSession, CRYPT_SESSINFO_SSH_CHANNEL,
								&channel );
	if( cryptStatusOK( status ) )
		status = cryptGetAttributeString( cryptSession,
										  CRYPT_SESSINFO_SSH_CHANNEL_TYPE,
										  stringBuffer, &stringLength );
	if( cryptStatusError( status ) )
		{
		printf( "%sCouldn't query channel ID/type, status %d, line %d.\n",
				isServer ? "SVR: " : "", status, __LINE__ );
		return( status );
		}
#ifdef UNICODE_STRINGS
	stringBuffer[ stringLength / sizeof( wchar_t ) ] = TEXT( '\0' );
#else
	stringBuffer[ stringLength ] = '\0';
#endif /* UNICODE_STRINGS */
	if( !paramStrcmp( stringBuffer, TEXT( "subsystem" ) ) || \
		!paramStrcmp( stringBuffer, TEXT( "direct-tcpip" ) ) )
		{
		status = cryptGetAttributeString( cryptSession,
										  CRYPT_SESSINFO_SSH_CHANNEL_ARG1,
										  argBuffer, &argLength );
		if( cryptStatusError( status ) )
			{
			printf( "%sCouldn't query channel arg, status %d, line %d.\n",
					isServer ? "SVR: " : "", status, __LINE__ );
			return( status );
			}
#ifdef UNICODE_STRINGS
		argBuffer[ argLength / sizeof( wchar_t ) ] = TEXT( '\0' );
		printf( "SVR: Client opened channel #%d, type '%S', arg '%S'.\n",
				channel, stringBuffer, argBuffer );
#else
		argBuffer[ argLength ] = '\0';
		printf( "SVR: Client opened channel #%d, type '%s', arg '%s'.\n",
				channel, stringBuffer, argBuffer );
#endif /* UNICODE_STRINGS */

		return( CRYPT_OK );
		}

	if( testType == SSH_TEST_SUBSYSTEM )
		{
		printf( "SVR: Client requested subsystem but server didn't "
				"report it, line %d.\n", __LINE__ );
		return( CRYPT_ERROR_FAILED );
		}

#ifdef UNICODE_STRINGS
	printf( "SVR: Client opened channel #%d, type '%S'.\n",
			channel, stringBuffer );
#else
	printf( "SVR: Client opened channel #%d, type '%s'.\n",
			channel, stringBuffer );
#endif /* UNICODE_STRINGS */
	return( CRYPT_OK );
	}

/* Print information on data sent over an SSH channel */

static int printDataInfo( CRYPT_SESSION cryptSession,
						  char *buffer, int *bytesCopied,
						  const BOOLEAN isServer )
	{
	int channel, status;

	status = cryptPopData( cryptSession, buffer, BUFFER_SIZE, bytesCopied );
	if( cryptStatusError( status ) )
		{
		printf( "%sCouldn't read data from %s, status %d, line %d.\n",
				isServer ? "SVR: " : "", isServer ? "client" : "server",
				status, __LINE__ );
		return( status );
		}
	buffer[ *bytesCopied ] = '\0';
	cryptGetAttribute( cryptSession, CRYPT_SESSINFO_SSH_CHANNEL, &channel );
	printf( "%s---- %s sent %d bytes on channel %d ----\n",
			isServer ? "SVR: " : "", isServer ? "Client" : "Server",
			*bytesCopied, channel );
	if( isServer )
		printf( "SVR: " );
	puts( buffer );
	printf( "%s---- End of output ----\n", isServer ? "SVR: " : "" );

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*								SSH Routines Test							*
*																			*
****************************************************************************/

/* There are various servers running that we can use for testing, the
   following remapping allows us to switch between them.  Notes:

	Server 1: Local loopback.
	Server 2: Sends extraneous lines of text before the SSH ID string
			  (technically allowed by the RFC, but probably not in the way
			  that it's being used here).
	Server 3: Reference ssh.com implementation.
	Server 4: Reference OpenSSH implementation.
	Server 5: Generic embedded device implementation.

   To test local -> remote/remote -> local forwarding:

	ssh localhost -v -l test -pw test -L 110:pop3.test.com:110
	ssh localhost -v -l test -pw test -R 110:pop3.test.com:110

  For test purposes we connect to the OpenSSH server for the SSHv2 test
  because this is the most frequently-used one around, so maintaining
  compatibility with it whenever it changes is important.  Using it for test
  connects is slightly antisocial but in practice few people seem to run the
  self-test and we never get past the initial handshake phase so it shouldn't
  be a big deal.  Testing SSHv1 is a bit tricky since there are few of these
  servers still around, in the absence of a convenient test server we just
  try a local connect, which either times out or goes through an SSHv2
  handshake if there's a server there */

static const C_STR FAR_BSS ssh1Info[] = {
	NULL,
	TEXT( "localhost" ),
	NULL
	};
static const C_STR FAR_BSS ssh2Info[] = {
	NULL,
	TEXT( "localhost" ),
	TEXT( "sorrel.humboldt.edu:222" ),
	TEXT( "www.ssh.com" ),
	TEXT( "www.openssh.com" ),
	NULL
	};

#define SSH1_SERVER_NO	1
#define SSH2_SERVER_NO	4

/* Establish an SSH session.  The generic SSHv1 client test will always step
   up to SSHv2 if the server is v2 (which almost all are), so v1 can't
   easily be generically tested without hardcoding v1-only into
   session/ssh.c.  However, the loopback test, which forces the use of a
   v1-only server, does test the client as a v1 client */

static int connectSSH( const CRYPT_SESSION_TYPE sessionType,
					   const SSH_TEST_TYPE testType,
					   const BOOLEAN localSession )
	{
	CRYPT_SESSION cryptSession;
#ifdef SSH2_SERVER_NAME
	const C_STR serverName = SSH2_SERVER_NAME;
#else
	const C_STR serverName = localSession ? TEXT( "localhost" ) : \
							 ( testType == SSH_TEST_SSH1 ) ? \
								ssh1Info[ SSH1_SERVER_NO ] : \
								ssh2Info[ SSH2_SERVER_NO ];
#endif /* SSH2_SERVER_NAME */
	const BOOLEAN isServer = ( sessionType == CRYPT_SESSION_SSH_SERVER ) ? \
							   TRUE : FALSE;
	C_CHR stringBuffer[ CRYPT_MAX_TEXTSIZE + 1 ];
	char buffer[ BUFFER_SIZE ];
	int channel, length, bytesCopied, status;

	printf( "%sTesting %sSSH%s%s session...\n",
			isServer ? "SVR: " : "",
			localSession ? "local " : "",
			( testType == SSH_TEST_SSH1 ) ? "v1" : "v2",
			( testType == SSH_TEST_SUBSYSTEM ) ? " SFTP" : \
				( testType == SSH_TEST_PORTFORWARDING ) ? " port-forwarding" : \
				( testType == SSH_TEST_EXEC ) ? " remote exec" : \
				( testType == SSH_TEST_MULTICHANNEL ) ? " multi-channel" : \
				( testType == SSH_TEST_CLIENTCERT ) ? " pubkey-auth" : "" );
	if( !isServer && !localSession )
#ifdef UNICODE_STRINGS
		printf( "  Remote host: %S.\n", serverName );
#else
		printf( "  Remote host: %s.\n", serverName );
#endif /* UNICODE_STRINGS */

	/* Create the session */
	status = cryptCreateSession( &cryptSession, CRYPT_UNUSED, sessionType );
	if( status == CRYPT_ERROR_PARAM3 )	/* SSH session access not available */
		return( CRYPT_ERROR_NOTAVAIL );
	if( cryptStatusError( status ) )
		{
		printf( "cryptCreateSession() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}

	/* Set up the server and user information and activate the session */
	if( isServer )
		{
		CRYPT_CONTEXT privateKey;

		if( !setLocalConnect( cryptSession, 22 ) )
			return( FALSE );
		status = getPrivateKey( &privateKey, SSH_PRIVKEY_FILE,
								USER_PRIVKEY_LABEL, TEST_PRIVKEY_PASSWORD );
		if( cryptStatusOK( status ) )
			{
			status = cryptSetAttribute( cryptSession,
										CRYPT_SESSINFO_PRIVATEKEY, privateKey );
			cryptDestroyContext( privateKey );
			}
		}
	else
		{
		if( localSession )
			{
			if( !setLocalConnect( cryptSession, 22 ) )
				return( FALSE );
			}
		else
			{
			status = cryptSetAttributeString( cryptSession,
									CRYPT_SESSINFO_SERVER_NAME,
									serverName, paramStrlen( serverName ) );
			}
		if( cryptStatusOK( status ) )
			status = cryptSetAttributeString( cryptSession,
									CRYPT_SESSINFO_USERNAME,
									SSH_USER_NAME,
									paramStrlen( SSH_USER_NAME ) );
		if( cryptStatusOK( status ) )
			{
			if( testType == SSH_TEST_CLIENTCERT )
				{
				CRYPT_CONTEXT privateKey;

				status = getPrivateKey( &privateKey, USER_PRIVKEY_FILE,
									USER_PRIVKEY_LABEL, TEST_PRIVKEY_PASSWORD );
				if( cryptStatusOK( status ) )
					{
					status = cryptSetAttribute( cryptSession,
									CRYPT_SESSINFO_PRIVATEKEY, privateKey );
					cryptDestroyContext( privateKey );
					}
				}
			else
				{
#ifdef USER_SUPPLIED_PASSWORD
				char password[ 256 ];

				printf( "Enter SSHv2 server password: " );
				fgets( password, 255, stdin );
				password[ strlen( password ) - 1 ] = '\0';
				status = cryptSetAttributeString( cryptSession,
									CRYPT_SESSINFO_PASSWORD,
									password, strlen( password ) );
#else
				status = cryptSetAttributeString( cryptSession,
									CRYPT_SESSINFO_PASSWORD,
									SSH_PASSWORD,
									paramStrlen( SSH_PASSWORD ) );
#endif /* User-supplied password */
				}
			}
		if( cryptStatusOK( status ) && \
			( testType == SSH_TEST_SUBSYSTEM ) )
			status = createChannel( cryptSession, TEXT( "subsystem" ),
									TEXT( "sftp" ) );
		if( cryptStatusOK( status ) && \
			( testType == SSH_TEST_PORTFORWARDING || \
			  testType == SSH_TEST_MULTICHANNEL ) )
			createChannel( cryptSession, TEXT( "direct-tcpip" ),
							TEXT( "localhost:1234" ) );
		if( cryptStatusOK( status ) && \
			( testType == SSH_TEST_FINGERPRINT ) )
			{
			BYTE fingerPrint[ CRYPT_MAX_HASHSIZE ];

			/* Set a dummy (all-zero) fingerprint to force the connect to
			   fail */
			memset( fingerPrint, 0, CRYPT_MAX_HASHSIZE );
			status = cryptSetAttributeString( cryptSession,
											  CRYPT_SESSINFO_SERVER_FINGERPRINT,
											  fingerPrint, 16 );
			}
		if( cryptStatusOK( status ) && \
			( testType == SSH_TEST_EXEC ) )
			status = createChannel( cryptSession, TEXT( "exec" ),
									TEXT( "/bin/netstat" ) );
		}
	if( cryptStatusOK( status ) )
		status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_VERSION,
									( testType == SSH_TEST_SSH1 ) ? 1 : 2 );
	if( cryptStatusOK( status ) && isServer && \
		testType != SSH_TEST_CONFIRMAUTH )
		/* If we're not testing manual confirmation of client auth, have
		   cryptlib automatically confirm the auth */
		status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_AUTHRESPONSE,
									TRUE );
	if( cryptStatusError( status ) )
		{
		/* If we're trying to enable SSHv1 and it fails, this isn't an error
		   since this protocol is disabled by default */
		if( testType == SSH_TEST_SSH1 )
			{
			puts( "SSHv1 appears to be disbled in this build." );
			cryptDestroySession( cryptSession );
			puts( isServer ? "SVR: SSH server session succeeded.\n" : \
							 "SSH client session succeeded.\n" );
			return( CRYPT_ERROR_NOTAVAIL );
			}

		printf( "%scryptSetAttribute/AttributeString() failed with error "
				"code %d, line %d.\n", isServer ? "SVR: " : "", status, 
				__LINE__ );
		return( FALSE );
		}

	/* Activate the session.  Since we need to be able to process out-of-
	   band signalling such as channel control messages, we set a non-zero
	   timeout for reads */
	cryptSetAttribute( cryptSession, CRYPT_OPTION_NET_READTIMEOUT, 5 );
	status = cryptSetAttribute( cryptSession, CRYPT_SESSINFO_ACTIVE, TRUE );
	if( isServer )
		{
		if( status == CRYPT_ENVELOPE_RESOURCE )
			{
			/* The client has tried to authenticate themselves, print the
			   info */
			status = cryptGetAttributeString( cryptSession,
											  CRYPT_SESSINFO_USERNAME,
											  stringBuffer, &length );
			if( cryptStatusOK( status ) )
				{
#ifdef UNICODE_STRINGS
				stringBuffer[ length / sizeof( wchar_t ) ] = TEXT( '\0' );
				printf( "SVR: User name = '%S', ", stringBuffer );
#else
				stringBuffer[ length ] = '\0';
				printf( "SVR: User name = '%s', ", stringBuffer );
#endif /* UNICODE_STRINGS */
				}
			if( cryptStatusOK( status ) )
				status = cryptGetAttributeString( cryptSession,
												  CRYPT_SESSINFO_PASSWORD,
												  stringBuffer, &length );
			if( cryptStatusOK( status ) )
				{
#ifdef UNICODE_STRINGS
				stringBuffer[ length / sizeof( wchar_t ) ] = TEXT( '\0' );
				printf( "password = '%S'.\n", stringBuffer );
#else
				stringBuffer[ length ] = '\0';
				printf( "password = '%s'.\n", stringBuffer );
#endif /* UNICODE_STRINGS */
				}
			if( cryptStatusError( status ) )
				{
				printf( "SVR: Couldn't read client authentication details, "
						"status = %d, line %d.\n", status, __LINE__ );
				return( FALSE );
				}

			/* Allow the auth.and complete the handshake */
			puts( "SVR: Confirming authentication to client..." );
			status = cryptSetAttribute( cryptSession,
										CRYPT_SESSINFO_AUTHRESPONSE, TRUE );
			if( cryptStatusOK( status ) )
				status = cryptSetAttribute( cryptSession,
											CRYPT_SESSINFO_ACTIVE, TRUE );
			}

		/* Now that the handshake is complete, display the connection info */
		if( cryptStatusOK( status ) && !printConnectInfo( cryptSession ) )
			return( FALSE );
		}
	if( cryptStatusError( status ) )
		{
		if( testType == SSH_TEST_FINGERPRINT )
			{
			/* We've forced the connect to fail by using a dummy fingerprint,
			   everything is OK */
			if( isServer )
				printf( "SVR: " );
			puts( "SSH client rejected key with invalid fingerprint." );
			cryptDestroySession( cryptSession );
			puts( isServer ? "SVR: SSH server session succeeded.\n" : \
							 "SSH client session succeeded.\n" );
			return( TRUE );
			}
		printExtError( cryptSession, isServer ? \
					   "SVR: Attempt to activate SSH server session" : \
					   "Attempt to activate SSH client session", status,
					   __LINE__ );
		cryptDestroySession( cryptSession );
		if( status == CRYPT_ERROR_OPEN || status == CRYPT_ERROR_NOTFOUND )
			{
			/* These servers are constantly appearing and disappearing so if
			   we get a straight connect error we don't treat it as a serious
			   failure */
			puts( "  (Server could be down, faking it and continuing...)\n" );
			return( CRYPT_ERROR_FAILED );
			}
		if( status == CRYPT_ERROR_WRONGKEY )
			{
			/* This is another possible soft error condition, the default
			   username and password shouldn't be able to get into many
			   machines */
			puts( "  (Incorrect username/password, continuing...)\n" );
			return( TRUE );
			}
		if( status == CRYPT_ERROR_NOSECURE )
			{
			/* Another soft error condition, the server can't handle the
			   security level we want (usually occurs when trying to perform
			   an SSHv2 connect to an SSHv1 server) */
			puts( "  (Insufficiently secure protocol parameters, "
				  "continuing...)\n" );
			return( TRUE );
			}
		return( FALSE );
		}
	if( testType == SSH_TEST_FINGERPRINT )
		{
		printf( "Attempt to connect with invalid key fingerprint succeeded "
				"when it should\nhave failed, line %d.\n", __LINE__ );
		return( FALSE );
		}

	/* Report the session security info */
	if( !printSecurityInfo( cryptSession, isServer, TRUE ) )
		return( FALSE );
	status = cryptGetAttribute( cryptSession, CRYPT_SESSINFO_SSH_CHANNEL,
								&channel );
	if( cryptStatusError( status ) )
		{
		printf( "cryptGetAttributeString() failed with error code "
				"%d, line %d.\n", status, __LINE__ );
		return( FALSE );
		}
	printf( "%sCurrent channel is #%d.\n", isServer ? "SVR: " : "",
			channel );

	/* Report additional channel-specific information */
	if( isServer )
		{
		/* Display info on any channels that the client has opened */
		status = printChannelInfo( cryptSession, testType, TRUE );
		if( cryptStatusError( status ) )
			return( FALSE );

		/* Process any additional information that the client may throw
		   at us after the user-auth has completed */
		status = cryptPopData( cryptSession, buffer, BUFFER_SIZE,
							   &bytesCopied );
		if( cryptStatusOK( status ) && bytesCopied > 0 )
			printf( "SVR: Client sent additional %d bytes post-"
					"handshake data.\n", bytesCopied );
		else
			if( status == CRYPT_ENVELOPE_RESOURCE )
				{
				/* The client performed additional control actions that were
				   handled inline as part of the data-pop, report the
				   details */
				status = printChannelInfo( cryptSession, testType, TRUE );
				if( cryptStatusError( status ) )
					return( FALSE );
				}
		}

	/* If we're using the SFTP subsystem as a server, use the special-case
	   routines for this */
#ifdef WINDOWS_THREADS
	if( testType == SSH_TEST_SUBSYSTEM )
		{
		if( isServer )
			{
			int sftpServer( const CRYPT_SESSION cryptSession );

			status = sftpServer( cryptSession );
			if( cryptStatusError( status ) )
				{
				printf( "SVR: Couldn't receive SFTP data from client, status %d, "
						"line %d.\n", status, __LINE__ );
				return( FALSE );
				}
			cryptDestroySession( cryptSession );
			puts( "SVR: SFTP server session succeeded.\n" );
			return( TRUE );
			}
		else
			{
			int sftpClient( const CRYPT_SESSION cryptSession );

			status = sftpClient( cryptSession );
			if( cryptStatusError( status ) )
				{
				printf( "Couldn't send SFTP data to server, status %d, line "
						"%d.\n", status, __LINE__ );
				return( FALSE );
					}
			cryptDestroySession( cryptSession );
			puts( "SFTP client session succeeded.\n" );
			return( TRUE );
			}
		}
#endif /* WINDOWS_THREADS */

	/* If we're performing a multi-channel test, open a second channel (the
	   server handles this as part of its general connect-handling) */
	if( testType == SSH_TEST_MULTICHANNEL && !isServer )
		{
		status = createChannel( cryptSession, TEXT( "direct-tcpip" ),
								TEXT( "localhost:5678" ) );
		if( cryptStatusOK( status ) )
			status = cryptGetAttribute( cryptSession,
										CRYPT_SESSINFO_SSH_CHANNEL,
										&channel );
		if( cryptStatusOK( status ) )
			status = cryptSetAttribute( cryptSession,
										CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE,
										TRUE );
		if( cryptStatusError( status ) )
			{
			printf( "Couldn't open second SSH chanel, status %d, line "
					"%d.\n", status, __LINE__ );
			return( FALSE );
			}
		printf( "Opened additional channel #%d to server.\n", channel );
		}

	/* Send data over the SSH link */
	if( isServer )
		{
		/* Send a status message to the client */
		status = cryptPushData( cryptSession, "Welcome to cryptlib, now go "
								"away.\r\n", 35, &bytesCopied );
		if( cryptStatusOK( status ) )
			status = cryptFlushData( cryptSession );
		if( cryptStatusError( status ) || bytesCopied != 35 )
			{
			printf( "SVR: Couldn't send data to client, status %d, line "
					"%d.\n", status, __LINE__ );
			return( FALSE );
			}
		}

	/* Wait a bit while data arrives */
	delayThread( 2 );

	/* Print the first lot of output from the other side */
	status = printDataInfo( cryptSession, buffer, &bytesCopied, isServer );
	if( cryptStatusError( status ) )
		return( FALSE );

	/* If we're the server, echo the command to the client */
	if( isServer )
		{
		const int clientBytesCopied = bytesCopied;
		int dummy, i;

		/* If it's a multi-channel test, send the response back on a
		   different channel.  The currently-selected channel will be the
		   last one that the client opened (#2), so we can hardcode in
		   #1 for testing purposes */
		if( testType == SSH_TEST_MULTICHANNEL )
			{
			status = cryptSetAttribute( cryptSession,
										CRYPT_SESSINFO_SSH_CHANNEL, 1 );
			if( cryptStatusError( status ) )
				{
				printf( "SVR: Couldn't select channel #1 to return data to "
						"client, status %d, line %d.\n", status, __LINE__ );
				return( FALSE );
				}
			}
		for( i = 0; i < clientBytesCopied; i++ )
			if( buffer[ i ] < ' ' || buffer[ i ] >= 0x7F )
				buffer[ i ] = '.';
		status = cryptPushData( cryptSession, "Input was [", 11, &dummy );
		if( cryptStatusOK( status ) && clientBytesCopied > 0 )
			status = cryptPushData( cryptSession, buffer, clientBytesCopied,
									&bytesCopied );
		if( cryptStatusOK( status ) )
			status = cryptPushData( cryptSession, "]\r\n", 3, &dummy );
		if( cryptStatusOK( status ) )
			status = cryptFlushData( cryptSession );
		if( cryptStatusError( status ) || bytesCopied != clientBytesCopied )
			{
			printf( "SVR: Couldn't send data to client, status %d, line "
					"%d.\n", status, __LINE__ );
			return( FALSE );
			}
		}
	else
		{
		/* We're the client, if it's a session to a Unix ssh server, send a
		   sample command and display the output */
		if( !localSession )
			{
			/* Send a command to the server and get the results */
			status = cryptPushData( cryptSession, "ls -l | head -25\n", 18,
									&bytesCopied );
			if( cryptStatusOK( status ) )
				status = cryptFlushData( cryptSession );
			if( cryptStatusError( status ) || bytesCopied != 18 )
				{
				printf( "Couldn't send data to server, status %d, line "
						"%d.\n", status, __LINE__ );
				return( FALSE );
				}
			puts( "Sent 'ls -l | head -25'" );
			delayThread( 3 );
			status = printDataInfo( cryptSession, buffer, &bytesCopied,
									isServer );
			if( cryptStatusError( status ) )
				return( FALSE );
			}
		else
			{
			/* It's a local session, just send a simple text string for
			   testing */
			status = cryptPushData( cryptSession, "Some test data", 14,
									&bytesCopied );
			if( cryptStatusOK( status ) )
				status = cryptFlushData( cryptSession );
			if( cryptStatusError( status ) || bytesCopied != 14 )
				{
				printf( "Couldn't send data to server, status %d, line "
						"%d.\n", status, __LINE__ );
				return( FALSE );
				}

			/* Make sure that we stay around long enough to get the
			   server's response */
			delayThread( 1 );

			/* Print the server's response */
			status = printDataInfo( cryptSession, buffer, &bytesCopied,
									isServer );
			if( cryptStatusError( status ) )
				return( FALSE );
			}
		}

	/* If we're performing a multi-channel test, close the second channel */
	if( testType == SSH_TEST_MULTICHANNEL )
		{
		if( isServer )
			{
			/* Perform a dummy pop to process the channel close */
			cryptPopData( cryptSession, buffer, BUFFER_SIZE, &bytesCopied );
			}
		else
			{
			/* Close the current channel */
			status = cryptSetAttribute( cryptSession,
										CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE,
										FALSE );
			if( cryptStatusError( status ) )
				{
				printf( "Couldn't close second SSH chanel, status %d, line "
						"%d.\n", status, __LINE__ );
				return( FALSE );
				}
			printf( "Closed second channel to server.\n" );
			}
		}

	/* Clean up */
	status = cryptDestroySession( cryptSession );
	if( cryptStatusError( status ) )
		{
		printf( "cryptDestroySession() failed with error code %d, line %d.\n",
				status, __LINE__ );
		return( FALSE );
		}

	puts( isServer ? "SVR: SSH server session succeeded.\n" : \
					 "SSH client session succeeded.\n" );
	return( TRUE );
	}

int testSessionSSHv1( void )
	{
	return( connectSSH( CRYPT_SESSION_SSH, SSH_TEST_SSH1, FALSE ) );
	}
int testSessionSSH( void )
	{
	return( connectSSH( CRYPT_SESSION_SSH, SSH_TEST_NORMAL, FALSE ) );
	}
int testSessionSSHClientCert( void )
	{
	return( connectSSH( CRYPT_SESSION_SSH, SSH_TEST_CLIENTCERT, FALSE ) );
	}
int testSessionSSHPortforward( void )
	{
	return( connectSSH( CRYPT_SESSION_SSH, SSH_TEST_PORTFORWARDING, FALSE ) );
	}
int testSessionSSHExec( void )
	{
	return( connectSSH( CRYPT_SESSION_SSH, SSH_TEST_EXEC, FALSE ) );
	}
int testSessionSSH_SFTP( void )
	{
	return( connectSSH( CRYPT_SESSION_SSH, SSH_TEST_SUBSYSTEM, FALSE ) );
	}
int testSessionSSHv1Server( void )
	{
	return( connectSSH( CRYPT_SESSION_SSH_SERVER, SSH_TEST_SSH1, FALSE ) );
	}
int testSessionSSHServer( void )
	{
	return( connectSSH( CRYPT_SESSION_SSH_SERVER, SSH_TEST_CONFIRMAUTH, FALSE ) );
	}
int testSessionSSH_SFTPServer( void )
	{
	return( connectSSH( CRYPT_SESSION_SSH_SERVER, SSH_TEST_SUBSYSTEM, FALSE ) );
	}

/* Perform a client/server loopback test */

#ifdef WINDOWS_THREADS

unsigned __stdcall ssh1ServerThread( void *dummy )
	{
	connectSSH( CRYPT_SESSION_SSH_SERVER, SSH_TEST_SSH1, TRUE );
	_endthreadex( 0 );
	return( 0 );
	}
unsigned __stdcall ssh2ServerThread( void *dummy )
	{
	connectSSH( CRYPT_SESSION_SSH_SERVER, SSH_TEST_NORMAL, TRUE );
	_endthreadex( 0 );
	return( 0 );
	}
unsigned __stdcall ssh2ServerFingerprintThread( void *dummy )
	{
	connectSSH( CRYPT_SESSION_SSH_SERVER, SSH_TEST_FINGERPRINT, TRUE );
	_endthreadex( 0 );
	return( 0 );
	}
unsigned __stdcall ssh2ServerMultichannelThread( void *dummy )
	{
	connectSSH( CRYPT_SESSION_SSH_SERVER, SSH_TEST_MULTICHANNEL, TRUE );
	_endthreadex( 0 );
	return( 0 );
	}
unsigned __stdcall sftpServerThread( void *dummy )
	{
	connectSSH( CRYPT_SESSION_SSH_SERVER, SSH_TEST_SUBSYSTEM, TRUE );
	_endthreadex( 0 );
	return( 0 );
	}

static int sshClientServer( const SSH_TEST_TYPE testType )
	{
	HANDLE hThread;
	unsigned threadID;
	int status;

	/* Start the server and wait for it to initialise */
	hThread = ( HANDLE ) _beginthreadex( NULL, 0,
							( testType == SSH_TEST_SUBSYSTEM ) ? \
								sftpServerThread : \
							( testType == SSH_TEST_SSH1 ) ? \
								ssh1ServerThread : \
							( testType == SSH_TEST_FINGERPRINT ) ? \
								ssh2ServerFingerprintThread : \
							( testType == SSH_TEST_MULTICHANNEL ) ? \
								ssh2ServerMultichannelThread : \
								ssh2ServerThread,
							NULL, 0, &threadID );
	Sleep( 1000 );

	/* Connect to the local server */
	status = connectSSH( CRYPT_SESSION_SSH, testType, TRUE );
	if( WaitForSingleObject( hThread, 15000 ) == WAIT_TIMEOUT )
		{
		puts( "Warning: Server thread is still active due to session "
			  "negotiation failure,\n         this will cause an error "
			  "condition when cryptEnd() is called due\n         to "
			  "resources remaining allocated.  Press a key to continue." );
		getchar();
		}
	CloseHandle( hThread );

	return( status );
	}

int testSessionSSHv1ClientServer( void )
	{
	return( sshClientServer( SSH_TEST_SSH1 ) );
	}
int testSessionSSHClientServer( void )
	{
	return( sshClientServer( SSH_TEST_NORMAL ) );
	}
int testSessionSSHClientServerFingerprint( void )
	{
	/* Note that this test tests the correct functioning of a refused
	   connection when an incorrect key fingerprint is used, so it's
	   supposed to fail */
	return( sshClientServer( SSH_TEST_FINGERPRINT ) );
	}
int testSessionSSHClientServerSFTP( void )
	{
	return( sshClientServer( SSH_TEST_SUBSYSTEM ) );
	}
int testSessionSSHClientServerPortForward( void )
	{
	return( sshClientServer( SSH_TEST_PORTFORWARDING ) );
	}
int testSessionSSHClientServerExec( void )
	{
	return( sshClientServer( SSH_TEST_EXEC ) );
	}
int testSessionSSHClientServerMultichannel( void )
	{
	return( sshClientServer( SSH_TEST_MULTICHANNEL ) );
	}
#endif /* WINDOWS_THREADS */

/****************************************************************************
*																			*
*							SFTP Routines for SSH							*
*																			*
****************************************************************************/

/* The following code re-uses internal parts of cryptlib, so it provides its
   own dummy functions as stubs for cryptlib-internal ones.  Since this would
   produce link errors when cryptlib is statically linked with the test
   app, we only enable it for the threaded Windows (i.e. DLL) self-test */

#ifdef WINDOWS_THREADS

/* The following code is a bare-bones SFTP implementation created purely for
   interop/performance testing of cryptlib's SSH implementation.  It does
   the bare minimum needed to set up an SFTP transfer, and shouldn't be used
   for anything other than testing.

   Rather than creating our own versions of code already present in cryptlib,
   we pull in the cryptlib code wholesale here unless we've built cryptlib as
   a static lib, in which case it'll already be present.  This is a pretty
   ugly hack, but saves having to copy over a pile of cryptlib code.

   Because cryptlib has an internal BYTE type, we need to no-op it out before
   we pull in any cryptlib code */

#undef BYTE
#define BYTE	_BYTE_DUMMY
#ifdef BOOLEAN
  #undef BOOLEAN	/* May be a typedef or a #define */
#endif /* BOOLEAN */
#ifndef STATIC_LIB
  #if defined( SYMANTEC_C ) || defined( __BEOS__ )
	#define INC_ALL
	#include "misc_rw.c"
  #else
	#include "misc/misc_rw.c"
  #endif /* Compiler-specific includes */
#endif /* Non-static lib cryptlib */
#undef BYTE
#define BYTE	unsigned char

/* Replacements for cryptlib stream routines */

#define sMemDisconnect( stream )
#define sMemConnect		sMemOpen

int sMemOpen( STREAM *stream, void *buffer, const int bufSize )
	{
	memset( stream, 0, sizeof( STREAM ) );
	stream->buffer = ( void * ) buffer;
	stream->bufEnd = bufSize;
	return( CRYPT_OK );
	}

int sread( STREAM *stream, void *buffer, const int count )
	{
	if( stream->bufPos + count > stream->bufEnd )
		{
		sSetError( stream, CRYPT_ERROR_UNDERFLOW );
		return( CRYPT_ERROR_UNDERFLOW );
		}
	memcpy( buffer, stream->buffer + stream->bufPos, count );
	stream->bufPos += count;
	return( CRYPT_OK );
	}

int swrite( STREAM *stream, const void *buffer, const int count )
	{
	if( stream->buffer != NULL )
		{
		if( stream->bufPos + count > stream->bufEnd )
			{
			sSetError( stream, CRYPT_ERROR_OVERFLOW );
			return( CRYPT_ERROR_OVERFLOW );
			}
		memcpy( stream->buffer + stream->bufPos, buffer, count );
		}
	stream->bufPos += count;
	return( CRYPT_OK );
	}

int sgetc( STREAM *stream )
	{
	int ch;

	if( stream->bufPos + 1 > stream->bufEnd )
		{
		sSetError( stream, CRYPT_ERROR_UNDERFLOW );
		return( CRYPT_ERROR_UNDERFLOW );
		}
	ch = stream->buffer[ stream->bufPos ];
	stream->bufPos++;
	return( ch );
	}

int sputc( STREAM *stream, const int data )
	{
	if( stream->buffer != NULL )
		{
		if( stream->bufPos + 1 > stream->bufEnd )
			{
			sSetError( stream, CRYPT_ERROR_OVERFLOW );
			return( CRYPT_ERROR_OVERFLOW );
			}
		stream->buffer[ stream->bufPos++ ] = data;
		}
	else
		stream->bufPos++;
	return( CRYPT_OK );
	}

int sseek( STREAM *stream, const long position )
	{
	return( 0 );
	}

int sPeek( STREAM *stream )
	{
	return( 0 );
	}

int sSkip( STREAM *stream, const long offset )
	{
	return( 0 );
	}

/* Dummy routines needed in misc_rw.c */

int BN_num_bits( const BIGNUM *a ) { return 0; }
int BN_high_bit( BIGNUM *a ) { return 0; }
BIGNUM *BN_bin2bn( const unsigned char *s, int len, BIGNUM *ret ) { return NULL; }
int	BN_bn2bin( const BIGNUM *a, unsigned char *to ) { return 0; }

/* SFTP command types */

#define SSH_FXP_INIT			1
#define SSH_FXP_VERSION			2
#define SSH_FXP_OPEN			3
#define SSH_FXP_CLOSE			4
#define SSH_FXP_READ			5
#define SSH_FXP_WRITE			6
#define SSH_FXP_LSTAT			7
#define SSH_FXP_FSTAT			8
#define SSH_FXP_SETSTAT			9
#define SSH_FXP_FSETSTAT		10
#define SSH_FXP_OPENDIR			11
#define SSH_FXP_READDIR			12
#define SSH_FXP_REMOVE			13
#define SSH_FXP_MKDIR			14
#define SSH_FXP_RMDIR			15
#define SSH_FXP_REALPATH		16
#define SSH_FXP_STAT			17
#define SSH_FXP_RENAME			18
#define SSH_FXP_READLINK		19
#define SSH_FXP_SYMLINK			20
#define SSH_FXP_STATUS			101
#define SSH_FXP_HANDLE			102
#define SSH_FXP_DATA			103
#define SSH_FXP_NAME			104
#define SSH_FXP_ATTRS			105

/* SFTP attribute presence flags.  When these flags are set, the
   corresponding file attribute value is present */

#define SSH_FILEXFER_ATTR_SIZE			0x01
#define SSH_FILEXFER_ATTR_UIDGID		0x02
#define SSH_FILEXFER_ATTR_PERMISSIONSv3	0x04
#define SSH_FILEXFER_ATTR_ACMODTIME		0x08
#define SSH_FILEXFER_ATTR_ACCESSTIME	0x08
#define SSH_FILEXFER_ATTR_CREATETIME	0x10
#define SSH_FILEXFER_ATTR_MODIFYTIME	0x20
#define SSH_FILEXFER_ATTR_PERMISSIONSv4	0x40
#define SSH_FILEXFER_ATTR_ACL			0x40
#define SSH_FILEXFER_ATTR_OWNERGROUP	0x80
#define SSH_FILEXFER_ATTR_SUBSECOND_TIMES 0x100
#define SSH_FILEXFER_ATTR_EXTENDED		0x80000000

/* SFTP file open/create flags */

#define SSH_FXF_READ			0x01
#define SSH_FXF_WRITE			0x02
#define SSH_FXF_APPEND			0x04
#define SSH_FXF_CREAT			0x08
#define SSH_FXF_TRUNC			0x10
#define SSH_FXF_EXCL			0x20
#define SSH_FXF_TEXT			0x40

/* SFTP file types */

#define SSH_FILETYPE_REGULAR	1
#define SSH_FILETYPE_DIRECTORY	2
#define SSH_FILETYPE_SYMLINK	3
#define SSH_FILETYPE_SPECIAL	4
#define SSH_FILETYPE_UNKNOWN	5

/* SFTP status codes */

#define SSH_FX_OK				0
#define SSH_FX_EOF				1
#define SSH_FX_NO_SUCH_FILE		2
#define SSH_FX_PERMISSION_DENIED 3
#define SSH_FX_FAILURE			4
#define SSH_FX_BAD_MESSAGE		5
#define SSH_FX_NO_CONNECTION	6
#define SSH_FX_CONNECTION_LOST	7
#define SSH_FX_OP_UNSUPPORTED	8
#define SSH_FX_INVALID_HANDLE	9
#define SSH_FX_NO_SUCH_PATH		10
#define SSH_FX_FILE_ALREADY_EXISTS 11
#define SSH_FX_WRITE_PROTECT	12
#define SSH_FX_NO_MEDIA			13

/* A structure to contain SFTP file attributes */

typedef struct {
	BOOLEAN isDirectory;		/* Whether directory or normal file */
	long size;					/* File size */
	int permissions;			/* File permissions */
	time_t ctime, atime, mtime;	/* File create, access, mod times */
	} SFTP_ATTRS;

/* A structure to contain SFTP session information */

#define MAX_HANDLE_SIZE		16

typedef struct {
	int version;				/* SFTP protocol version */
	long id;					/* Session ID */
	BYTE handle[ MAX_HANDLE_SIZE ];	/* File handle */
	int handleSize;
	} SFTP_INFO;

/* Read/write SFTP attributes.  This changed completely from v3 to v4, so we
   have to treat them as special-cases:

	uint32		flags
	byte		file_type
	uint64		size (present if ATTR_SIZE)
	string		owner (present if ATTR_OWNERGROUP)
	string		group (present if ATTR_OWNERGROUP)
	uint32		permissions (present if ATTR_PERMISSIONS)
	uint64		atime (present if ATTR_ACCESSTIME)
	uint32		atime_nseconds (present if ATTR_SUBSECOND_TIMES)
	uint64		createtime (present if ATTR_CREATETIME)
	uint32		createtime_nseconds (present if ATTR_SUBSECOND_TIMES)
	uint64		mtime (present if ATTR_MODIFYTIME)
	uint32		mtime_nseconds (present if ATTR_SUBSECOND_TIMES)
	string		acl (present if ATTR_ACL)
	uint32		extended_count (present if ATTR_EXTENDED)
		string	extended_type
		string	extended_value
   		[ extended_count type/value pairs ] */

static int sizeofAttributes( SFTP_ATTRS *attributes, const int version )
	{
	int size = UINT32_SIZE;	/* Flags */

	if( version < 4 )
		{
		if( attributes->size != CRYPT_UNUSED )
			size += UINT64_SIZE;
		if( attributes->permissions != CRYPT_UNUSED )
			size += UINT32_SIZE;
		if( attributes->atime )
			size += UINT32_SIZE;
		if( attributes->mtime )
			size += UINT32_SIZE;
		}
	else
		{
		size++;
		if( attributes->size != CRYPT_UNUSED )
			size += UINT64_SIZE;
		if( attributes->permissions != CRYPT_UNUSED )
			size += UINT32_SIZE;
		if( attributes->ctime )
			size += UINT64_SIZE;
		if( attributes->atime )
			size += UINT64_SIZE;
		if( attributes->mtime )
			size += UINT64_SIZE;
		}

	return( size );
	}

static int readAttributes( STREAM *stream, SFTP_ATTRS *attributes, const int version )
	{
	long flags;

	memset( attributes, 0, sizeof( SFTP_ATTRS ) );
	attributes->permissions = CRYPT_UNUSED;
	attributes->size = CRYPT_UNUSED;

	/* Read basic attribute information: File size, and owner, and
	   permissions */
	flags = readUint32( stream );
	if( cryptStatusError( flags ) )
		return( flags );
	if( version < 4 )
		{
		if( flags & SSH_FILEXFER_ATTR_SIZE )
			attributes->size = readUint64( stream );
		if( flags & SSH_FILEXFER_ATTR_UIDGID )
			{
			readUint32( stream );
			readUint32( stream );
			}
		if( flags & SSH_FILEXFER_ATTR_PERMISSIONSv3 )
			attributes->permissions = readUint32( stream );

		/* Read file access and modify times */
		if( flags & SSH_FILEXFER_ATTR_ACMODTIME )
			{
			readUint32Time( stream, &attributes->atime );
			readUint32Time( stream, &attributes->mtime );
			}
		}
	else
		{
		if( flags & SSH_FILEXFER_ATTR_SIZE )
			attributes->size = readUint64( stream );
		if( flags & SSH_FILEXFER_ATTR_OWNERGROUP )
			{
			readString32( stream, NULL, NULL, 0 );
			readString32( stream, NULL, NULL, 0 );
			}
		if( flags & SSH_FILEXFER_ATTR_PERMISSIONSv4 )
			attributes->permissions = readUint32( stream );

		/* Read file create, access, and modify times */
		if( flags & SSH_FILEXFER_ATTR_ACCESSTIME )
			{
			readUint64Time( stream, &attributes->atime );
			if( flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES )
				readUint32( stream );
			}
		if( flags & SSH_FILEXFER_ATTR_CREATETIME )
			{
			readUint64Time( stream, &attributes->ctime );
			if( flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES )
				readUint32( stream );
			}
		if( flags & SSH_FILEXFER_ATTR_MODIFYTIME )
			{
			readUint64Time( stream, &attributes->mtime );
			if( flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES )
				readUint32( stream );
			}
		}

	/* Read ACLs and extended attribute type/value pairs, the one thing that
	   stayed the same from v3 to v4 */
	if( flags & SSH_FILEXFER_ATTR_ACL )
		readString32( stream, NULL, NULL, 0 );
	if( flags & SSH_FILEXFER_ATTR_EXTENDED )
		{
		int extAttrCount = readUint32( stream );

		if( cryptStatusError( extAttrCount ) )
			return( extAttrCount );
		while( extAttrCount > 0 )
			{
			readString32( stream, NULL, NULL, 0 );
			readString32( stream, NULL, NULL, 0 );
			extAttrCount--;
			}
		}

	return( sGetStatus( stream ) );
	}

static int writeAttributes( STREAM *stream, SFTP_ATTRS *attributes, const int version )
	{
	int flags = 0;

	if( version < 4 )
		{
		/* Indicate which attribute values we're going to write */
		if( attributes->size != CRYPT_UNUSED )
			flags |= SSH_FILEXFER_ATTR_SIZE;
		if( attributes->permissions != CRYPT_UNUSED )
			flags |= SSH_FILEXFER_ATTR_PERMISSIONSv3;
		if( attributes->atime )
			flags |= SSH_FILEXFER_ATTR_ACMODTIME;
		writeUint32( stream, flags );

		/* Write the optional attributes */
		if( attributes->size != CRYPT_UNUSED )
			writeUint64( stream, attributes->size );
		if( attributes->permissions != CRYPT_UNUSED )
			writeUint32( stream, attributes->permissions );
		if( attributes->atime )
			{
			writeUint32Time( stream, attributes->atime );
			writeUint32Time( stream, attributes->mtime );
			}
		}
	else
		{
		/* Indicate which attribute values we're going to write */
		if( attributes->size != CRYPT_UNUSED )
			flags |= SSH_FILEXFER_ATTR_SIZE;
		if( attributes->permissions != CRYPT_UNUSED )
			flags |= SSH_FILEXFER_ATTR_PERMISSIONSv4;
		if( attributes->ctime )
			flags |= SSH_FILEXFER_ATTR_CREATETIME;
		if( attributes->atime )
			flags |= SSH_FILEXFER_ATTR_ACCESSTIME;
		if( attributes->mtime )
			flags |= SSH_FILEXFER_ATTR_MODIFYTIME;
		writeUint32( stream, flags );
		sputc( stream, attributes->isDirectory ? \
					   SSH_FILETYPE_DIRECTORY : SSH_FILETYPE_REGULAR );

		/* Write the optional attributes */
		if( attributes->size != CRYPT_UNUSED )
			writeUint64( stream, attributes->size );
		if( attributes->permissions != CRYPT_UNUSED )
			writeUint32( stream, attributes->permissions );
		if( attributes->ctime )
			writeUint64Time( stream, attributes->ctime );
		if( attributes->atime )
			writeUint64Time( stream, attributes->atime );
		if( attributes->mtime )
			writeUint64Time( stream, attributes->mtime );
		}

	return( sGetStatus( stream ) );
	}

/* Read/write SFTP status:

	uint32		id
	uint32		error/status code
	string		error message (ISO-10646 UTF-8 [RFC-2279])
	string		language tag (as defined in [RFC-1766]) */

static int sizeofStatus( const char *sshStatusString )
	{
	return( UINT32_SIZE + UINT32_SIZE + \
			( UINT32_SIZE + strlen( sshStatusString ) ) + \
			UINT32_SIZE );
	}

static int readStatus( STREAM *stream, SFTP_INFO *info )
	{
	static const struct {
		const int sftpStatus, cryptlibStatus;
		} sftpStatusMap[] = {
		{ SSH_FX_OK, CRYPT_OK },
		{ SSH_FX_EOF, CRYPT_ERROR_COMPLETE },
		{ SSH_FX_NO_SUCH_FILE, CRYPT_ERROR_NOTFOUND },
		{ SSH_FX_PERMISSION_DENIED, CRYPT_ERROR_PERMISSION },
		{ SSH_FX_FAILURE, CRYPT_ERROR_FAILED },
		{ SSH_FX_BAD_MESSAGE, CRYPT_ERROR_BADDATA },
		{ SSH_FX_NO_CONNECTION, CRYPT_ERROR_FAILED },
		{ SSH_FX_CONNECTION_LOST, CRYPT_ERROR_FAILED },
		{ SSH_FX_OP_UNSUPPORTED, CRYPT_ERROR_NOTAVAIL },
		{ SSH_FX_INVALID_HANDLE, CRYPT_ERROR_BADDATA },
		{ SSH_FX_NO_SUCH_PATH, CRYPT_ERROR_NOTFOUND },
		{ SSH_FX_FILE_ALREADY_EXISTS, CRYPT_ERROR_DUPLICATE },
		{ SSH_FX_WRITE_PROTECT, CRYPT_ERROR_PERMISSION },
		{ SSH_FX_NO_MEDIA, CRYPT_ERROR_FAILED },
		{ CRYPT_ERROR, CRYPT_ERROR_FAILED }
		};
	int value, i, status;

	/* Read the status info and make sure that it's valid */
	value = readUint32( stream );
	status = readUint32( stream );
	if( cryptStatusError( status ) )
		return( status );
	if( value != info->id )
		return( CRYPT_ERROR_BADDATA );

	/* Translate the SFTP status into a cryptlib status */
	for( i = 0; sftpStatusMap[ i ].sftpStatus != CRYPT_ERROR && \
				sftpStatusMap[ i ].sftpStatus != status; i++ );
	status = sftpStatusMap[ i ].cryptlibStatus;

	return( status );
	}

static int writeStatus( STREAM *stream, SFTP_INFO *info, const int sshStatus,
						const char *sshStatusString )
	{
	writeUint32( stream, info->id );
	writeUint32( stream, sshStatus );
	writeString32( stream, sshStatusString, strlen( sshStatusString ) );
	return( writeString32( stream, "", 0 ) );
	}

static int readSftpPacket( const CRYPT_SESSION cryptSession, void *buffer,
						   const int bufSize )
	{
	int bytesCopied, status;

	status = cryptPopData( cryptSession, buffer, BUFFER_SIZE, &bytesCopied );
	if( cryptStatusError( status ) )
		{
		printf( "SVR: Couldn't read data from SFTP client, status %d, line "
				"%d.\n", status, __LINE__ );
		return( status );
		}
	return( bytesCopied > 0 ? bytesCopied : CRYPT_ERROR_UNDERFLOW );
	}

static int writeSftpPacket( const CRYPT_SESSION cryptSession, const void *data,
							const int length )
	{
	int bytesCopied, status;

	status = cryptPushData( cryptSession, data, length, &bytesCopied );
	if( cryptStatusOK( status ) )
		status = cryptFlushData( cryptSession );
	if( cryptStatusError( status ) )
		{
		printf( "SVR: Couldn't write data to SFTP client, status %d, line "
				"%d.\n", status, __LINE__ );
		return( status );
		}
	if( bytesCopied < length )
		{
		printf( "SVR: Only wrote %d of %d bytes of SFTP data, line %d.\n",
				bytesCopied, length, __LINE__ );
		return( status );
		}
	return( CRYPT_OK );
	}

static int sendAck( const CRYPT_SESSION cryptSession, SFTP_INFO *sftpInfo )
	{
	STREAM stream;
	BYTE buffer[ 128 ];
	int length;

	/* Ack an SFTP packet */
	sMemOpen( &stream, buffer, 128 );
	writeUint32( &stream, 1 + sizeofStatus( "" ) );
	sputc( &stream, SSH_FXP_STATUS );
	writeStatus( &stream, sftpInfo, SSH_FX_OK, "" );
	length = stell( &stream );
	sMemDisconnect( &stream );
	return( writeSftpPacket( cryptSession, buffer, length ) );
	}

int sftpServer( const CRYPT_SESSION cryptSession )
	{
	STREAM stream;
	SFTP_ATTRS sftpAttrs;
	SFTP_INFO sftpInfo;
	BYTE buffer[ BUFFER_SIZE ], nameBuffer[ 128 ];
	time_t xferTime;
	long xferCount = 0, dataLength;
	int length, value, status;

	cryptSetAttribute( cryptSession, CRYPT_OPTION_NET_READTIMEOUT, 30 );

	memset( &sftpInfo, 0, sizeof( SFTP_INFO ) );

	/* Read the client's FXP_INIT and send our response */
	status = readSftpPacket( cryptSession, buffer, BUFFER_SIZE );
	if( cryptStatusError( status ) )
		return( status );
	sMemConnect( &stream, buffer, status );
	length = readUint32( &stream );
	value = sgetc( &stream );
	if( ( length != 1 + 4 ) || ( value != SSH_FXP_INIT ) )
		return( CRYPT_ERROR_BADDATA );
	sftpInfo.version = readUint32( &stream );
	sMemDisconnect( &stream );
	printf( "SVR: Client supports SFTP version %d.\n", sftpInfo.version );
	sMemOpen( &stream, buffer, BUFFER_SIZE );
	writeUint32( &stream, 1 + 4 );
	sputc( &stream, SSH_FXP_VERSION );
	writeUint32( &stream, 3 );
	length = stell( &stream );
	sMemDisconnect( &stream );
	status = writeSftpPacket( cryptSession, buffer, length );
	if( cryptStatusError( status ) )
		return( status );

	/* Read the client's FXP_OPEN and send our response */
	status = readSftpPacket( cryptSession, buffer, BUFFER_SIZE );
	if( cryptStatusError( status ) )
		{
		printExtError( cryptSession, "SVR: Attempt to read data from "
					   "client", status, __LINE__ );
		return( status );
		}
	sMemConnect( &stream, buffer, status );
	length = readUint32( &stream );
	value = sgetc( &stream );
	if( value == SSH_FXP_STAT )
		{
		/* See what the client is after */
		sftpInfo.id = readUint32( &stream );
		length = readUint32( &stream );
		sread( &stream, nameBuffer, length );
		sMemDisconnect( &stream );
		nameBuffer[ length ] = '\0';
		printf( "SVR: Client tried to stat file '%s'.\n", nameBuffer );
		if( strcmp( nameBuffer, "." ) )
			{
			puts( "SVR: Don't know how to respond to stat request for this "
				  "file." );
			return( CRYPT_ERROR_NOTAVAIL );
			}

		/* Send back a dummy response */
		memset( &sftpAttrs, 0, sizeof( SFTP_ATTRS ) );
		sftpAttrs.isDirectory = TRUE;
		sftpAttrs.permissions = 0777;
		sftpAttrs.size = CRYPT_UNUSED;
		sftpAttrs.atime = sftpAttrs.ctime = sftpAttrs.mtime = time( NULL );
		length = sizeofAttributes( &sftpAttrs, sftpInfo.version );
		sMemOpen( &stream, buffer, BUFFER_SIZE );
		writeUint32( &stream, 1 + UINT32_SIZE + length );
		sputc( &stream, SSH_FXP_ATTRS );
		writeUint32( &stream, sftpInfo.id );
		writeAttributes( &stream, &sftpAttrs, sftpInfo.version );
		length = stell( &stream );
		sMemDisconnect( &stream );
		status = writeSftpPacket( cryptSession, buffer, length );
		if( cryptStatusError( status ) )
			return( status );

		/* See what they want next */
		status = readSftpPacket( cryptSession, buffer, BUFFER_SIZE );
		if( cryptStatusError( status ) )
			{
			printExtError( cryptSession, "SVR: Attempt to read data from "
						   "client", status, __LINE__ );
			return( status );
			}
		sMemConnect( &stream, buffer, status );
		length = readUint32( &stream );
		value = sgetc( &stream );
		}
	if( value == SSH_FXP_OPEN )
		{
		/* See what the client is after */
		sftpInfo.id = readUint32( &stream );
		length = readUint32( &stream );
		sread( &stream, nameBuffer, length );
		value = readUint32( &stream );
		readAttributes( &stream, &sftpAttrs, sftpInfo.version );
		sMemDisconnect( &stream );
		nameBuffer[ length ] = '\0';
		printf( "Client tried to open file '%s', mode %02X, length %d.\n",
				nameBuffer, value, sftpAttrs.size );

		/* Putty for some reason tries to open the current directory for
		   create (rather than the filename), and bails out when it gets a
		   permission-denied.  So I guess we tell it to go ahead... */
		sMemOpen( &stream, buffer, BUFFER_SIZE );
		writeUint32( &stream, 1 + UINT32_SIZE + ( UINT32_SIZE + 1 ) );
		sputc( &stream, SSH_FXP_HANDLE );
		writeUint32( &stream, sftpInfo.id );
		writeUint32( &stream, 1 );
		sputc( &stream, 1 );
		length = stell( &stream );
		sMemDisconnect( &stream );
		status = writeSftpPacket( cryptSession, buffer, length );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* Now we're in the write loop... */
	xferTime = time( NULL );
	dataLength = 0;
	while( TRUE )
		{
		/* See what they want next */
		status = readSftpPacket( cryptSession, buffer, BUFFER_SIZE );
		if( cryptStatusError( status ) )
			{
			printExtError( cryptSession, "SVR: Attempt to read data from "
						   "client", status, __LINE__ );
			return( status );
			}
		if( status < 1 )
			{
			printf( "SVR: Read 0 bytes from client.\n" );
			return( CRYPT_ERROR_UNDERFLOW );
			}
		if( dataLength > 0 )
			{
			xferCount += status;
			dataLength -= status;
			printf( "SRV: -------- : %d.\r", xferCount );
			if( dataLength <= 0 )
				break;
			continue;
			}
		sMemConnect( &stream, buffer, status );
		length = readUint32( &stream );
		if( status < BUFFER_SIZE && ( length != status - UINT32_SIZE ) )
			{
			printf( "Didn't read complete packet, length = %d, byte count = "
					"%d.\n", length, status - UINT32_SIZE );
			}
		value = sgetc( &stream );
		if( value != SSH_FXP_WRITE )
			break;
		sftpInfo.id = readUint32( &stream );
		readString32( &stream, nameBuffer, &length, 128 );
		value = readUint64( &stream );
		dataLength = readUint32( &stream );
		printf( "SRV: %8d : %d.\r", value, length );
		xferCount += status - stell( &stream );
		dataLength -= status - stell( &stream );
		sMemDisconnect( &stream );

		/* Ack the write */
		if( dataLength <= 0 )
			{
			status = sendAck( cryptSession, &sftpInfo );
			if( cryptStatusError( status ) )
				return( status );
			}
		}
	xferTime = time( NULL ) - xferTime;
	printf( "Transfer time = %d seconds, %ld bytes, %d bytes/sec.\n",
			xferTime, xferCount, xferCount / xferTime );

	/* Clean up */
	if( value != SSH_FXP_CLOSE )
		{
		printf( "SVR: Client sent unexpected packet %d.\n", value );
		return( CRYPT_ERROR_BADDATA );
		}
	sftpInfo.id = readUint32( &stream );
	status = sendAck( cryptSession, &sftpInfo );
	if( cryptStatusError( status ) )
		return( status );
	status = readSftpPacket( cryptSession, buffer, BUFFER_SIZE );
	if( status == CRYPT_ERROR_COMPLETE )
		{
		puts( "SVR: Client has closed the channel." );
		return( CRYPT_OK );
		}
	if( cryptStatusError( status ) )
		return( status );
	sMemConnect( &stream, buffer, status );
	length = readUint32( &stream );
	value = sgetc( &stream );

	return( CRYPT_OK );
	}

#define SFTP_DATA_AMOUNT	( 1024 * 1024 )

int sftpClient( const CRYPT_SESSION cryptSession )
	{
	STREAM stream;
	SFTP_ATTRS sftpAttrs;
	SFTP_INFO sftpInfo;
	BYTE buffer[ BUFFER_SIZE ];
	long totalLength = SFTP_DATA_AMOUNT;
	int length, value, status;

	cryptSetAttribute( cryptSession, CRYPT_OPTION_NET_READTIMEOUT, 30 );

	memset( &sftpInfo, 0, sizeof( SFTP_INFO ) );

	/* Send our FXP_INIT and read back the response */
	sMemOpen( &stream, buffer, BUFFER_SIZE );
	writeUint32( &stream, 1 + 4 );
	sputc( &stream, SSH_FXP_INIT );
	writeUint32( &stream, 3 );
	length = stell( &stream );
	sMemDisconnect( &stream );
	status = writeSftpPacket( cryptSession, buffer, length );
	if( cryptStatusError( status ) )
		return( status );
	status = readSftpPacket( cryptSession, buffer, BUFFER_SIZE );
	if( cryptStatusError( status ) )
		return( status );
	sMemConnect( &stream, buffer, status );
	length = readUint32( &stream );
	value = sgetc( &stream );
	if( ( length != 1 + 4 ) || ( value != SSH_FXP_VERSION ) )
		return( CRYPT_ERROR_BADDATA );
	sftpInfo.version = readUint32( &stream );
	sMemDisconnect( &stream );
	printf( "Server supports SFTP version %d.\n", sftpInfo.version );

	/* Open the file to transfer */
	memset( &sftpAttrs, 0, sizeof( SFTP_ATTRS ) );
	sftpAttrs.permissions = 0777;
	sftpAttrs.size = CRYPT_UNUSED;
	sftpAttrs.atime = sftpAttrs.ctime = sftpAttrs.mtime = time( NULL );
	length = sizeofAttributes( &sftpAttrs, sftpInfo.version );
	sMemOpen( &stream, buffer, BUFFER_SIZE );
	writeUint32( &stream, 1 + UINT32_SIZE + ( UINT32_SIZE + 8 ) + UINT32_SIZE + length );
	sputc( &stream, SSH_FXP_OPEN );
	writeUint32( &stream, 1 );
	writeString32( &stream, "test.dat", 8 );
	writeUint32( &stream, SSH_FXF_CREAT | SSH_FXF_WRITE	);
	writeAttributes( &stream, &sftpAttrs, sftpInfo.version );
	length = stell( &stream );
	sMemDisconnect( &stream );
	status = writeSftpPacket( cryptSession, buffer, length );
	if( cryptStatusError( status ) )
		return( status );
	status = readSftpPacket( cryptSession, buffer, BUFFER_SIZE );
	if( cryptStatusError( status ) )
		{
		printExtError( cryptSession, "Attempt to read data from server",
					   status, __LINE__ );
		return( status );
		}
	sMemConnect( &stream, buffer, status );
	length = readUint32( &stream );
	value = sgetc( &stream );
	readUint32( &stream );
	readString32( &stream, sftpInfo.handle, &sftpInfo.handleSize,
				  MAX_HANDLE_SIZE );
	sMemDisconnect( &stream );
	if( value != SSH_FXP_HANDLE )
		{
		printf( "Server sent packet %d, expected file handle.\n", value );
		return( CRYPT_ERROR_BADDATA );
		}

	/* Send the file (just 1MB of test data) */
	sMemOpen( &stream, buffer, BUFFER_SIZE );
	writeUint32( &stream, 1 + UINT32_SIZE + \
						  ( UINT32_SIZE + sftpInfo.handleSize ) + \
						  UINT64_SIZE + ( UINT32_SIZE + SFTP_DATA_AMOUNT ) );
	sputc( &stream, SSH_FXP_WRITE );
	writeUint32( &stream, sftpInfo.id );
	writeString32( &stream, sftpInfo.handle, sftpInfo.handleSize );
	writeUint64( &stream, 0 );
	writeUint32( &stream, SFTP_DATA_AMOUNT );
	length = stell( &stream );
	memset( buffer + length, '*', BUFFER_SIZE - length );
	status = writeSftpPacket( cryptSession, buffer, BUFFER_SIZE );
	if( cryptStatusError( status ) )
		return( status );
	totalLength -= BUFFER_SIZE - length;
	while( totalLength > 0 )
		{
		memset( buffer, '*', BUFFER_SIZE );
		status = writeSftpPacket( cryptSession, buffer,
								  min( totalLength, BUFFER_SIZE ) );
		if( cryptStatusError( status ) )
			return( status );
		totalLength -= min( totalLength, BUFFER_SIZE );
		}

	/* Wait for the ack */
	status = readSftpPacket( cryptSession, buffer, BUFFER_SIZE );
	if( cryptStatusError( status ) )
		{
		printExtError( cryptSession, "Attempt to read data from server",
					   status, __LINE__ );
		return( status );
		}

	return( CRYPT_OK );
	}
#endif /* WINDOWS_THREADS */

#endif /* TEST_SESSION */
