📄 ssh2_rw.c
字号:
/****************************************************************************
* *
* cryptlib SSHv2 Session Read/Write Routines *
* Copyright Peter Gutmann 1998-2004 *
* *
****************************************************************************/
#include <stdlib.h>
#include <string.h>
#if defined( INC_ALL )
#include "crypt.h"
#include "misc_rw.h"
#include "session.h"
#include "ssh.h"
#elif defined( INC_CHILD )
#include "../crypt.h"
#include "../misc/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_SSH2
/****************************************************************************
* *
* Utility Functions *
* *
****************************************************************************/
/* Format a string sent by the peer as a cryptlib error message */
static void formatErrorString( SESSION_INFO *sessionInfoPtr, STREAM *stream,
const char *prefixString )
{
const int stringLen = strlen( prefixString );
char *errorMessagePtr = sessionInfoPtr->errorMessage + stringLen;
int length, status;
/* Build the error message string from the prefix string and string
supplied by the peer */
memcpy( sessionInfoPtr->errorMessage, prefixString, stringLen );
status = readString32( stream, errorMessagePtr, &length,
MAX_ERRMSG_SIZE - ( stringLen + 16 ) );
if( cryptStatusOK( status ) )
{
errorMessagePtr[ length ] = '\0';
return;
}
/* There was an error with the supplied string, insert a generic
placeholder */
strcpy( errorMessagePtr, "<No details available>" );
}
/****************************************************************************
* *
* Read/Unwrap a Packet *
* *
****************************************************************************/
/* Get the reason why the peer closed the connection */
int getDisconnectInfo( SESSION_INFO *sessionInfoPtr, STREAM *stream )
{
static const FAR_BSS struct {
const int sshStatus, cryptlibStatus;
} 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 }
};
int errorCode, i;
/* Peer is disconnecting, find out why:
[ byte SSH2_MSG_DISCONNECT ]
uint32 reason
string description
string language_tag */
errorCode = readUint32( stream );
if( cryptStatusError( errorCode ) )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid status information in disconnect message" );
sessionInfoPtr->errorCode = errorCode;
formatErrorString( sessionInfoPtr, stream,
"Received disconnect message: " );
/* Try and map the SSH status to an equivalent cryptlib one */
for( i = 0; errorMap[ i ].sshStatus != CRYPT_ERROR; i++ )
if( errorMap[ i ].sshStatus == sessionInfoPtr->errorCode )
break;
return( errorMap[ i ].cryptlibStatus );
}
/* 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 )
{
BYTE *bufPtr = sessionInfoPtr->receiveBuffer + \
sessionInfoPtr->receiveBufPos, *lengthPtr = bufPtr;
const BOOLEAN isHandshake = ( readInfo == NULL ) ? TRUE : FALSE;
long length;
int extraLength = 0, status;
/* Clear return values */
*packetLength = 0;
*packetExtraLength = 0;
/* 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. Because of the
ad-hoc data handling that this requires, we use the direct memory
manipulation routines rather than the stream functions */
status = readFixedHeader( sessionInfoPtr, MIN_PACKET_SIZE );
if( cryptStatusError( status ) )
{
/* If it's something other than a read error or if we're past the
initial handshake phase, there's no special-case error handling
required and we're done */
if( status != CRYPT_ERROR_READ || !isHandshake )
return( status );
assert( isHandshake );
/* 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( !( sessionInfoPtr->flags & SESSION_ISSERVER ) && \
( expectedType == SSH2_MSG_SPECIAL_USERAUTH || \
expectedType == SSH2_MSG_SPECIAL_USERAUTH_PAM ) )
retExt( sessionInfoPtr, status,
"Remote server closed the connection, possibly in "
"response to an incorrect password" );
/* 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( ( sessionInfoPtr->flags & SESSION_ISSERVER ) && \
( sessionInfoPtr->protocolFlags & SSH_PFLAG_CUTEFTP ) && \
expectedType == SSH2_MSG_NEWKEYS )
retExt( sessionInfoPtr, status,
"CuteFTP client has aborted the handshake due to a "
"CuteFTP bug, please contact the CuteFTP vendor" );
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 )
{
/* If we didn't get anything, let the caller know */
if( status == 0 )
return( OK_SPECIAL );
/* 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;
}
/* 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 */
assert( status == MIN_PACKET_SIZE );
if( isHandshake && \
( sessionInfoPtr->protocolFlags & SSH_PFLAG_TEXTDIAGS ) && \
bufPtr [ 0 ] == 'F' && ( !memcmp( bufPtr , "FATAL: ", 7 ) || \
!memcmp( bufPtr , "FATAL ERROR:", 12 ) ) )
{
BYTE *dataStartPtr = bufPtr + MIN_PACKET_SIZE;
const int maxLength = \
min( MAX_ERRMSG_SIZE - ( MIN_PACKET_SIZE + 128 ),
sessionInfoPtr->receiveBufSize - \
( sessionInfoPtr->receiveBufPos + MIN_PACKET_SIZE ) );
/* Read the rest of the error message */
for( length = 0; length < maxLength; length++ )
{
status = sread( &sessionInfoPtr->stream,
dataStartPtr + length, 1 );
if( cryptStatusError( status ) || \
dataStartPtr[ length ] == '\n' )
break;
}
while( length > 0 && \
( dataStartPtr[ length - 1 ] == '\r' || \
dataStartPtr[ length - 1 ] == '\n' ) )
length--;
dataStartPtr[ 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 perform a proper shutdown */
sessionInfoPtr->flags |= SESSION_SENDCLOSED;
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Remote SSH software has crashed, diagnostic was '%s'",
sanitiseString( bufPtr ) );
}
/* Decrypt the header if necessary */
if( sessionInfoPtr->flags & SESSION_ISSECURE_READ )
{
status = krnlSendMessage( sessionInfoPtr->iCryptInContext,
IMESSAGE_CTX_DECRYPT, bufPtr,
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 casethere'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.
Secure mode: length < ID_SIZE + PADLENGTH_SIZE +
SSH2_MIN_PADLENGTH_SIZE. In this case there's an (implicit) MAC
present so the packet (length + extraLength) will always be
larger than the (remaining) data that we've already read. For
this case we need to check that the data payload is at least as
long as the minimum-length packet */
length = mgetLong( lengthPtr );
assert( SSH2_HEADER_REMAINDER_SIZE == MIN_PACKET_SIZE - LENGTH_SIZE );
if( sessionInfoPtr->flags & SESSION_ISSECURE_READ )
/* The MAC size isn't included in the packet length so we have to
add it manually */
extraLength = sessionInfoPtr->authBlocksize;
if( length + extraLength < SSH2_HEADER_REMAINDER_SIZE || \
length < ID_SIZE + PADLENGTH_SIZE + SSH2_MIN_PADLENGTH_SIZE || \
length + extraLength >= sessionInfoPtr->receiveBufSize )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid packet length %ld, should be %d...%d", length,
ID_SIZE + PADLENGTH_SIZE + SSH2_MIN_PADLENGTH_SIZE,
sessionInfoPtr->receiveBufSize - extraLength );
memmove( bufPtr, lengthPtr, SSH2_HEADER_REMAINDER_SIZE );
*packetLength = length;
*packetExtraLength = extraLength;
return( CRYPT_OK );
}
/* Read an SSHv2 packet. This function is only used during the handshake
phase (the data transfer phase has its own read/write code) so we can
perform some special-case handling based on this */
int readPacketSSH2( SESSION_INFO *sessionInfoPtr, int expectedType,
const int minPacketSize )
{
SSH_INFO *sshInfo = sessionInfoPtr->sessionSSH;
BYTE *dataStartPtr;
long length;
int padLength = 0, packetType, iterationCount = 0, status;
assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
assert( expectedType >= SSH2_MSG_DISCONNECT && \
expectedType <= SSH2_MSG_SPECIAL_REQUEST );
assert( minPacketSize >= 1 && minPacketSize < 1024 );
/* Alongside the expected packets the server can send us all sorts of
no-op messages, ranging from explicit no-ops (SSH2_MSG_IGNORE) through
to general chattiness (SSH2_MSG_DEBUG, SSH2_MSG_USERAUTH_BANNER).
Because we can receive any quantity of these at any time, we have to
run the receive code in a loop to strip them out */
do
{
int extraLength, status;
/* Read the SSHv2 packet header:
uint32 length (excluding MAC size)
byte padLen
[ byte type - checked but not removed ]
byte[] data
byte[] padding
byte[] MAC
The reason why the length and pad length precede the packet type
and other information is that these two fields are part of the
SSHv2 transport layer while the type and payload are seen as part
of the connection layer, although the different RFCs tend to mix
them up quite thoroughly */
assert( sessionInfoPtr->receiveBufEnd == 0 );
status = readPacketHeaderSSH2( sessionInfoPtr, expectedType, &length,
&extraLength, NULL );
if( cryptStatusError( status ) )
return( status );
assert( length + extraLength >= SSH2_HEADER_REMAINDER_SIZE && \
length + extraLength < sessionInfoPtr->receiveBufSize );
/* Read the remainder of the message. The change cipherspec message
has length 0 so we only perform the read if there's packet data
present */
if( length + extraLength > SSH2_HEADER_REMAINDER_SIZE )
{
const long remainingLength = length + extraLength - \
SSH2_HEADER_REMAINDER_SIZE;
/* Because this code is called conditionally, we can't make the
read part of the fixed-header read but have to do independent
handling of shortfalls due to read timeouts */
status = sread( &sessionInfoPtr->stream,
sessionInfoPtr->receiveBuffer + \
SSH2_HEADER_REMAINDER_SIZE,
remainingLength );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
sessionInfoPtr->errorMessage,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -