📄 ssh2_msg.c
字号:
const int streamPos = stell( stream );
const BOOLEAN hasWindowBug = \
( sessionInfoPtr->protocolFlags & SSH_PFLAG_WINDOWBUG );
long length;
int windowCount;
/* Get the payload length and make sure that it's
(approximately) valid. More exact checking will be done
by the caller (which is why we reset the stream position
to allow it to be re-read), all that we're really interested
in here is that the length is approximately valid for window-
adjust calculation purposes */
length = readUint32( stream );
sseek( stream, streamPos );
if( length < 0 || length > sessionInfoPtr->receiveBufSize )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Invalid data packet payload length %ld, should "
"be 0...%d", length,
sessionInfoPtr->receiveBufSize ) );
}
/* These are messages that consume window space, adjust the data
window and communicate changes to the other side if necessary.
See the comment in sendChannelOpen() for the reason for the
window size handling */
status = getChannelExtAttribute( sessionInfoPtr,
SSH_ATTRIBUTE_WINDOWCOUNT,
NULL, 0, &windowCount );
if( cryptStatusError( status ) )
retIntError();
windowCount += length;
if( windowCount < 0 || \
windowCount > MAX_WINDOW_SIZE - \
sessionInfoPtr->sendBufSize || \
hasWindowBug )
{
/* Send the window adjust to the remote system:
byte SSH2_MSG_CHANNEL_WINDOW_ADJUST
uint32 channel
uint32 bytes_to_add
Unfortunately the error status we return from a failed
window adjust is going to come as a complete surprise to
the caller because we're supposed to be processing a read
and not a write at this point, the write is only required
by SSH's braindamaged flow-control handling */
status = enqueueChannelData( sessionInfoPtr,
SSH2_MSG_CHANNEL_WINDOW_ADJUST,
channelNo, hasWindowBug ? \
length : MAX_WINDOW_SIZE );
if( cryptStatusError( status ) )
{
retExt( status,
( status, SESSION_ERRINFO,
"Error sending SSH window adjust for data "
"flow control" ) );
}
/* We've reset the window, start again from zero */
windowCount = 0;
}
status = setChannelExtAttribute( sessionInfoPtr,
SSH_ATTRIBUTE_WINDOWCOUNT,
NULL, windowCount );
if( cryptStatusError( status ) )
retIntError();
/* If it's a standard data packet, we're done */
if( sshInfo->packetType == SSH2_MSG_CHANNEL_DATA )
return( CRYPT_OK );
/* The extended data message is used for out-of-band data sent
over a channel, specifically output sent to stderr from a
shell command. What to do with this is somewhat uncertain,
the only possible action that we could take apart from just
ignoring it is to convert it back to in-band data. However,
something running a shell command may not expect to get
anything returned in this manner (see the comment for the
port-forwarding channel open in the client-side channel-open
code for more on this), so for now we just ignore it and
assume that the user will rely on results sent as in-band
data. This should be fairly safe since this message type
seems to be rarely (if ever) used, so apps will function
without it */
return( OK_SPECIAL );
}
case SSH2_MSG_CHANNEL_REQUEST:
status = processChannelRequest( sessionInfoPtr, stream,
prevChannelNo );
if( cryptStatusError( status ) && status != OK_SPECIAL )
return( status );
return( OK_SPECIAL );
case SSH2_MSG_CHANNEL_WINDOW_ADJUST:
/* Another noop-equivalent (but a very performance-affecting
one) */
return( OK_SPECIAL );
case SSH2_MSG_CHANNEL_EOF:
/* According to the SSH docs the EOF packet is mostly a courtesy
notification, however many implementations seem to use a
channel EOF in place of a close before sending a disconnect
message */
return( OK_SPECIAL );
case SSH2_MSG_CHANNEL_CLOSE:
/* The peer has closed their side of the channel, if our side
isn't already closed (in other words if this message isn't
a response to a close that we sent), close our side as well */
if( getChannelStatus( sessionInfoPtr, channelNo ) == CHANNEL_BOTH )
status = sendChannelClose( sessionInfoPtr, channelNo,
CHANNEL_BOTH, TRUE );
else
{
/* We've already closed our side of the channel, delete it */
status = deleteChannel( sessionInfoPtr, channelNo,
CHANNEL_BOTH, TRUE );
}
/* If this wasn't the last channel, we're done */
if( status != OK_SPECIAL )
return( OK_SPECIAL );
/* We've closed the last channel, indicate that the overall
connection is now closed. This behaviour isn't mentioned in
the spec, but it seems to be the standard way of handling
things, particularly for the most common case where
channel == session */
sessionInfoPtr->flags |= SESSION_SENDCLOSED;
retExt( CRYPT_ERROR_COMPLETE,
( CRYPT_ERROR_COMPLETE, SESSION_ERRINFO,
"Remote system closed last remaining SSH channel" ) );
}
retIntError();
}
/* Close a channel */
int closeChannel( SESSION_INFO *sessionInfoPtr,
const BOOLEAN closeAllChannels )
{
READSTATE_INFO readInfo;
const int currWriteChannelNo = \
getCurrentChannelNo( sessionInfoPtr, CHANNEL_WRITE );
int noChannels = 1, status;
/* If we've already sent the final channel-close message in response to
getting a final close notification from the peer, all that's left
to do is to disconnect the session */
if( sessionInfoPtr->flags & SESSION_SENDCLOSED )
{
sNetDisconnect( &sessionInfoPtr->stream );
return( CRYPT_OK );
}
/* Normally we can keep closing open channels until we hit the last one
whereupon we close the overall session, however if we're closing a
single identified channel we can't automatically close the whole
session as a side-effect of closing the single channel */
if( !closeAllChannels && currWriteChannelNo == UNUSED_CHANNEL_NO )
{
retExt( CRYPT_ERROR_NOTINITED,
( CRYPT_ERROR_NOTINITED, SESSION_ERRINFO,
"No channel information available to close the current "
"channel" ) );
}
/* If there's no channel open, close the session with a session
disconnect rather than a channel close:
byte SSH2_MSG_DISCONNECT
uint32 reason_code = SSH2_DISCONNECT_CONNECTION_LOST
string description = ""
string language_tag = ""
The spec doesn't explain what the reason codes actually mean, but
SSH2_DISCONNECT_CONNECTION_LOST seems to be the least inappropriate
disconnect reason at this point */
if( currWriteChannelNo == UNUSED_CHANNEL_NO )
{
/* Since we're closing the session, there's not much that we can do
in the case of an error because the very next operation is to
shut down the network session, so we don't do anything if the
send fails */
status = enqueueResponse( sessionInfoPtr, SSH2_MSG_DISCONNECT, 3,
SSH2_DISCONNECT_CONNECTION_LOST, 0, 0,
CRYPT_UNUSED );
if( cryptStatusOK( status ) )
( void ) sendEnqueuedResponse( sessionInfoPtr, CRYPT_UNUSED );
sessionInfoPtr->flags |= SESSION_SENDCLOSED;
sNetDisconnect( &sessionInfoPtr->stream );
return( CRYPT_OK );
}
/* Close one or all channels */
if( closeAllChannels )
{
int iterationCount = 0;
/* Get the first available channel (which must succeed, since we
just checked it above) and close each successive channel in
turn */
status = selectChannel( sessionInfoPtr, CRYPT_USE_DEFAULT,
CHANNEL_WRITE );
for( noChannels = 0;
cryptStatusOK( status ) && \
cryptStatusOK( selectChannel( sessionInfoPtr, CRYPT_USE_DEFAULT,
CHANNEL_WRITE ) ) && \
iterationCount++ < FAILSAFE_ITERATIONS_MED;
noChannels++ )
{
status = sendChannelClose( sessionInfoPtr,
getCurrentChannelNo( sessionInfoPtr, CHANNEL_WRITE ),
CHANNEL_WRITE, TRUE );
}
if( iterationCount >= FAILSAFE_ITERATIONS_MED )
retIntError();
}
else
{
/* We're just closing one channel, close the write side. The
complete close will be done when the other side acknowledges our
close. If this isn't the last open channel, the response to our
close will be handled as part of normal packet processing and
we're done */
status = sendChannelClose( sessionInfoPtr, currWriteChannelNo,
CHANNEL_WRITE, FALSE );
if( status != OK_SPECIAL )
{
/* If this is the last remaining channel, we similarly can't
close it */
if( status == CRYPT_ERROR_PERMISSION )
retExt( CRYPT_ERROR_PERMISSION,
( CRYPT_ERROR_PERMISSION, SESSION_ERRINFO,
"Cannot close last remaining channel without "
"closing the overall session" ) );
return( CRYPT_OK );
}
}
/* It's the last open channel, flush through the remaining data */
status = sendCloseNotification( sessionInfoPtr, NULL, 0 );
if( cryptStatusError( status ) || \
( sessionInfoPtr->protocolFlags & SESSION_SENDCLOSED ) )
{
/* There's a problem at the network level or the other side has
already closed the session, close the network link and exit */
sNetDisconnect( &sessionInfoPtr->stream );
return( CRYPT_OK );
}
/* If there's not enough room in the receive buffer to read at least 1K
of packet data, we can't try anything further */
if( sessionInfoPtr->receiveBufSize - sessionInfoPtr->receiveBufEnd < \
min( sessionInfoPtr->pendingPacketRemaining, 1024 ) )
{
sNetDisconnect( &sessionInfoPtr->stream );
return( CRYPT_OK );
}
/* Read back the other side's channel close(s). This is somewhat messy
since the other side could decide that it still wants to send us
arbitrary amounts of data (the spec is rather vague about how urgent
a channel close is, the general idea among implementors seems to be
that you should let output drain before you close your side, but
if you're in the middle of sending a 2GB file that's a lot of output
to drain). This can also be complicated by implementation-specific
quirks, for example OpenSSH may hang more or less indefinitely if
there's output coming from a background process on the server. This
is because of a rather obscure race condition that would occur if it
exited immediately in which the SSH server gets the SIGCHLD from the
(local) background process exiting before it's written all of its
data to the (local) pipe connecting it to the SSH server, so it
closes the (remote) SSH channel/connection before the last piece of
data comes over the (local) pipe. Because the server won't close the
(remote) SSH connection until it's certain that the (local) process
has written all of its data, and it'll never get the EOF over the
pipe, it hangs forever. This is a piece of Unix plumbing arcana that
doesn't really concern us, so again just exiting after a short wait
is the best response.
Since we're about to shut down the session anyway, we try
to read a basic channel close ack from the other side, if there's
anything more than that we drop it. This is complicated somewhat by
the fact that what we're doing here is something that's normally
handled by the high-level read code in sess_rw.c. What we implement
here is the absolute minimum needed to clear the stream
(sendCloseNotification() has set the necessary (small) nonzero
timeout for us) */
while( noChannels-- > 0 ) /* Range-checked earlier */
{
status = sessionInfoPtr->readHeaderFunction( sessionInfoPtr, &readInfo );
if( !cryptStatusError( status ) )
{
/* Adjust the packet info for the packet header data that was
just read */
sessionInfoPtr->receiveBufEnd += status;
sessionInfoPtr->pendingPacketPartialLength = status;
sessionInfoPtr->pendingPacketRemaining -= status;
if( sessionInfoPtr->pendingPacketRemaining <= 512 )
{
const int bytesLeft = sessionInfoPtr->receiveBufSize - \
sessionInfoPtr->receiveBufEnd;
/* We got a packet and it's probably the channel close ack,
read it */
status = sread( &sessionInfoPtr->stream,
sessionInfoPtr->receiveBuffer + \
sessionInfoPtr->receiveBufEnd,
min( sessionInfoPtr->pendingPacketRemaining, \
bytesLeft ) );
}
}
}
sNetDisconnect( &sessionInfoPtr->stream );
return( CRYPT_OK );
}
#endif /* USE_SSH */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -