📄 ssh2_msg.c
字号:
/****************************************************************************
* *
* cryptlib SSHv2 Control Message Management *
* Copyright Peter Gutmann 1998-2008 *
* *
****************************************************************************/
#include <stdio.h>
#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 *
* *
****************************************************************************/
/* Read host name/address and port information and format it into string
form for the caller */
static int readAddressAndPort( SESSION_INFO *sessionInfoPtr, STREAM *stream,
char *hostInfo, const int hostInfoMaxLen,
int *hostInfoLen )
{
BYTE stringBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
char portBuffer[ 16 + 8 ];
int stringLength, port, portLength, status;
/* Clear return value */
*hostInfo = '\0';
*hostInfoLen = 0;
/* Get the host and port:
string host
uint32 port */
status = readString32( stream, stringBuffer, CRYPT_MAX_TEXTSIZE - 4,
&stringLength );
if( cryptStatusError( status ) || \
stringLength <= 0 || stringLength > CRYPT_MAX_TEXTSIZE - 4 )
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Invalid host name value" ) );
status = port = readUint32( stream );
if( cryptStatusError( status ) || port <= 0 || port >= 65535L )
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Invalid port number value" ) );
/* Convert the info into string form for the caller to process */
portLength = sprintf_s( portBuffer, 8, ":%d", port );
memcpy( hostInfo, stringBuffer, stringLength );
if( stringLength + portLength <= hostInfoMaxLen )
{
memcpy( hostInfo + stringLength, portBuffer, portLength );
stringLength += portLength;
}
*hostInfoLen = stringLength;
return( CRYPT_OK );
}
/* Add or clear host name/address and port information */
static int getAddressAndPort( SESSION_INFO *sessionInfoPtr, STREAM *stream,
char *hostInfo, const int hostInfoMaxLen,
int *hostInfoLen )
{
int status;
/* Read the address and port info */
status = readAddressAndPort( sessionInfoPtr, stream, hostInfo,
hostInfoMaxLen, hostInfoLen );
if( cryptStatusError( status ) )
return( status );
if( getChannelStatusAddr( sessionInfoPtr, hostInfo, \
*hostInfoLen ) != CHANNEL_NONE )
{
/* We're adding new forwarding info, if it already exists this is
an error */
retExt( CRYPT_ERROR_DUPLICATE,
( CRYPT_ERROR_DUPLICATE, SESSION_ERRINFO,
"Received duplicate request for existing host/port %s",
sanitiseString( hostInfo, *hostInfoLen, *hostInfoLen ) ) );
}
return( CRYPT_OK );
}
static int clearAddressAndPort( SESSION_INFO *sessionInfoPtr, STREAM *stream )
{
#if 0 /* This is a somewhat special-case function in that it does't apply
to an open channel but to a past request for forwarding that
exists outside of the normal attribute space. Until this type of
functionality is explicitly requested by users, we don't handle
this special-case non-attribute data setting */
SSH_CHANNEL_INFO *channelInfoPtr;
char hostInfo[ CRYPT_MAX_TEXTSIZE + 8 ];
int hostInfoLen, status;
/* Read the address and port info */
status = readAddressAndPort( sessionInfoPtr, stream, hostInfo,
hostInfoMaxLen, &hostInfoLen );
if( cryptStatusError( status ) )
return( status );
return( deleteChannelAddr( sessionInfoPtr, addrInfo, addrInfoLen ) );
#else
return( CRYPT_OK );
#endif /* 0 */
}
/****************************************************************************
* *
* Client-side Channel Management *
* *
****************************************************************************/
/* Create a request for the appropriate type of service, either encrypted-
telnet, SFTP (or more generically a subsystem), or port forwarding.
There are several different port-forwarding mechanisms that we can use.
A global request of type "tcpip-forward" requests forwarding of a remote
port to the local system, specifying the remote port to be forwarded but
without actually opening a session/channel, it's merely q request for
future forwarding. When a connection arrives on the remote port for
which forwarding has been requested, the remote system opens a channel of
type "forwarded-tcpip" to the local system. To open a connection from a
locally-forwarded port to a port on the remote system, the local system
opens a channel of type "direct-tcpip" to the remote system:
Pkt Name Arg1 Arg2 Comment
--- ---- ---- ---- -------
open "session" Followed by pty-req
or subsys
open "fded-tcpip" remote_info (in) Server -> client in
response.to tcpip-fd
open "direct-tcpip" remote_info local_info Client -> server, currently
local_info = 127.0.0.1
global "tcpip-fd" remote_info (out) Request for remote
forwarding
Once we've opened a standard session, we need to follow it with either a
pty-request + shell request or a subsystem request:
Pkt Name Arg1 Arg2 Comment
--- ---- ---- ---- -------
channel "pty-req"
channel "subsystem" name
In theory we could bundle the channel open + pty-request + shell request
into a single packet group to save round-trips, but the packets sent after
the channel open require the use of the receive-channel number supplied by
the remote system. This is usually the same as the send channel that we
specify, but for some unknown reason Cisco use different send and receive
channel numbers, requiring that we wait for the response to the channel-
open before we send any subsequent packets, adding another RTT to the
exchange */
typedef enum { OPENREQUEST_NONE, OPENREQUEST_STANDALONE,
OPENREQUEST_CHANNELONLY, OPENREQUEST_SESSION } OPENREQUEST_TYPE;
static int createOpenRequest( SESSION_INFO *sessionInfoPtr, STREAM *stream,
OPENREQUEST_TYPE *requestType )
{
const long channelNo = getCurrentChannelNo( sessionInfoPtr,
CHANNEL_WRITE );
const int maxPacketSize = sessionInfoPtr->sendBufSize - \
EXTRA_PACKET_SIZE;
URL_INFO urlInfo = { DUMMY_INIT };
BYTE typeString[ CRYPT_MAX_TEXTSIZE + 8 ];
BYTE arg1String[ CRYPT_MAX_TEXTSIZE + 8 ];
BOOLEAN isPortForward = FALSE, isSubsystem = FALSE, isExec = FALSE;
int typeLen, arg1Len = DUMMY_INIT, status;
/* Clear return value */
*requestType = OPENREQUEST_NONE;
/* Get the information that's needed for the channel we're about to
create */
status = getChannelAttributeString( sessionInfoPtr,
CRYPT_SESSINFO_SSH_CHANNEL_TYPE,
typeString, CRYPT_MAX_TEXTSIZE,
&typeLen );
if( cryptStatusError( status ) )
{
retExt( status,
( status, SESSION_ERRINFO,
"Missing channel type for channel activation" ) );
}
if( !strCompare( typeString, "subsystem", 9 ) )
isSubsystem = TRUE;
if( !strCompare( typeString, "direct-tcpip", 12 ) || \
!strCompare( typeString, "forwarded-tcpip", 15 ) )
isPortForward = TRUE;
if( !strCompare( typeString, "exec", 4 ) )
isExec = TRUE;
if( isPortForward || isSubsystem || isExec )
{
status = getChannelAttributeString( sessionInfoPtr,
CRYPT_SESSINFO_SSH_CHANNEL_ARG1,
arg1String, CRYPT_MAX_TEXTSIZE,
&arg1Len );
if( cryptStatusError( status ) )
{
retExt( status,
( status, SESSION_ERRINFO,
"Missing channel argument (%s) for channel "
"activation", isPortForward ? "host name/port" : \
isExec ? "command" : "subsystem name" ) );
}
}
/* If we know that the argument is a URL (rather than a subsystem name
or command), check its validity */
if( isPortForward )
{
status = sNetParseURL( &urlInfo, arg1String, arg1Len, URL_TYPE_SSH );
if( cryptStatusError( status ) )
{
retExt( status,
( status, SESSION_ERRINFO,
"Invalid channel argument (host name/port) for "
"channel activation" ) );
}
}
/* Set the request type to tell the caller what to do after they've
sent the initial channel open */
*requestType = isPortForward ? OPENREQUEST_CHANNELONLY : \
OPENREQUEST_SESSION;
#if 0 /* Request forwarding of a port from the remote system to the local
one. Once a connection arrives on the remote port it'll open a
channel to the local system of type "forwarded-tcpip". Since
this isn't a normal channel open, we return a special status to
let the caller know that there's nothing further to do */
if( "tcpip-forward" )
{
URL_INFO urlInfo;
*requestType = OPENREQUEST_STANDALONE;
/* ...
byte type = SSH_MSG_GLOBAL_REQUEST
string request_name = "tcpip-forward"
boolean want_reply = FALSE
string remote_address_to_bind (e.g. "0.0.0.0")
uint32 remote_port_to_bind
Since this is a special-case request-only message, we let the
caller know that they don't have to proceed further with the
channel-open */
status = continuePacketStreamSSH( stream, SSH2_MSG_GLOBAL_REQUEST,
&packetOffset );
if( cryptStatusError( status ) )
return( status );
writeString32( stream, "tcpip-forward", 13 );
sputc( stream, 0 );
writeString32( stream, urlInfo.host, urlInfo.hostLen );
writeUint32( stream, urlInfo.port );
return( wrapPacketSSH2( sessionInfoPtr, stream, packetOffset ) );
}
#endif /* 0 */
/* Send a channel open:
byte type = SSH2_MSG_CHANNEL_OPEN
string channel_type
uint32 sender_channel
uint32 initial_window_size = MAX_WINDOW_SIZE
uint32 max_packet_size = bufSize
...
The use of security protocol-level flow control when there's already
a far better, heavily analysed and field-tested network protocol-
level flow control mechanism is just stupid. All it does is create
a performance handbrake where throughput can be reduced by as much as
an order of magnitude due to SSH's "flow-control" getting in the way
(Putty even has an FAQ entry "Why is SFTP so much slower than scp?",
for which the correct answer should be "It's the SSH-level flow-
control braindamage"). For this reason cryptlib always advertises a
maximum window size (effectively disabling the SSH-level flow
control) and lets the network stack and network hardware take care of
flow control, as they should */
status = openPacketStreamSSH( stream, sessionInfoPtr, CRYPT_USE_DEFAULT,
SSH2_MSG_CHANNEL_OPEN );
if( cryptStatusError( status ) )
return( status );
if( isSubsystem || isExec )
{
/* A subsystem is an additional layer on top of the standard
channel, so we have to open the channel first and then add the
subsystem later via a channel request rather than opening it
directly. An exec is a special case that works like the default
type of operation, "shell", but doesn't go via a pty */
writeString32( stream, "session", 7 );
}
else
writeString32( stream, typeString, typeLen );
writeUint32( stream, channelNo );
writeUint32( stream, MAX_WINDOW_SIZE );
status = writeUint32( stream, maxPacketSize );
if( isPortForward )
{
/* The caller has requested a port-forwarding channel open, continue
the basic channel-open packet with port-forwarding info:
...
string remote_host_to_connect
uint32 rempte_port_to_connect
string local_originator_IP_address
uint32 local_originator_port */
writeString32( stream, urlInfo.host, urlInfo.hostLen );
writeUint32( stream, urlInfo.port );
writeString32( stream, "127.0.0.1", 9 );
status = writeUint32( stream, 22 );
}
if( cryptStatusOK( status ) )
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -