📄 ssh2_rw.c
字号:
/****************************************************************************
* *
* cryptlib SSHv2 Session Read/Write Routines *
* Copyright Peter Gutmann 1998-2008 *
* *
****************************************************************************/
#if defined( INC_ALL )
#include "crypt.h"
#include "misc_rw.h"
#include "session.h"
#include "ssh.h"
#else
#include "crypt.h"
#include "misc/misc_rw.h"
#include "session/session.h"
#include "session/ssh.h"
#endif /* Compiler-specific includes */
#ifdef USE_SSH
/****************************************************************************
* *
* Utility Functions *
* *
****************************************************************************/
/* Processing handshake data can run into a number of special-case
conditions due to buggy SSH implementations, we handle these in a special
function to avoid cluttering up the main packet-read code */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
static int checkHandshakePacketStatus( INOUT SESSION_INFO *sessionInfoPtr,
const int headerStatus,
IN_BUFFER( headerLength ) \
const BYTE *header, const int headerLength,
const int expectedType )
{
assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
assert( headerStatus == CRYPT_ERROR_READ || cryptStatusOK( headerStatus ) );
assert( isReadPtr( header, headerLength ) );
assert( expectedType >= SSH2_MSG_DISCONNECT && \
expectedType <= SSH2_MSG_SPECIAL_REQUEST );
/* If the other side has simply dropped the connection, see if we can
get further details on what went wrong */
if( headerStatus == CRYPT_ERROR_READ )
{
/* Some servers just close the connection in response to a bad
password rather than returning an error, if it looks like this
has occurred we return a more informative error than the low-
level networking one */
if( !isServer( sessionInfoPtr ) && \
( expectedType == SSH2_MSG_SPECIAL_USERAUTH || \
expectedType == SSH2_MSG_SPECIAL_USERAUTH_PAM ) )
{
retExt( headerStatus,
( headerStatus, SESSION_ERRINFO,
"Remote server has closed the connection, possibly "
"in response to an incorrect password or other "
"authentication value" ) );
}
/* Some versions of CuteFTP simply drop the connection with no
diagnostics or error information when they get the phase 2 keyex
packet, the best that we can do is tell the user to hassle the
CuteFTP vendor about this */
if( isServer( sessionInfoPtr ) && \
( sessionInfoPtr->protocolFlags & SSH_PFLAG_CUTEFTP ) && \
expectedType == SSH2_MSG_NEWKEYS )
{
retExt( headerStatus,
( headerStatus, SESSION_ERRINFO,
"CuteFTP client has aborted the handshake due to a "
"CuteFTP bug, please contact the CuteFTP vendor" ) );
}
return( CRYPT_OK );
}
assert( cryptStatusOK( headerStatus ) );
/* Versions of SSH derived from the original SSH code base can sometimes
dump raw text strings (that is, strings not encapsulated in SSH
packets such as error packets) onto the connection if something
unexpected occurs. Normally this would result in a bad data or MAC
error since they decrypt to garbage, so we try and catch them here */
if( ( sessionInfoPtr->protocolFlags & SSH_PFLAG_TEXTDIAGS ) && \
header[ 0 ] == 'F' && \
( !memcmp( header, "FATAL: ", 7 ) || \
!memcmp( header, "FATAL ERROR:", 12 ) ) )
{
BYTE *bufPtr;
const int maxLength = min( MAX_ERRMSG_SIZE - 128,
sessionInfoPtr->receiveBufSize - 128 );
int length;
/* Copy across what we've got so far. Since this is a fatal error,
we use the receive buffer to contain the data since we don't need
it for any further processing */
memcpy( sessionInfoPtr->receiveBuffer, header,
MIN_PACKET_SIZE );
/* Read the rest of the error message */
for( length = MIN_PACKET_SIZE; length < maxLength; length++ )
{
const int ch = sgetc( &sessionInfoPtr->stream );
if( cryptStatusError( ch ) || ch == '\n' || ch == '\r' )
break;
sessionInfoPtr->receiveBuffer[ length ] = ch;
}
/* Remove trailing garbage. We check for CR and LF even though
they're excluded by the loop above because they may have been read
as part of the initial read of MIN_PACKET_SIZE bytes */
for( bufPtr = sessionInfoPtr->receiveBuffer; length > 0; length-- )
{
const int ch = bufPtr[ length - 1 ];
if( ch != '\r' && ch != '\n' && ch != '\t' && ch != ' ' )
break;
}
bufPtr[ length ] = '\0';
/* Report the error as a problem with the remote software. Since
the other side has bailed out, we mark the channel as closed to
prevent any attempt to try and perform a standard shutdown */
sessionInfoPtr->flags |= SESSION_SENDCLOSED;
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Remote SSH software has crashed, diagnostic was: '%s'",
sanitiseString( sessionInfoPtr->receiveBuffer,
MAX_ERRMSG_SIZE - 64, length ) ) );
}
/* No buggy behaviour detected */
return( CRYPT_OK );
}
/****************************************************************************
* *
* Read/Unwrap a Packet *
* *
****************************************************************************/
/* Get the reason why the peer closed the connection */
int getDisconnectInfo( SESSION_INFO *sessionInfoPtr, STREAM *stream )
{
typedef struct {
const int sshStatus, cryptlibStatus;
} ERRORMAP_INFO;
static const ERRORMAP_INFO FAR_BSS errorMap[] = {
/* A mapping of SSH error codes that have cryptlib equivalents to
the equivalent cryptlib codes. If there's no mapping available,
we use a default of CRYPT_ERROR_READ */
{ SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT, CRYPT_ERROR_PERMISSION },
{ SSH2_DISCONNECT_MAC_ERROR, CRYPT_ERROR_SIGNATURE },
{ SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE, CRYPT_ERROR_NOTAVAIL },
{ SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, CRYPT_ERROR_NOTAVAIL },
{ SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, CRYPT_ERROR_WRONGKEY },
{ CRYPT_ERROR, CRYPT_ERROR_READ }, { CRYPT_ERROR, CRYPT_ERROR_READ }
};
ERROR_INFO *errorInfo = &sessionInfoPtr->errorInfo;
char errorString[ MAX_ERRMSG_SIZE + 8 ];
int errorCode, length, i, status;
assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
assert( isWritePtr( stream, sizeof( STREAM ) ) );
/* Peer is disconnecting, find out why:
[ byte SSH2_MSG_DISCONNECT ]
uint32 reason
string description
string language_tag */
errorCode = readUint32( stream );
if( cryptStatusError( errorCode ) )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Invalid disconnect status information in disconnect "
"message" ) );
}
errorInfo->errorCode = errorCode;
status = readString32( stream, errorString, MAX_ERRMSG_SIZE - 64,
&length );
if( cryptStatusOK( status ) )
sanitiseString( errorString, MAX_ERRMSG_SIZE - 64, length );
else
{
memcpy( errorString, "<No details available>", 22 + 1 );
}
/* Try and map the SSH status to an equivalent cryptlib one */
for( i = 0; errorMap[ i ].sshStatus != CRYPT_ERROR && \
i < FAILSAFE_ARRAYSIZE( errorMap, ERRORMAP_INFO ); i++ )
{
if( errorMap[ i ].sshStatus == errorInfo->errorCode )
break;
}
if( i >= FAILSAFE_ARRAYSIZE( errorMap, ERRORMAP_INFO ) )
retIntError();
retExt( errorMap[ i ].cryptlibStatus,
( errorMap[ i ].cryptlibStatus, SESSION_ERRINFO,
"Received disconnect message: %s", errorString ) );
}
/* Read, decrypt if necessary, and check the start of a packet header */
int readPacketHeaderSSH2( SESSION_INFO *sessionInfoPtr,
const int expectedType, long *packetLength,
int *packetExtraLength,
READSTATE_INFO *readInfo )
{
SSH_INFO *sshInfo = sessionInfoPtr->sessionSSH;
STREAM stream;
BYTE headerBuffer[ MIN_PACKET_SIZE + 8 ];
const BOOLEAN isHandshake = ( readInfo == NULL ) ? TRUE : FALSE;
BYTE *headerBufPtr = isHandshake ? headerBuffer : sshInfo->headerBuffer;
long length;
int extraLength = 0, status = CRYPT_OK;
assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
assert( expectedType >= SSH2_MSG_DISCONNECT && \
expectedType <= SSH2_MSG_SPECIAL_REQUEST );
assert( isWritePtr( packetLength, sizeof( long ) ) );
assert( isWritePtr( packetExtraLength, sizeof( int ) ) );
assert( readInfo == NULL || \
isWritePtr( readInfo, sizeof( READSTATE_INFO ) ) );
/* Clear return values */
*packetLength = 0;
*packetExtraLength = 0;
assert( CRYPT_MAX_IVSIZE >= MIN_PACKET_SIZE );
/* Packet header is a single cipher block */
/* SSHv2 encrypts everything but the MAC (including the packet length)
so we need to speculatively read ahead for the minimum packet size
and decrypt that in order to figure out what to do */
if( isHandshake )
{
int localStatus;
/* Processing handshake data can run into a number of special-case
conditions due to buggy SSH implementations, to handle these we
check the return code as well as the returned data to see if we
need to process it specially */
status = readFixedHeaderAtomic( sessionInfoPtr, headerBufPtr,
MIN_PACKET_SIZE );
if( status == CRYPT_ERROR_READ || cryptStatusOK( status ) )
{
localStatus = checkHandshakePacketStatus( sessionInfoPtr,
status, headerBufPtr, MIN_PACKET_SIZE,
expectedType );
if( cryptStatusError( localStatus ) )
status = localStatus;
}
}
else
{
status = readFixedHeader( sessionInfoPtr, headerBufPtr,
MIN_PACKET_SIZE );
}
if( cryptStatusError( status ) )
return( status );
/* If we're in the data-processing stage (i.e. it's a post-handshake
data packet read), exception conditions need to be handled specially
if they occur */
if( !isHandshake )
{
/* Since data errors are always fatal, when we're in the data-
processing stage we make all errors fatal until we've finished
handling the header */
*readInfo = READINFO_FATAL;
}
/* Decrypt the header if necessary */
if( sessionInfoPtr->flags & SESSION_ISSECURE_READ )
{
status = krnlSendMessage( sessionInfoPtr->iCryptInContext,
IMESSAGE_CTX_DECRYPT, headerBufPtr,
MIN_PACKET_SIZE );
if( cryptStatusError( status ) )
return( status );
}
/* Process the packet header. The dual minimum-length checks actually
simplify to the following:
Non-secure mode: length < SSH2_HEADER_REMAINDER_SIZE (extraLength = 0).
In this case there's no MAC being used, so all that we need to
guarantee is that the packet is at least as long as the
(remaining) data that we've already read.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -