📄 ssh2_msg.c
字号:
/* It's a standard channel open:
byte type = SSH2_MSG_CHANNEL_REQUEST
uint32 recipient_channel
string request_name = "pty-req"
boolean want_reply = FALSE
string TERM_environment_variable = "xterm"
uint32 cols = 80
uint32 rows = 48
uint32 pixel_width = 0
uint32 pixel_height = 0
string tty_mode_info = ""
... */
openPacketStreamSSH( stream, sessionInfoPtr, CRYPT_USE_DEFAULT,
SSH2_MSG_CHANNEL_REQUEST );
writeUint32( stream, channelNo );
writeString32( stream, "pty-req", 0 );
sputc( stream, 0 ); /* No reply */
writeString32( stream, "xterm", 0 );/* Generic */
writeUint32( stream, 80 );
writeUint32( stream, 48 ); /* 48 x 80 (we're past 24 x 80) */
writeUint32( stream, 0 );
writeUint32( stream, 0 ); /* No graphics capabilities */
writeUint32( stream, 0 ); /* No special TTY modes */
status = wrapPacketSSH2( sessionInfoPtr, stream, 0 );
if( cryptStatusError( status ) )
return( status );
/* ...
byte type = SSH2_MSG_CHANNEL_REQUEST
uint32 recipient_channel
string request_name = "shell"
boolean want_reply = FALSE
This final request, once sent, moves the server into interactive
session mode */
packetOffset = continuePacketStreamSSH( stream,
SSH2_MSG_CHANNEL_REQUEST );
writeUint32( stream, channelNo );
writeString32( stream, "shell", 0 );
sputc( stream, 0 ); /* No reply */
return( wrapPacketSSH2( sessionInfoPtr, stream, packetOffset ) );
}
/* Send a channel open */
int sendChannelOpen( SESSION_INFO *sessionInfoPtr )
{
STREAM stream;
OPENREQUEST_TYPE requestType;
const long channelNo = getCurrentChannelNo( sessionInfoPtr,
CHANNEL_READ );
long currentChannelNo;
int length, value, status;
/* Make sure that there's channel data available to activate and
that it doesn't correspond to an already-active channel */
if( channelNo == UNUSED_CHANNEL_NO )
retExt( sessionInfoPtr, CRYPT_ERROR_NOTINITED,
"No current channel information available to activate "
"channel" );
status = getChannelAttribute( sessionInfoPtr,
CRYPT_SESSINFO_SSH_CHANNEL_ACTIVE,
NULL, &value );
if( cryptStatusError( status ) || value )
retExt( sessionInfoPtr, CRYPT_ERROR_INITED,
"Current channel has already been activated" );
/* Create a request for the appropriate type of service */
status = createOpenRequest( sessionInfoPtr, &stream, &requestType );
if( cryptStatusError( status ) )
{
sMemDisconnect( &stream );
return( status );
}
/* If it's a request-only message that doesn't open a channel,send it
and exit */
if( requestType == OPENREQUEST_STANDALONE )
{
status = sendPacketSSH2( sessionInfoPtr, &stream, TRUE );
sMemDisconnect( &stream );
return( status );
}
/* Send the open request to the server. The SSHv2 spec doesn't really
explain the semantics of the server's response to the channel open
command, in particular whether the returned data size parameters are
merely a confirmation of the client's requested values or whether the
server is allowed to further modify them to suit its own requirements
(or perhaps one is for send and the other for receive?). In the
absence of any further guidance, we just ignore the returned values,
which seems to work for all deployed servers */
status = sendPacketSSH2( sessionInfoPtr, &stream, TRUE );
sMemDisconnect( &stream );
if( cryptStatusError( status ) )
return( status );
/* Wait for the server's ack of the channel open request:
byte SSH_MSG_CHANNEL_OPEN_CONFIRMATION
uint32 recipient_channel
uint32 sender_channel
uint32 initial_window_size
uint32 maximum_packet_size
... */
length = readPacketSSH2( sessionInfoPtr, SSH2_MSG_SPECIAL_CHANNEL,
ID_SIZE + UINT32_SIZE + UINT32_SIZE + \
UINT32_SIZE + UINT32_SIZE );
if( cryptStatusError( length ) )
return( length );
sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
if( sgetc( &stream ) == SSH2_MSG_CHANNEL_OPEN_FAILURE )
{
BYTE stringBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
int stringLen;
/* The channel open failed, tell the caller why:
byte SSH_MSG_CHANNEL_OPEN_FAILURE
uint32 recipient_channel
uint32 reason_code
string additional_text */
readUint32( &stream ); /* Skip channel number */
sessionInfoPtr->errorCode = readUint32( &stream );
status = readString32( &stream, stringBuffer, &stringLen,
CRYPT_MAX_TEXTSIZE );
if( cryptStatusError( status ) || \
stringLen <= 0 || stringLen > CRYPT_MAX_TEXTSIZE )
/* No error message, the best that we can do is give the reason
code as part of the message */
retExt( sessionInfoPtr, CRYPT_ERROR_OPEN,
"Channel open failed, reason code %ld",
sessionInfoPtr->errorCode );
stringBuffer[ stringLen ] = '\0';
retExt( sessionInfoPtr, CRYPT_ERROR_OPEN,
"Channel open failed, error message '%s'",
sanitiseString( stringBuffer ) );
}
currentChannelNo = readUint32( &stream );
if( currentChannelNo != channelNo )
{
sMemDisconnect( &stream );
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid channel number %ld in channel open confirmation, "
"should be %ld", currentChannelNo, channelNo );
}
currentChannelNo = readUint32( &stream );
sMemDisconnect( &stream );
/* The channel has been successfully created, mark it as active and
select it for future exchanges */
setChannelExtAttribute( sessionInfoPtr, SSH_ATTRIBUTE_ACTIVE,
NULL, TRUE );
if( currentChannelNo != channelNo )
/* It's unclear why anyone would use different channel numbers for
different directions, since it's the same channel that the data is
moving across. All (known) implementations use the same value in
both directions, just in case anyone doesn't we throw an exception in
the debug version */
setChannelExtAttribute( sessionInfoPtr, SSH_ATTRIBUTE_ALTCHANNELNO,
NULL, currentChannelNo );
status = selectChannel( sessionInfoPtr, channelNo, CHANNEL_BOTH );
if( ( requestType == OPENREQUEST_CHANNELONLY ) || \
cryptStatusError( status ) )
return( status );
assert( requestType == OPENREQUEST_SESSION );
/* It's a session open request that requires additional messages to do
anything useful, create and send the extra packets */
status = createSessionOpenRequest( sessionInfoPtr, &stream );
if( cryptStatusOK( status ) )
status = sendPacketSSH2( sessionInfoPtr, &stream, TRUE );
sMemDisconnect( &stream );
return( status );
}
/****************************************************************************
* *
* Server-side Channel Management *
* *
****************************************************************************/
/* SSH identifies channel requests using awkward string-based identifiers,
to make these easier to work with we map them to integer values */
typedef enum { REQUEST_NONE, REQUEST_SUBSYSTEM, REQUEST_SHELL, REQUEST_EXEC,
REQUEST_PORTFORWARD, REQUEST_PORTFORWARD_CANCEL, REQUEST_PTY,
REQUEST_NOOP, REQUEST_DISALLOWED } REQUEST_TYPE;
#define REQUEST_FLAG_NONE 0x00/* No request flag */
#define REQUEST_FLAG_TERMINAL 0x01/* Request ends negotiation */
typedef struct {
const char *requestName; /* String form of request type */
const REQUEST_TYPE requestType; /* Integer form of request type */
const int flags; /* Request flags */
} REQUEST_TYPE_INFO;
/* Process a global or channel request */
static int sendRequestResponse( SESSION_INFO *sessionInfoPtr,
const long channelNo,
const BOOLEAN isChannelRequest,
const BOOLEAN isSuccessful )
{
int status;
/* Indicate that the request succeeded/was denied:
byte type = SSH2_MSG_CHANNEL/GLOBAL_SUCCESS/FAILURE
[ uint32 channel_no - For channel reqs ] */
if( isChannelRequest )
status = enqueueResponse( sessionInfoPtr,
isSuccessful ? SSH2_MSG_CHANNEL_SUCCESS : \
SSH2_MSG_CHANNEL_FAILURE, 1,
( channelNo == CRYPT_USE_DEFAULT ) ? \
getCurrentChannelNo( sessionInfoPtr, CHANNEL_READ ) : \
channelNo,
CRYPT_UNUSED, CRYPT_UNUSED, CRYPT_UNUSED );
else
status = enqueueResponse( sessionInfoPtr,
isSuccessful ? SSH2_MSG_GLOBAL_SUCCESS : \
SSH2_MSG_GLOBAL_FAILURE, 0,
CRYPT_UNUSED, CRYPT_UNUSED, CRYPT_UNUSED,
CRYPT_UNUSED );
return( cryptStatusOK( status ) ? \
sendEnqueuedResponse( sessionInfoPtr, CRYPT_UNUSED ) : status );
}
static int processChannelRequest( SESSION_INFO *sessionInfoPtr,
STREAM *stream, const long prevChannelNo )
{
static const FAR_BSS REQUEST_TYPE_INFO requestInfo[] = {
/* Channel/session-creation requests, only permitted on the server-
side */
{ "subsystem", REQUEST_SUBSYSTEM, REQUEST_FLAG_TERMINAL },
{ "tcpip-forward", REQUEST_PORTFORWARD, REQUEST_FLAG_NONE },
{ "cancel-tcpip-forward", REQUEST_PORTFORWARD_CANCEL, REQUEST_FLAG_NONE },
{ "shell", REQUEST_SHELL, REQUEST_FLAG_TERMINAL },
{ "exec", REQUEST_EXEC, REQUEST_FLAG_TERMINAL },
{ "pty-req", REQUEST_PTY, REQUEST_FLAG_NONE },
/* No-op requests */
{ "env", REQUEST_NOOP, REQUEST_FLAG_NONE },
{ "exit-signal", REQUEST_NOOP, REQUEST_FLAG_NONE },
{ "exit-status", REQUEST_NOOP, REQUEST_FLAG_NONE },
{ "signal", REQUEST_NOOP, REQUEST_FLAG_NONE },
{ "xon-xoff", REQUEST_NOOP, REQUEST_FLAG_NONE },
{ "window-change", REQUEST_NOOP, REQUEST_FLAG_NONE },
/* Disallowed requests */
{ "x11-req", REQUEST_DISALLOWED, REQUEST_FLAG_NONE },
{ NULL, REQUEST_NONE, REQUEST_FLAG_NONE }
};
SSH_INFO *sshInfo = sessionInfoPtr->sessionSSH;
const BOOLEAN isChannelRequest = \
( sshInfo->packetType == SSH2_MSG_CHANNEL_REQUEST );
REQUEST_TYPE requestType = REQUEST_DISALLOWED;
BYTE stringBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
BOOLEAN wantReply, requestOK = FALSE, requestIsTerminal = FALSE;
int stringLength, i, status;
assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
assert( isWritePtr( stream, sizeof( STREAM ) ) );
/* Process the channel/global request (the type and channel number
have already been read by the caller):
[ byte type = SSH2_MSG_CHANNEL_REQUEST / SSH2_MSG_GLOBAL_REQUEST ]
[ uint32 recipient_channel - For channel reqs ]
string request_type
boolean want_reply
[...]
If there's an error at this point we can't send back a response
because one or both of the channel number and the want_reply flag
aren't available yet. The consensus among SSH implementors was that
not doing anything if the request packet is invalid is preferable to
sending back a response with a placeholder channel number, or a
response when want_reply could have been false had it been able to
be decoded */
status = readString32( stream, stringBuffer, &stringLength,
CRYPT_MAX_TEXTSIZE );
if( cryptStatusError( status ) || \
stringLength <= 0 || stringLength > CRYPT_MAX_TEXTSIZE || \
cryptStatusError( wantReply = sgetc( stream ) ) )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid %s request packet type",
isChannelRequest ? "channel" : "global" );
/* Try and identify the request type */
for( i = 0; requestInfo[ i ].requestName != NULL; i++ )
if( stringLength == strlen( requestInfo[ i ].requestName ) && \
!memcmp( stringBuffer, requestInfo[ i ].requestName,
stringLength ) )
{
requestType = requestInfo[ i ].requestType;
requestOK = ( requestType != REQUEST_DISALLOWED ) ? \
TRUE : FALSE;
requestIsTerminal = \
( requestInfo[ i ].flags & REQUEST_FLAG_TERMINAL ) ? \
TRUE : FALSE;
break;
}
/* If it's an explicitly disallowed request type or if we're the client
and it's anything other than a no-op request (for example a request
to execute a command or perform port forwarding), it isn't
permitted */
if( !requestOK || \
( !( sessionInfoPtr->flags & SESSION_ISSERVER ) && \
( requestType != REQUEST_NOOP ) ) )
{
if( wantReply )
{
status = sendRequestResponse( sessionInfoPtr, prevChannelNo,
isChannelRequest, FALSE );
if( isChannelRequest )
/* The request failed, go back to the previous channel */
selectChannel( sessionInfoPtr, prevChannelNo, CHANNEL_READ );
}
return( status );
}
assert( requestOK && \
( ( sessionInfoPtr->flags & SESSION_ISSERVER ) || \
( requestType == REQUEST_NOOP ) ) );
/* Process the request. Since these are administrative messages that
aren't visible to the caller, we don't bail out if we encounter a
problem, we just deny the request */
switch( requestType )
{
case REQUEST_SUBSYSTEM:
/* We're being asked for a subsystem, record the type:
[...]
string subsystem_name */
status = readString32( stream, stringBuffer, &stringLength,
CRYPT_MAX_TEXTSIZE );
if( cryptStatusError( status ) || \
stringLength <= 0 || stringLength > CRYPT_MAX_TEXTSIZE )
requestOK = FALSE;
else
{
/* The handling of subsystems is somewhat awkward, instead
of opening a subsystem channel SSH first opens a standard
session channel and then layers a subsystem on top of it.
Because of this we have to replace the standard channel
type with a new subsystem channel-type as well as recording
the subsystem type */
setChannelAttribute( sessionInfoPtr,
CRYPT_SESSINFO_SSH_CHANNEL_TYPE,
"subsystem", 9 );
setChannelAttribute( sessionInfoPtr,
CRYPT_SESSINFO_SSH_CHANNEL_ARG1,
stringBuffer, stringLength );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -