/**************************************************************************** * * * 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 #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 */