You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2443 lines
101 KiB
C#

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
using Renci.SshNet.Compression;
using Renci.SshNet.Messages;
using Renci.SshNet.Messages.Authentication;
using Renci.SshNet.Messages.Connection;
using Renci.SshNet.Messages.Transport;
using Renci.SshNet.Security;
using System.Globalization;
using System.Linq;
using Renci.SshNet.Abstractions;
using Renci.SshNet.Security.Cryptography;
namespace Renci.SshNet
{
/// <summary>
/// Provides functionality to connect and interact with SSH server.
/// </summary>
public class Session : ISession
{
private const byte Null = 0x00;
private const byte CarriageReturn = 0x0d;
private const byte LineFeed = 0x0a;
/// <summary>
/// Specifies an infinite waiting period.
/// </summary>
/// <remarks>
/// The value of this field is <c>-1</c> millisecond.
/// </remarks>
internal static readonly TimeSpan InfiniteTimeSpan = new TimeSpan(0, 0, 0, 0, -1);
/// <summary>
/// Specifies an infinite waiting period.
/// </summary>
/// <remarks>
/// The value of this field is <c>-1</c>.
/// </remarks>
internal static readonly int Infinite = -1;
/// <summary>
/// Specifies maximum packet size defined by the protocol.
/// </summary>
private const int MaximumSshPacketSize = LocalChannelDataPacketSize + 3000;
/// <summary>
/// Holds the initial local window size for the channels.
/// </summary>
/// <value>
/// 2 MB.
/// </value>
private const int InitialLocalWindowSize = LocalChannelDataPacketSize * 32;
/// <summary>
/// Holds the maximum size of channel data packets that we receive.
/// </summary>
/// <value>
/// 64 KB.
/// </value>
private const int LocalChannelDataPacketSize = 1024*64;
#if FEATURE_REGEX_COMPILE
private static readonly Regex ServerVersionRe = new Regex("^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+)( SP.+)?$", RegexOptions.Compiled);
#else
private static readonly Regex ServerVersionRe = new Regex("^SSH-(?<protoversion>[^-]+)-(?<softwareversion>.+)( SP.+)?$");
#endif
/// <summary>
/// Controls how many authentication attempts can take place at the same time.
/// </summary>
/// <remarks>
/// Some server may restrict number to prevent authentication attacks
/// </remarks>
private static readonly SemaphoreLight AuthenticationConnection = new SemaphoreLight(3);
/// <summary>
/// Holds metada about session messages
/// </summary>
private SshMessageFactory _sshMessageFactory;
/// <summary>
/// Holds a <see cref="WaitHandle"/> that is signaled when the message listener loop has completed.
/// </summary>
private EventWaitHandle _messageListenerCompleted;
/// <summary>
/// Specifies outbound packet number
/// </summary>
private volatile uint _outboundPacketSequence;
/// <summary>
/// Specifies incoming packet number
/// </summary>
private uint _inboundPacketSequence;
/// <summary>
/// WaitHandle to signal that last service request was accepted
/// </summary>
private EventWaitHandle _serviceAccepted = new AutoResetEvent(false);
/// <summary>
/// WaitHandle to signal that exception was thrown by another thread.
/// </summary>
private EventWaitHandle _exceptionWaitHandle = new ManualResetEvent(false);
/// <summary>
/// WaitHandle to signal that key exchange was completed.
/// </summary>
private EventWaitHandle _keyExchangeCompletedWaitHandle = new ManualResetEvent(false);
/// <summary>
/// WaitHandle to signal that key exchange is in progress.
/// </summary>
private bool _keyExchangeInProgress;
/// <summary>
/// Exception that need to be thrown by waiting thread
/// </summary>
private Exception _exception;
/// <summary>
/// Specifies whether connection is authenticated
/// </summary>
private bool _isAuthenticated;
/// <summary>
/// Specifies whether user issued Disconnect command or not
/// </summary>
private bool _isDisconnecting;
private IKeyExchange _keyExchange;
private HashAlgorithm _serverMac;
private HashAlgorithm _clientMac;
private Cipher _clientCipher;
private Cipher _serverCipher;
private Compressor _serverDecompression;
private Compressor _clientCompression;
private SemaphoreLight _sessionSemaphore;
/// <summary>
/// Holds the factory to use for creating new services.
/// </summary>
private readonly IServiceFactory _serviceFactory;
/// <summary>
/// Holds connection socket.
/// </summary>
private Socket _socket;
#if FEATURE_SOCKET_POLL
/// <summary>
/// Holds an object that is used to ensure only a single thread can read from
/// <see cref="_socket"/> at any given time.
/// </summary>
private readonly object _socketReadLock = new object();
#endif // FEATURE_SOCKET_POLL
/// <summary>
/// Holds an object that is used to ensure only a single thread can write to
/// <see cref="_socket"/> at any given time.
/// </summary>
/// <remarks>
/// This is also used to ensure that <see cref="_outboundPacketSequence"/> is
/// incremented atomatically.
/// </remarks>
private readonly object _socketWriteLock = new object();
/// <summary>
/// Holds an object that is used to ensure only a single thread can dispose
/// <see cref="_socket"/> at any given time.
/// </summary>
/// <remarks>
/// This is also used to ensure that <see cref="_socket"/> will not be disposed
/// while performing a given operation or set of operations on <see cref="_socket"/>.
/// </remarks>
private readonly object _socketDisposeLock = new object();
/// <summary>
/// Gets the session semaphore that controls session channels.
/// </summary>
/// <value>
/// The session semaphore.
/// </value>
public SemaphoreLight SessionSemaphore
{
get
{
if (_sessionSemaphore == null)
{
lock (this)
{
if (_sessionSemaphore == null)
{
_sessionSemaphore = new SemaphoreLight(ConnectionInfo.MaxSessions);
}
}
}
return _sessionSemaphore;
}
}
private bool _isDisconnectMessageSent;
private uint _nextChannelNumber;
/// <summary>
/// Gets the next channel number.
/// </summary>
/// <value>
/// The next channel number.
/// </value>
private uint NextChannelNumber
{
get
{
uint result;
lock (this)
{
result = _nextChannelNumber++;
}
return result;
}
}
/// <summary>
/// Gets a value indicating whether the session is connected.
/// </summary>
/// <value>
/// <c>true</c> if the session is connected; otherwise, <c>false</c>.
/// </value>
/// <remarks>
/// This methods returns <c>true</c> in all but the following cases:
/// <list type="bullet">
/// <item>
/// <description>The <see cref="Session"/> is disposed.</description>
/// </item>
/// <item>
/// <description>The SSH_MSG_DISCONNECT message - which is used to disconnect from the server - has been sent.</description>
/// </item>
/// <item>
/// <description>The client has not been authenticated successfully.</description>
/// </item>
/// <item>
/// <description>The listener thread - which is used to receive messages from the server - has stopped.</description>
/// </item>
/// <item>
/// <description>The socket used to communicate with the server is no longer connected.</description>
/// </item>
/// </list>
/// </remarks>
public bool IsConnected
{
get
{
if (_disposed || _isDisconnectMessageSent || !_isAuthenticated)
return false;
if (_messageListenerCompleted == null || _messageListenerCompleted.WaitOne(0))
return false;
return IsSocketConnected();
}
}
/// <summary>
/// Gets the session id.
/// </summary>
/// <value>
/// The session id, or <c>null</c> if the client has not been authenticated.
/// </value>
public byte[] SessionId { get; private set; }
private Message _clientInitMessage;
/// <summary>
/// Gets the client init message.
/// </summary>
/// <value>The client init message.</value>
public Message ClientInitMessage
{
get
{
if (_clientInitMessage == null)
{
_clientInitMessage = new KeyExchangeInitMessage
{
KeyExchangeAlgorithms = ConnectionInfo.KeyExchangeAlgorithms.Keys.ToArray(),
ServerHostKeyAlgorithms = ConnectionInfo.HostKeyAlgorithms.Keys.ToArray(),
EncryptionAlgorithmsClientToServer = ConnectionInfo.Encryptions.Keys.ToArray(),
EncryptionAlgorithmsServerToClient = ConnectionInfo.Encryptions.Keys.ToArray(),
MacAlgorithmsClientToServer = ConnectionInfo.HmacAlgorithms.Keys.ToArray(),
MacAlgorithmsServerToClient = ConnectionInfo.HmacAlgorithms.Keys.ToArray(),
CompressionAlgorithmsClientToServer = ConnectionInfo.CompressionAlgorithms.Keys.ToArray(),
CompressionAlgorithmsServerToClient = ConnectionInfo.CompressionAlgorithms.Keys.ToArray(),
LanguagesClientToServer = new[] {string.Empty},
LanguagesServerToClient = new[] {string.Empty},
FirstKexPacketFollows = false,
Reserved = 0
};
}
return _clientInitMessage;
}
}
/// <summary>
/// Gets or sets the server version string.
/// </summary>
/// <value>The server version.</value>
public string ServerVersion { get; private set; }
/// <summary>
/// Gets or sets the client version string.
/// </summary>
/// <value>The client version.</value>
public string ClientVersion { get; private set; }
/// <summary>
/// Gets or sets the connection info.
/// </summary>
/// <value>The connection info.</value>
public ConnectionInfo ConnectionInfo { get; private set; }
/// <summary>
/// Occurs when an error occurred.
/// </summary>
public event EventHandler<ExceptionEventArgs> ErrorOccured;
/// <summary>
/// Occurs when session has been disconnected from the server.
/// </summary>
public event EventHandler<EventArgs> Disconnected;
/// <summary>
/// Occurs when host key received.
/// </summary>
public event EventHandler<HostKeyEventArgs> HostKeyReceived;
/// <summary>
/// Occurs when <see cref="BannerMessage"/> message is received from the server.
/// </summary>
public event EventHandler<MessageEventArgs<BannerMessage>> UserAuthenticationBannerReceived;
/// <summary>
/// Occurs when <see cref="InformationRequestMessage"/> message is received from the server.
/// </summary>
internal event EventHandler<MessageEventArgs<InformationRequestMessage>> UserAuthenticationInformationRequestReceived;
/// <summary>
/// Occurs when <see cref="PasswordChangeRequiredMessage"/> message is received from the server.
/// </summary>
internal event EventHandler<MessageEventArgs<PasswordChangeRequiredMessage>> UserAuthenticationPasswordChangeRequiredReceived;
/// <summary>
/// Occurs when <see cref="PublicKeyMessage"/> message is received from the server.
/// </summary>
internal event EventHandler<MessageEventArgs<PublicKeyMessage>> UserAuthenticationPublicKeyReceived;
/// <summary>
/// Occurs when <see cref="KeyExchangeDhGroupExchangeGroup"/> message is received from the server.
/// </summary>
internal event EventHandler<MessageEventArgs<KeyExchangeDhGroupExchangeGroup>> KeyExchangeDhGroupExchangeGroupReceived;
/// <summary>
/// Occurs when <see cref="KeyExchangeDhGroupExchangeReply"/> message is received from the server.
/// </summary>
internal event EventHandler<MessageEventArgs<KeyExchangeDhGroupExchangeReply>> KeyExchangeDhGroupExchangeReplyReceived;
#region Message events
/// <summary>
/// Occurs when <see cref="DisconnectMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<DisconnectMessage>> DisconnectReceived;
/// <summary>
/// Occurs when <see cref="IgnoreMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<IgnoreMessage>> IgnoreReceived;
/// <summary>
/// Occurs when <see cref="UnimplementedMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<UnimplementedMessage>> UnimplementedReceived;
/// <summary>
/// Occurs when <see cref="DebugMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<DebugMessage>> DebugReceived;
/// <summary>
/// Occurs when <see cref="ServiceRequestMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<ServiceRequestMessage>> ServiceRequestReceived;
/// <summary>
/// Occurs when <see cref="ServiceAcceptMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<ServiceAcceptMessage>> ServiceAcceptReceived;
/// <summary>
/// Occurs when <see cref="KeyExchangeInitMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<KeyExchangeInitMessage>> KeyExchangeInitReceived;
/// <summary>
/// Occurs when a <see cref="KeyExchangeDhReplyMessage"/> message is received from the SSH server.
/// </summary>
internal event EventHandler<MessageEventArgs<KeyExchangeDhReplyMessage>> KeyExchangeDhReplyMessageReceived;
/// <summary>
/// Occurs when <see cref="NewKeysMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<NewKeysMessage>> NewKeysReceived;
/// <summary>
/// Occurs when <see cref="RequestMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<RequestMessage>> UserAuthenticationRequestReceived;
/// <summary>
/// Occurs when <see cref="FailureMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<FailureMessage>> UserAuthenticationFailureReceived;
/// <summary>
/// Occurs when <see cref="SuccessMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<SuccessMessage>> UserAuthenticationSuccessReceived;
/// <summary>
/// Occurs when <see cref="GlobalRequestMessage"/> message received
/// </summary>
internal event EventHandler<MessageEventArgs<GlobalRequestMessage>> GlobalRequestReceived;
/// <summary>
/// Occurs when <see cref="RequestSuccessMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<RequestSuccessMessage>> RequestSuccessReceived;
/// <summary>
/// Occurs when <see cref="RequestFailureMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<RequestFailureMessage>> RequestFailureReceived;
/// <summary>
/// Occurs when <see cref="ChannelOpenMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<ChannelOpenMessage>> ChannelOpenReceived;
/// <summary>
/// Occurs when <see cref="ChannelOpenConfirmationMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<ChannelOpenConfirmationMessage>> ChannelOpenConfirmationReceived;
/// <summary>
/// Occurs when <see cref="ChannelOpenFailureMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<ChannelOpenFailureMessage>> ChannelOpenFailureReceived;
/// <summary>
/// Occurs when <see cref="ChannelWindowAdjustMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<ChannelWindowAdjustMessage>> ChannelWindowAdjustReceived;
/// <summary>
/// Occurs when <see cref="ChannelDataMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<ChannelDataMessage>> ChannelDataReceived;
/// <summary>
/// Occurs when <see cref="ChannelExtendedDataMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<ChannelExtendedDataMessage>> ChannelExtendedDataReceived;
/// <summary>
/// Occurs when <see cref="ChannelEofMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<ChannelEofMessage>> ChannelEofReceived;
/// <summary>
/// Occurs when <see cref="ChannelCloseMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<ChannelCloseMessage>> ChannelCloseReceived;
/// <summary>
/// Occurs when <see cref="ChannelRequestMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<ChannelRequestMessage>> ChannelRequestReceived;
/// <summary>
/// Occurs when <see cref="ChannelSuccessMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<ChannelSuccessMessage>> ChannelSuccessReceived;
/// <summary>
/// Occurs when <see cref="ChannelFailureMessage"/> message received
/// </summary>
public event EventHandler<MessageEventArgs<ChannelFailureMessage>> ChannelFailureReceived;
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="Session"/> class.
/// </summary>
/// <param name="connectionInfo">The connection info.</param>
/// <param name="serviceFactory">The factory to use for creating new services.</param>
/// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="serviceFactory"/> is <c>null</c>.</exception>
internal Session(ConnectionInfo connectionInfo, IServiceFactory serviceFactory)
{
if (connectionInfo == null)
throw new ArgumentNullException("connectionInfo");
if (serviceFactory == null)
throw new ArgumentNullException("serviceFactory");
ClientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1";
ConnectionInfo = connectionInfo;
_serviceFactory = serviceFactory;
_messageListenerCompleted = new ManualResetEvent(true);
}
/// <summary>
/// Connects to the server.
/// </summary>
/// <exception cref="SocketException">Socket connection to the SSH server or proxy server could not be established, or an error occurred while resolving the hostname.</exception>
/// <exception cref="SshConnectionException">SSH session could not be established.</exception>
/// <exception cref="SshAuthenticationException">Authentication of SSH session failed.</exception>
/// <exception cref="ProxyException">Failed to establish proxy connection.</exception>
public void Connect()
{
if (IsConnected)
return;
try
{
AuthenticationConnection.Wait();
if (IsConnected)
return;
lock (this)
{
// If connected don't connect again
if (IsConnected)
return;
// reset connection specific information
Reset();
// Build list of available messages while connecting
_sshMessageFactory = new SshMessageFactory();
switch (ConnectionInfo.ProxyType)
{
case ProxyTypes.None:
SocketConnect(ConnectionInfo.Host, ConnectionInfo.Port);
break;
case ProxyTypes.Socks4:
SocketConnect(ConnectionInfo.ProxyHost, ConnectionInfo.ProxyPort);
ConnectSocks4();
break;
case ProxyTypes.Socks5:
SocketConnect(ConnectionInfo.ProxyHost, ConnectionInfo.ProxyPort);
ConnectSocks5();
break;
case ProxyTypes.Http:
SocketConnect(ConnectionInfo.ProxyHost, ConnectionInfo.ProxyPort);
ConnectHttp();
break;
}
Match versionMatch;
// Get server version from the server,
// ignore text lines which are sent before if any
while (true)
{
var serverVersion = SocketReadLine(ConnectionInfo.Timeout);
if (serverVersion == null)
throw new SshConnectionException("Server response does not contain SSH protocol identification.", DisconnectReason.ProtocolError);
versionMatch = ServerVersionRe.Match(serverVersion);
if (versionMatch.Success)
{
ServerVersion = serverVersion;
break;
}
}
// Set connection versions
ConnectionInfo.ServerVersion = ServerVersion;
ConnectionInfo.ClientVersion = ClientVersion;
// Get server SSH version
var version = versionMatch.Result("${protoversion}");
var softwareName = versionMatch.Result("${softwareversion}");
DiagnosticAbstraction.Log(string.Format("Server version '{0}' on '{1}'.", version, softwareName));
if (!(version.Equals("2.0") || version.Equals("1.99")))
{
throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Server version '{0}' is not supported.", version), DisconnectReason.ProtocolVersionNotSupported);
}
SocketAbstraction.Send(_socket, Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\x0D\x0A", ClientVersion)));
// Register Transport response messages
RegisterMessage("SSH_MSG_DISCONNECT");
RegisterMessage("SSH_MSG_IGNORE");
RegisterMessage("SSH_MSG_UNIMPLEMENTED");
RegisterMessage("SSH_MSG_DEBUG");
RegisterMessage("SSH_MSG_SERVICE_ACCEPT");
RegisterMessage("SSH_MSG_KEXINIT");
RegisterMessage("SSH_MSG_NEWKEYS");
// Some server implementations might sent this message first, prior establishing encryption algorithm
RegisterMessage("SSH_MSG_USERAUTH_BANNER");
// mark the message listener threads as started
_messageListenerCompleted.Reset();
// Start incoming request listener
ThreadAbstraction.ExecuteThread(MessageListener);
// Wait for key exchange to be completed
WaitOnHandle(_keyExchangeCompletedWaitHandle);
// If sessionId is not set then its not connected
if (SessionId == null)
{
Disconnect();
return;
}
// Request user authorization service
SendMessage(new ServiceRequestMessage(ServiceName.UserAuthentication));
// Wait for service to be accepted
WaitOnHandle(_serviceAccepted);
if (string.IsNullOrEmpty(ConnectionInfo.Username))
{
throw new SshException("Username is not specified.");
}
// Some servers send a global request immediately after successful authentication
// Avoid race condition by already enabling SSH_MSG_GLOBAL_REQUEST before authentication
RegisterMessage("SSH_MSG_GLOBAL_REQUEST");
ConnectionInfo.Authenticate(this, _serviceFactory);
_isAuthenticated = true;
// Register Connection messages
RegisterMessage("SSH_MSG_REQUEST_SUCCESS");
RegisterMessage("SSH_MSG_REQUEST_FAILURE");
RegisterMessage("SSH_MSG_CHANNEL_OPEN_CONFIRMATION");
RegisterMessage("SSH_MSG_CHANNEL_OPEN_FAILURE");
RegisterMessage("SSH_MSG_CHANNEL_WINDOW_ADJUST");
RegisterMessage("SSH_MSG_CHANNEL_EXTENDED_DATA");
RegisterMessage("SSH_MSG_CHANNEL_REQUEST");
RegisterMessage("SSH_MSG_CHANNEL_SUCCESS");
RegisterMessage("SSH_MSG_CHANNEL_FAILURE");
RegisterMessage("SSH_MSG_CHANNEL_DATA");
RegisterMessage("SSH_MSG_CHANNEL_EOF");
RegisterMessage("SSH_MSG_CHANNEL_CLOSE");
}
}
finally
{
AuthenticationConnection.Release();
}
}
/// <summary>
/// Disconnects from the server.
/// </summary>
/// <remarks>
/// This sends a <b>SSH_MSG_DISCONNECT</b> message to the server, waits for the
/// server to close the socket on its end and subsequently closes the client socket.
/// </remarks>
public void Disconnect()
{
DiagnosticAbstraction.Log(string.Format("[{0}] Disconnecting session.", ToHex(SessionId)));
// send SSH_MSG_DISCONNECT message, clear socket read buffer and dispose it
Disconnect(DisconnectReason.ByApplication, "Connection terminated by the client.");
// at this point, we are sure that the listener thread will stop as we've
// disconnected the socket, so lets wait until the message listener thread
// has completed
if (_messageListenerCompleted != null)
{
_messageListenerCompleted.WaitOne();
}
}
private void Disconnect(DisconnectReason reason, string message)
{
// transition to disconnecting state to avoid throwing exceptions while cleaning up, and to
// ensure any exceptions that are raised do not overwrite the exception that is set
_isDisconnecting = true;
// send disconnect message to the server if the connection is still open
// and the disconnect message has not yet been sent
//
// note that this should also cause the listener loop to be interrupted as
// the server should respond by closing the socket
if (IsConnected)
{
TrySendDisconnect(reason, message);
}
// disconnect socket, and dispose it
SocketDisconnectAndDispose();
}
/// <summary>
/// Waits for the specified handle or the exception handle for the receive thread
/// to signal within the connection timeout.
/// </summary>
/// <param name="waitHandle">The wait handle.</param>
/// <exception cref="SshConnectionException">A received package was invalid or failed the message integrity check.</exception>
/// <exception cref="SshOperationTimeoutException">None of the handles are signaled in time and the session is not disconnecting.</exception>
/// <exception cref="SocketException">A socket error was signaled while receiving messages from the server.</exception>
/// <remarks>
/// When neither handles are signaled in time and the session is not closing, then the
/// session is disconnected.
/// </remarks>
void ISession.WaitOnHandle(WaitHandle waitHandle)
{
WaitOnHandle(waitHandle, ConnectionInfo.Timeout);
}
/// <summary>
/// Waits for the specified handle or the exception handle for the receive thread
/// to signal within the specified timeout.
/// </summary>
/// <param name="waitHandle">The wait handle.</param>
/// <param name="timeout">The time to wait for any of the handles to become signaled.</param>
/// <exception cref="SshConnectionException">A received package was invalid or failed the message integrity check.</exception>
/// <exception cref="SshOperationTimeoutException">None of the handles are signaled in time and the session is not disconnecting.</exception>
/// <exception cref="SocketException">A socket error was signaled while receiving messages from the server.</exception>
/// <remarks>
/// When neither handles are signaled in time and the session is not closing, then the
/// session is disconnected.
/// </remarks>
void ISession.WaitOnHandle(WaitHandle waitHandle, TimeSpan timeout)
{
WaitOnHandle(waitHandle, timeout);
}
/// <summary>
/// Waits for the specified handle or the exception handle for the receive thread
/// to signal within the connection timeout.
/// </summary>
/// <param name="waitHandle">The wait handle.</param>
/// <exception cref="SshConnectionException">A received package was invalid or failed the message integrity check.</exception>
/// <exception cref="SshOperationTimeoutException">None of the handles are signaled in time and the session is not disconnecting.</exception>
/// <exception cref="SocketException">A socket error was signaled while receiving messages from the server.</exception>
/// <remarks>
/// When neither handles are signaled in time and the session is not closing, then the
/// session is disconnected.
/// </remarks>
internal void WaitOnHandle(WaitHandle waitHandle)
{
WaitOnHandle(waitHandle, ConnectionInfo.Timeout);
}
/// <summary>
/// Waits for the specified handle or the exception handle for the receive thread
/// to signal within the specified timeout.
/// </summary>
/// <param name="waitHandle">The wait handle.</param>
/// <param name="timeout">The time to wait for any of the handles to become signaled.</param>
/// <exception cref="SshConnectionException">A received package was invalid or failed the message integrity check.</exception>
/// <exception cref="SshOperationTimeoutException">None of the handles are signaled in time and the session is not disconnecting.</exception>
/// <exception cref="SocketException">A socket error was signaled while receiving messages from the server.</exception>
internal void WaitOnHandle(WaitHandle waitHandle, TimeSpan timeout)
{
if (waitHandle == null)
throw new ArgumentNullException("waitHandle");
var waitHandles = new[]
{
_exceptionWaitHandle,
_messageListenerCompleted,
waitHandle
};
switch (WaitHandle.WaitAny(waitHandles, timeout))
{
case 0:
throw _exception;
case 1:
throw new SshConnectionException("Client not connected.");
case WaitHandle.WaitTimeout:
// when the session is disconnecting, a timeout is likely when no
// network connectivity is available; depending on the configured
// timeout either the WaitAny times out first or a SocketException
// detailing a timeout thrown hereby completing the listener thread
// (which makes us end up in case 1). Either way, we do not want to
// report an exception to the client when we're disconnecting anyway
if (!_isDisconnecting)
{
throw new SshOperationTimeoutException("Session operation has timed out");
}
break;
}
}
/// <summary>
/// Sends a message to the server.
/// </summary>
/// <param name="message">The message to send.</param>
/// <exception cref="SshConnectionException">The client is not connected.</exception>
/// <exception cref="SshOperationTimeoutException">The operation timed out.</exception>
/// <exception cref="InvalidOperationException">The size of the packet exceeds the maximum size defined by the protocol.</exception>
internal void SendMessage(Message message)
{
if (!_socket.CanWrite())
throw new SshConnectionException("Client not connected.");
if (_keyExchangeInProgress && !(message is IKeyExchangedAllowed))
{
// Wait for key exchange to be completed
WaitOnHandle(_keyExchangeCompletedWaitHandle);
}
DiagnosticAbstraction.Log(string.Format("[{0}] Sending message '{1}' to server: '{2}'.", ToHex(SessionId), message.GetType().Name, message));
var paddingMultiplier = _clientCipher == null ? (byte) 8 : Math.Max((byte) 8, _serverCipher.MinimumSize);
var packetData = message.GetPacket(paddingMultiplier, _clientCompression);
// take a write lock to ensure the outbound packet sequence number is incremented
// atomically, and only after the packet has actually been sent
lock (_socketWriteLock)
{
byte[] hash = null;
var packetDataOffset = 4; // first four bytes are reserved for outbound packet sequence
if (_clientMac != null)
{
// write outbound packet sequence to start of packet data
Pack.UInt32ToBigEndian(_outboundPacketSequence, packetData);
// calculate packet hash
hash = _clientMac.ComputeHash(packetData);
}
// Encrypt packet data
if (_clientCipher != null)
{
packetData = _clientCipher.Encrypt(packetData, packetDataOffset, (packetData.Length - packetDataOffset));
packetDataOffset = 0;
}
if (packetData.Length > MaximumSshPacketSize)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Packet is too big. Maximum packet size is {0} bytes.", MaximumSshPacketSize));
}
var packetLength = packetData.Length - packetDataOffset;
if (hash == null)
{
SendPacket(packetData, packetDataOffset, packetLength);
}
else
{
var data = new byte[packetLength + hash.Length];
Buffer.BlockCopy(packetData, packetDataOffset, data, 0, packetLength);
Buffer.BlockCopy(hash, 0, data, packetLength, hash.Length);
SendPacket(data, 0, data.Length);
}
// increment the packet sequence number only after we're sure the packet has
// been sent; even though it's only used for the MAC, it needs to be incremented
// for each package sent.
//
// the server will use it to verify the data integrity, and as such the order in
// which messages are sent must follow the outbound packet sequence number
_outboundPacketSequence++;
}
}
/// <summary>
/// Sends an SSH packet to the server.
/// </summary>
/// <param name="packet">A byte array containing the packet to send.</param>
/// <param name="offset">The offset of the packet.</param>
/// <param name="length">The length of the packet.</param>
/// <exception cref="SshConnectionException">Client is not connected to the server.</exception>
/// <remarks>
/// <para>
/// The send is performed in a dispose lock to avoid <see cref="NullReferenceException"/>
/// and/or <see cref="ObjectDisposedException"/> when sending the packet.
/// </para>
/// <para>
/// This method is only to be used when the connection is established, as the locking
/// overhead is not required while establising the connection.
/// </para>
/// </remarks>
private void SendPacket(byte[] packet, int offset, int length)
{
lock (_socketDisposeLock)
{
if (!_socket.IsConnected())
throw new SshConnectionException("Client not connected.");
SocketAbstraction.Send(_socket, packet, offset, length);
}
}
/// <summary>
/// Sends a message to the server.
/// </summary>
/// <param name="message">The message to send.</param>
/// <returns>
/// <c>true</c> if the message was sent to the server; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="InvalidOperationException">The size of the packet exceeds the maximum size defined by the protocol.</exception>
/// <remarks>
/// This methods returns <c>false</c> when the attempt to send the message results in a
/// <see cref="SocketException"/> or a <see cref="SshException"/>.
/// </remarks>
private bool TrySendMessage(Message message)
{
try
{
SendMessage(message);
return true;
}
catch (SshException ex)
{
DiagnosticAbstraction.Log(string.Format("Failure sending message '{0}' to server: '{1}' => {2}", message.GetType().Name, message, ex));
return false;
}
catch (SocketException ex)
{
DiagnosticAbstraction.Log(string.Format("Failure sending message '{0}' to server: '{1}' => {2}", message.GetType().Name, message, ex));
return false;
}
}
/// <summary>
/// Receives the message from the server.
/// </summary>
/// <returns>
/// The incoming SSH message, or <c>null</c> if the connection with the SSH server was closed.
/// </returns>
/// <remarks>
/// We need no locking here since all messages are read by a single thread.
/// </remarks>
private Message ReceiveMessage()
{
// the length of the packet sequence field in bytes
const int inboundPacketSequenceLength = 4;
// The length of the "packet length" field in bytes
const int packetLengthFieldLength = 4;
// The length of the "padding length" field in bytes
const int paddingLengthFieldLength = 1;
// Determine the size of the first block, which is 8 or cipher block size (whichever is larger) bytes
var blockSize = _serverCipher == null ? (byte) 8 : Math.Max((byte) 8, _serverCipher.MinimumSize);
var serverMacLength = _serverMac != null ? _serverMac.HashSize/8 : 0;
byte[] data;
uint packetLength;
#if FEATURE_SOCKET_POLL
// avoid reading from socket while IsSocketConnected is attempting to determine whether the
// socket is still connected by invoking Socket.Poll(...) and subsequently verifying value of
// Socket.Available
lock (_socketReadLock)
{
#endif // FEATURE_SOCKET_POLL
// Read first block - which starts with the packet length
var firstBlock = new byte[blockSize];
if (TrySocketRead(firstBlock, 0, blockSize) == 0)
{
// connection with SSH server was closed
return null;
}
if (_serverCipher != null)
{
firstBlock = _serverCipher.Decrypt(firstBlock);
}
packetLength = Pack.BigEndianToUInt32(firstBlock);
// Test packet minimum and maximum boundaries
if (packetLength < Math.Max((byte) 16, blockSize) - 4 || packetLength > MaximumSshPacketSize - 4)
throw new SshConnectionException(
string.Format(CultureInfo.CurrentCulture, "Bad packet length: {0}.", packetLength),
DisconnectReason.ProtocolError);
// Determine the number of bytes left to read; We've already read "blockSize" bytes, but the
// "packet length" field itself - which is 4 bytes - is not included in the length of the packet
var bytesToRead = (int) (packetLength - (blockSize - packetLengthFieldLength)) + serverMacLength;
// Construct buffer for holding the payload and the inbound packet sequence as we need both in order
// to generate the hash.
//
// The total length of the "data" buffer is an addition of:
// - inboundPacketSequenceLength (4 bytes)
// - packetLength
// - serverMacLength
//
// We include the inbound packet sequence to allow us to have the the full SSH packet in a single
// byte[] for the purpose of calculating the client hash. Room for the server MAC is foreseen
// to read the packet including server MAC in a single pass (except for the initial block).
data = new byte[bytesToRead + blockSize + inboundPacketSequenceLength];
Pack.UInt32ToBigEndian(_inboundPacketSequence, data);
Buffer.BlockCopy(firstBlock, 0, data, inboundPacketSequenceLength, firstBlock.Length);
if (bytesToRead > 0)
{
if (TrySocketRead(data, blockSize + inboundPacketSequenceLength, bytesToRead) == 0)
{
return null;
}
}
#if FEATURE_SOCKET_POLL
}
#endif // FEATURE_SOCKET_POLL
if (_serverCipher != null)
{
var numberOfBytesToDecrypt = data.Length - (blockSize + inboundPacketSequenceLength + serverMacLength);
if (numberOfBytesToDecrypt > 0)
{
var decryptedData = _serverCipher.Decrypt(data, blockSize + inboundPacketSequenceLength, numberOfBytesToDecrypt);
Buffer.BlockCopy(decryptedData, 0, data, blockSize + inboundPacketSequenceLength, decryptedData.Length);
}
}
var paddingLength = data[inboundPacketSequenceLength + packetLengthFieldLength];
var messagePayloadLength = (int) packetLength - paddingLength - paddingLengthFieldLength;
var messagePayloadOffset = inboundPacketSequenceLength + packetLengthFieldLength + paddingLengthFieldLength;
// validate message against MAC
if (_serverMac != null)
{
var clientHash = _serverMac.ComputeHash(data, 0, data.Length - serverMacLength);
var serverHash = data.Take(data.Length - serverMacLength, serverMacLength);
// TODO add IsEqualTo overload that takes left+right index and number of bytes to compare;
// TODO that way we can eliminate the extrate allocation of the Take above
if (!serverHash.IsEqualTo(clientHash))
{
throw new SshConnectionException("MAC error", DisconnectReason.MacError);
}
}
if (_serverDecompression != null)
{
data = _serverDecompression.Decompress(data, messagePayloadOffset, messagePayloadLength);
// data now only contains the decompressed payload, and as such the offset is reset to zero
messagePayloadOffset = 0;
// the length of the payload is now the complete decompressed content
messagePayloadLength = data.Length;
}
_inboundPacketSequence++;
return LoadMessage(data, messagePayloadOffset, messagePayloadLength);
}
private void TrySendDisconnect(DisconnectReason reasonCode, string message)
{
var disconnectMessage = new DisconnectMessage(reasonCode, message);
// send the disconnect message, but ignore the outcome
TrySendMessage(disconnectMessage);
// mark disconnect message sent regardless of whether the send sctually succeeded
_isDisconnectMessageSent = true;
}
#region Handle received message events
/// <summary>
/// Called when <see cref="DisconnectMessage"/> received.
/// </summary>
/// <param name="message"><see cref="DisconnectMessage"/> message.</param>
internal void OnDisconnectReceived(DisconnectMessage message)
{
DiagnosticAbstraction.Log(string.Format("[{0}] Disconnect received: {1} {2}.", ToHex(SessionId), message.ReasonCode, message.Description));
// transition to disconnecting state to avoid throwing exceptions while cleaning up, and to
// ensure any exceptions that are raised do not overwrite the SshConnectionException that we
// set below
_isDisconnecting = true;
_exception = new SshConnectionException(string.Format(CultureInfo.InvariantCulture, "The connection was closed by the server: {0} ({1}).", message.Description, message.ReasonCode), message.ReasonCode);
_exceptionWaitHandle.Set();
var disconnectReceived = DisconnectReceived;
if (disconnectReceived != null)
disconnectReceived(this, new MessageEventArgs<DisconnectMessage>(message));
var disconnected = Disconnected;
if (disconnected != null)
disconnected(this, new EventArgs());
// disconnect socket, and dispose it
SocketDisconnectAndDispose();
}
/// <summary>
/// Called when <see cref="IgnoreMessage"/> received.
/// </summary>
/// <param name="message"><see cref="IgnoreMessage"/> message.</param>
internal void OnIgnoreReceived(IgnoreMessage message)
{
var handlers = IgnoreReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<IgnoreMessage>(message));
}
/// <summary>
/// Called when <see cref="UnimplementedMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="UnimplementedMessage"/> message.</param>
internal void OnUnimplementedReceived(UnimplementedMessage message)
{
var handlers = UnimplementedReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<UnimplementedMessage>(message));
}
/// <summary>
/// Called when <see cref="DebugMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="DebugMessage"/> message.</param>
internal void OnDebugReceived(DebugMessage message)
{
var handlers = DebugReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<DebugMessage>(message));
}
/// <summary>
/// Called when <see cref="ServiceRequestMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ServiceRequestMessage"/> message.</param>
internal void OnServiceRequestReceived(ServiceRequestMessage message)
{
var handlers = ServiceRequestReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ServiceRequestMessage>(message));
}
/// <summary>
/// Called when <see cref="ServiceAcceptMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ServiceAcceptMessage"/> message.</param>
internal void OnServiceAcceptReceived(ServiceAcceptMessage message)
{
var handlers = ServiceAcceptReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ServiceAcceptMessage>(message));
_serviceAccepted.Set();
}
internal void OnKeyExchangeDhGroupExchangeGroupReceived(KeyExchangeDhGroupExchangeGroup message)
{
var handlers = KeyExchangeDhGroupExchangeGroupReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<KeyExchangeDhGroupExchangeGroup>(message));
}
internal void OnKeyExchangeDhGroupExchangeReplyReceived(KeyExchangeDhGroupExchangeReply message)
{
var handlers = KeyExchangeDhGroupExchangeReplyReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<KeyExchangeDhGroupExchangeReply>(message));
}
/// <summary>
/// Called when <see cref="KeyExchangeInitMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="KeyExchangeInitMessage"/> message.</param>
internal void OnKeyExchangeInitReceived(KeyExchangeInitMessage message)
{
_keyExchangeInProgress = true;
_keyExchangeCompletedWaitHandle.Reset();
// Disable messages that are not key exchange related
_sshMessageFactory.DisableNonKeyExchangeMessages();
_keyExchange = _serviceFactory.CreateKeyExchange(ConnectionInfo.KeyExchangeAlgorithms,
message.KeyExchangeAlgorithms);
ConnectionInfo.CurrentKeyExchangeAlgorithm = _keyExchange.Name;
_keyExchange.HostKeyReceived += KeyExchange_HostKeyReceived;
// Start the algorithm implementation
_keyExchange.Start(this, message);
var keyExchangeInitReceived = KeyExchangeInitReceived;
if (keyExchangeInitReceived != null)
keyExchangeInitReceived(this, new MessageEventArgs<KeyExchangeInitMessage>(message));
}
internal void OnKeyExchangeDhReplyMessageReceived(KeyExchangeDhReplyMessage message)
{
var handlers = KeyExchangeDhReplyMessageReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<KeyExchangeDhReplyMessage>(message));
}
/// <summary>
/// Called when <see cref="NewKeysMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="NewKeysMessage"/> message.</param>
internal void OnNewKeysReceived(NewKeysMessage message)
{
// Update sessionId
if (SessionId == null)
{
SessionId = _keyExchange.ExchangeHash;
}
// Dispose of old ciphers and hash algorithms
if (_serverMac != null)
{
_serverMac.Dispose();
_serverMac = null;
}
if (_clientMac != null)
{
_clientMac.Dispose();
_clientMac = null;
}
// Update negotiated algorithms
_serverCipher = _keyExchange.CreateServerCipher();
_clientCipher = _keyExchange.CreateClientCipher();
_serverMac = _keyExchange.CreateServerHash();
_clientMac = _keyExchange.CreateClientHash();
_clientCompression = _keyExchange.CreateCompressor();
_serverDecompression = _keyExchange.CreateDecompressor();
// Dispose of old KeyExchange object as it is no longer needed.
if (_keyExchange != null)
{
_keyExchange.HostKeyReceived -= KeyExchange_HostKeyReceived;
_keyExchange.Dispose();
_keyExchange = null;
}
// Enable activated messages that are not key exchange related
_sshMessageFactory.EnableActivatedMessages();
var newKeysReceived = NewKeysReceived;
if (newKeysReceived != null)
newKeysReceived(this, new MessageEventArgs<NewKeysMessage>(message));
// Signal that key exchange completed
_keyExchangeCompletedWaitHandle.Set();
_keyExchangeInProgress = false;
}
/// <summary>
/// Called when client is disconnecting from the server.
/// </summary>
void ISession.OnDisconnecting()
{
_isDisconnecting = true;
}
/// <summary>
/// Called when <see cref="RequestMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="RequestMessage"/> message.</param>
internal void OnUserAuthenticationRequestReceived(RequestMessage message)
{
var handlers = UserAuthenticationRequestReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<RequestMessage>(message));
}
/// <summary>
/// Called when <see cref="FailureMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="FailureMessage"/> message.</param>
internal void OnUserAuthenticationFailureReceived(FailureMessage message)
{
var handlers = UserAuthenticationFailureReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<FailureMessage>(message));
}
/// <summary>
/// Called when <see cref="SuccessMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="SuccessMessage"/> message.</param>
internal void OnUserAuthenticationSuccessReceived(SuccessMessage message)
{
var handlers = UserAuthenticationSuccessReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<SuccessMessage>(message));
}
/// <summary>
/// Called when <see cref="BannerMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="BannerMessage"/> message.</param>
internal void OnUserAuthenticationBannerReceived(BannerMessage message)
{
var handlers = UserAuthenticationBannerReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<BannerMessage>(message));
}
/// <summary>
/// Called when <see cref="InformationRequestMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="InformationRequestMessage"/> message.</param>
internal void OnUserAuthenticationInformationRequestReceived(InformationRequestMessage message)
{
var handlers = UserAuthenticationInformationRequestReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<InformationRequestMessage>(message));
}
internal void OnUserAuthenticationPasswordChangeRequiredReceived(PasswordChangeRequiredMessage message)
{
var handlers = UserAuthenticationPasswordChangeRequiredReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<PasswordChangeRequiredMessage>(message));
}
internal void OnUserAuthenticationPublicKeyReceived(PublicKeyMessage message)
{
var handlers = UserAuthenticationPublicKeyReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<PublicKeyMessage>(message));
}
/// <summary>
/// Called when <see cref="GlobalRequestMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="GlobalRequestMessage"/> message.</param>
internal void OnGlobalRequestReceived(GlobalRequestMessage message)
{
var handlers = GlobalRequestReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<GlobalRequestMessage>(message));
}
/// <summary>
/// Called when <see cref="RequestSuccessMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="RequestSuccessMessage"/> message.</param>
internal void OnRequestSuccessReceived(RequestSuccessMessage message)
{
var handlers = RequestSuccessReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<RequestSuccessMessage>(message));
}
/// <summary>
/// Called when <see cref="RequestFailureMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="RequestFailureMessage"/> message.</param>
internal void OnRequestFailureReceived(RequestFailureMessage message)
{
var handlers = RequestFailureReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<RequestFailureMessage>(message));
}
/// <summary>
/// Called when <see cref="ChannelOpenMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ChannelOpenMessage"/> message.</param>
internal void OnChannelOpenReceived(ChannelOpenMessage message)
{
var handlers = ChannelOpenReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ChannelOpenMessage>(message));
}
/// <summary>
/// Called when <see cref="ChannelOpenConfirmationMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ChannelOpenConfirmationMessage"/> message.</param>
internal void OnChannelOpenConfirmationReceived(ChannelOpenConfirmationMessage message)
{
var handlers = ChannelOpenConfirmationReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ChannelOpenConfirmationMessage>(message));
}
/// <summary>
/// Called when <see cref="ChannelOpenFailureMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ChannelOpenFailureMessage"/> message.</param>
internal void OnChannelOpenFailureReceived(ChannelOpenFailureMessage message)
{
var handlers = ChannelOpenFailureReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ChannelOpenFailureMessage>(message));
}
/// <summary>
/// Called when <see cref="ChannelWindowAdjustMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ChannelWindowAdjustMessage"/> message.</param>
internal void OnChannelWindowAdjustReceived(ChannelWindowAdjustMessage message)
{
var handlers = ChannelWindowAdjustReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ChannelWindowAdjustMessage>(message));
}
/// <summary>
/// Called when <see cref="ChannelDataMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ChannelDataMessage"/> message.</param>
internal void OnChannelDataReceived(ChannelDataMessage message)
{
var handlers = ChannelDataReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ChannelDataMessage>(message));
}
/// <summary>
/// Called when <see cref="ChannelExtendedDataMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ChannelExtendedDataMessage"/> message.</param>
internal void OnChannelExtendedDataReceived(ChannelExtendedDataMessage message)
{
var handlers = ChannelExtendedDataReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ChannelExtendedDataMessage>(message));
}
/// <summary>
/// Called when <see cref="ChannelCloseMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ChannelCloseMessage"/> message.</param>
internal void OnChannelEofReceived(ChannelEofMessage message)
{
var handlers = ChannelEofReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ChannelEofMessage>(message));
}
/// <summary>
/// Called when <see cref="ChannelCloseMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ChannelCloseMessage"/> message.</param>
internal void OnChannelCloseReceived(ChannelCloseMessage message)
{
var handlers = ChannelCloseReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ChannelCloseMessage>(message));
}
/// <summary>
/// Called when <see cref="ChannelRequestMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ChannelRequestMessage"/> message.</param>
internal void OnChannelRequestReceived(ChannelRequestMessage message)
{
var handlers = ChannelRequestReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ChannelRequestMessage>(message));
}
/// <summary>
/// Called when <see cref="ChannelSuccessMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ChannelSuccessMessage"/> message.</param>
internal void OnChannelSuccessReceived(ChannelSuccessMessage message)
{
var handlers = ChannelSuccessReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ChannelSuccessMessage>(message));
}
/// <summary>
/// Called when <see cref="ChannelFailureMessage"/> message received.
/// </summary>
/// <param name="message"><see cref="ChannelFailureMessage"/> message.</param>
internal void OnChannelFailureReceived(ChannelFailureMessage message)
{
var handlers = ChannelFailureReceived;
if (handlers != null)
handlers(this, new MessageEventArgs<ChannelFailureMessage>(message));
}
#endregion
private void KeyExchange_HostKeyReceived(object sender, HostKeyEventArgs e)
{
var handlers = HostKeyReceived;
if (handlers != null)
handlers(this, e);
}
#region Message loading functions
/// <summary>
/// Registers SSH message with the session.
/// </summary>
/// <param name="messageName">The name of the message to register with the session.</param>
public void RegisterMessage(string messageName)
{
_sshMessageFactory.EnableAndActivateMessage(messageName);
}
/// <summary>
/// Unregister SSH message from the session.
/// </summary>
/// <param name="messageName">The name of the message to unregister with the session.</param>
public void UnRegisterMessage(string messageName)
{
_sshMessageFactory.DisableAndDeactivateMessage(messageName);
}
/// <summary>
/// Loads a message from a given buffer.
/// </summary>
/// <param name="data">An array of bytes from which to construct the message.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="data"/> at which to begin reading.</param>
/// <param name="count">The number of bytes to load.</param>
/// <returns>
/// A message constructed from <paramref name="data"/>.
/// </returns>
/// <exception cref="SshException">The type of the message is not supported.</exception>
private Message LoadMessage(byte[] data, int offset, int count)
{
var messageType = data[offset];
var message = _sshMessageFactory.Create(messageType);
message.Load(data, offset + 1, count - 1);
DiagnosticAbstraction.Log(string.Format("[{0}] Received message '{1}' from server: '{2}'.", ToHex(SessionId), message.GetType().Name, message));
return message;
}
private static string ToHex(byte[] bytes, int offset)
{
var byteCount = bytes.Length - offset;
var builder = new StringBuilder(bytes.Length * 2);
for (var i = offset; i < byteCount; i++)
{
var b = bytes[i];
builder.Append(b.ToString("X2"));
}
return builder.ToString();
}
internal static string ToHex(byte[] bytes)
{
if (bytes == null)
return null;
return ToHex(bytes, 0);
}
#endregion
/// <summary>
/// Establishes a socket connection to the specified host and port.
/// </summary>
/// <param name="host">The host name of the server to connect to.</param>
/// <param name="port">The port to connect to.</param>
/// <exception cref="SshOperationTimeoutException">The connection failed to establish within the configured <see cref="Renci.SshNet.ConnectionInfo.Timeout"/>.</exception>
/// <exception cref="SocketException">An error occurred trying to establish the connection.</exception>
private void SocketConnect(string host, int port)
{
var ipAddress = DnsAbstraction.GetHostAddresses(host)[0];
var ep = new IPEndPoint(ipAddress, port);
DiagnosticAbstraction.Log(string.Format("Initiating connection to '{0}:{1}'.", host, port));
_socket = SocketAbstraction.Connect(ep, ConnectionInfo.Timeout);
const int socketBufferSize = 2 * MaximumSshPacketSize;
_socket.SendBufferSize = socketBufferSize;
_socket.ReceiveBufferSize = socketBufferSize;
}
/// <summary>
/// Performs a blocking read on the socket until <paramref name="length"/> bytes are received.
/// </summary>
/// <param name="buffer">An array of type <see cref="byte"/> that is the storage location for the received data.</param>
/// <param name="offset">The position in <paramref name="buffer"/> parameter to store the received data.</param>
/// <param name="length">The number of bytes to read.</param>
/// <returns>
/// The number of bytes read.
/// </returns>
/// <exception cref="SshConnectionException">The socket is closed.</exception>
/// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
/// <exception cref="SocketException">The read failed.</exception>
private int SocketRead(byte[] buffer, int offset, int length)
{
var bytesRead = SocketAbstraction.Read(_socket, buffer, offset, length, InfiniteTimeSpan);
if (bytesRead == 0)
{
// when we're in the disconnecting state (either triggered by client or server), then the
// SshConnectionException will interrupt the message listener loop (if not already interrupted)
// and the exception itself will be ignored (in RaiseError)
throw new SshConnectionException("An established connection was aborted by the server.",
DisconnectReason.ConnectionLost);
}
return bytesRead;
}
#if FEATURE_SOCKET_POLL
/// <summary>
/// Gets a value indicating whether the socket is connected.
/// </summary>
/// <returns>
/// <c>true</c> if the socket is connected; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// <para>
/// As a first check we verify whether <see cref="Socket.Connected"/> is
/// <c>true</c>. However, this only returns the state of the socket as of
/// the last I/O operation.
/// </para>
/// <para>
/// Therefore we use the combination of <see cref="Socket.Poll(int, SelectMode)"/> with mode <see cref="SelectMode.SelectRead"/>
/// and <see cref="Socket.Available"/> to verify if the socket is still connected.
/// </para>
/// <para>
/// The MSDN doc mention the following on the return value of <see cref="Socket.Poll(int, SelectMode)"/>
/// with mode <see cref="SelectMode.SelectRead"/>:
/// <list type="bullet">
/// <item>
/// <description><c>true</c> if data is available for reading;</description>
/// </item>
/// <item>
/// <description><c>true</c> if the connection has been closed, reset, or terminated; otherwise, returns <c>false</c>.</description>
/// </item>
/// </list>
/// </para>
/// <para>
/// <c>Conclusion:</c> when the return value is <c>true</c> - but no data is available for reading - then
/// the socket is no longer connected.
/// </para>
/// <para>
/// When a <see cref="Socket"/> is used from multiple threads, there's a race condition
/// between the invocation of <see cref="Socket.Poll(int, SelectMode)"/> and the moment
/// when the value of <see cref="Socket.Available"/> is obtained. To workaround this issue
/// we synchronize reads from the <see cref="Socket"/>.
/// </para>
/// </remarks>
#else
/// <summary>
/// Gets a value indicating whether the socket is connected.
/// </summary>
/// <returns>
/// <c>true</c> if the socket is connected; otherwise, <c>false</c>.
/// </returns>
/// <remarks>
/// We verify whether <see cref="Socket.Connected"/> is <c>true</c>. However, this only returns the state
/// of the socket as of the last I/O operation.
/// </remarks>
#endif
private bool IsSocketConnected()
{
lock (_socketDisposeLock)
{
#if FEATURE_SOCKET_POLL
if (!_socket.IsConnected())
{
return false;
}
lock (_socketReadLock)
{
var connectionClosedOrDataAvailable = _socket.Poll(0, SelectMode.SelectRead);
return !(connectionClosedOrDataAvailable && _socket.Available == 0);
}
#else
return _socket.IsConnected();
#endif // FEATURE_SOCKET_POLL
}
}
/// <summary>
/// Performs a blocking read on the socket until <paramref name="length"/> bytes are received.
/// </summary>
/// <param name="buffer">An array of type <see cref="byte"/> that is the storage location for the received data.</param>
/// <param name="offset">The position in <paramref name="buffer"/> parameter to store the received data.</param>
/// <param name="length">The number of bytes to read.</param>
/// <returns>
/// The number of bytes read.
/// </returns>
/// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
/// <exception cref="SocketException">The read failed.</exception>
private int TrySocketRead(byte[] buffer, int offset, int length)
{
return SocketAbstraction.Read(_socket, buffer, offset, length, InfiniteTimeSpan);
}
/// <summary>
/// Performs a blocking read on the socket until a line is read.
/// </summary>
/// <param name="timeout">A <see cref="TimeSpan"/> that represents the time to wait until a line is read.</param>
/// <exception cref="SshOperationTimeoutException">The read has timed-out.</exception>
/// <exception cref="SocketException">An error occurred when trying to access the socket.</exception>
/// <returns>
/// The line read from the socket, or <c>null</c> when the remote server has shutdown and all data has been received.
/// </returns>
private string SocketReadLine(TimeSpan timeout)
{
var encoding = SshData.Ascii;
var buffer = new List<byte>();
var data = new byte[1];
// read data one byte at a time to find end of line and leave any unhandled information in the buffer
// to be processed by subsequent invocations
do
{
var bytesRead = SocketAbstraction.Read(_socket, data, 0, data.Length, timeout);
if (bytesRead == 0)
// the remote server shut down the socket
break;
buffer.Add(data[0]);
}
while (!(buffer.Count > 0 && (buffer[buffer.Count - 1] == LineFeed || buffer[buffer.Count - 1] == Null)));
if (buffer.Count == 0)
return null;
if (buffer.Count == 1 && buffer[buffer.Count - 1] == 0x00)
// return an empty version string if the buffer consists of only a 0x00 character
return string.Empty;
if (buffer.Count > 1 && buffer[buffer.Count - 2] == CarriageReturn)
// strip trailing CRLF
return encoding.GetString(buffer.ToArray(), 0, buffer.Count - 2);
if (buffer.Count > 1 && buffer[buffer.Count - 1] == LineFeed)
// strip trailing LF
return encoding.GetString(buffer.ToArray(), 0, buffer.Count - 1);
return encoding.GetString(buffer.ToArray(), 0, buffer.Count);
}
/// <summary>
/// Shuts down and disposes the socket.
/// </summary>
private void SocketDisconnectAndDispose()
{
if (_socket != null)
{
lock (_socketDisposeLock)
{
if (_socket != null)
{
if (_socket.Connected)
{
try
{
DiagnosticAbstraction.Log(string.Format("[{0}] Shutting down socket.", ToHex(SessionId)));
// interrupt any pending reads; should be done outside of socket read lock as we
// actually want shutdown the socket to make sure blocking reads are interrupted
//
// this may result in a SocketException (eg. An existing connection was forcibly
// closed by the remote host) which we'll log and ignore as it means the socket
// was already shut down
_socket.Shutdown(SocketShutdown.Send);
}
catch (SocketException ex)
{
// TODO: log as warning
DiagnosticAbstraction.Log("Failure shutting down socket: " + ex);
}
}
DiagnosticAbstraction.Log(string.Format("[{0}] Disposing socket.", ToHex(SessionId)));
_socket.Dispose();
DiagnosticAbstraction.Log(string.Format("[{0}] Disposed socket.", ToHex(SessionId)));
_socket = null;
}
}
}
}
/// <summary>
/// Listens for incoming message from the server and handles them. This method run as a task on separate thread.
/// </summary>
private void MessageListener()
{
#if FEATURE_SOCKET_SELECT
var readSockets = new List<Socket> { _socket };
#endif // FEATURE_SOCKET_SELECT
try
{
// remain in message loop until socket is shut down or until we're disconnecting
while (_socket.IsConnected())
{
#if FEATURE_SOCKET_SELECT
// if the socket is already disposed when Select is invoked, then a SocketException
// stating "An operation was attempted on something that is not a socket" is thrown;
// we attempt to avoid this exception by having an IsConnected() that can break the
// message loop
//
// note that there's no guarantee that the socket will not be disposed between the
// IsConnected() check and the Select invocation; we can't take a "dispose" lock
// that includes the Select invocation as we want Dispose() to be able to interrupt
// the Select
// perform a blocking select to determine whether there's is data available to be
// read; we do not use a blocking read to allow us to use Socket.Poll to determine
// if the connection is still available (in IsSocketConnected)
Socket.Select(readSockets, null, null, -1);
// the Select invocation will be interrupted in one of the following conditions:
// * data is available to be read
// => the socket will not be removed from "readSockets"
// * the socket connection is closed during the Select invocation
// => the socket will be removed from "readSockets"
// * the socket is disposed during the Select invocation
// => the socket will not be removed from "readSocket"
//
// since we handle the second and third condition the same way and Socket.Connected
// allows us to check for both conditions, we use that instead of both checking for
// the removal from "readSockets" and the Connection check
if (!_socket.IsConnected())
{
// connection with SSH server was closed or socket was disposed;
// break out of the message loop
break;
}
#elif FEATURE_SOCKET_POLL
// when Socket.Select(IList,IList,IList,Int32) is not available or is buggy, we use
// Socket.Poll(Int, SelectMode) to block until either data is available or the socket
// is closed
_socket.Poll(-1, SelectMode.SelectRead);
if (!_socket.IsConnected())
{
// connection with SSH server was closed or socket was disposed;
// break out of the message loop
break;
}
#endif // FEATURE_SOCKET_SELECT
var message = ReceiveMessage();
if (message == null)
{
// connection with SSH server was closed;
// break out of the message loop
break;
}
// process message
message.Process(this);
}
// connection with SSH server was closed or socket was disposed
RaiseError(CreateConnectionAbortedByServerException());
}
catch (SocketException ex)
{
RaiseError(new SshConnectionException(ex.Message, DisconnectReason.ConnectionLost, ex));
}
catch (Exception exp)
{
RaiseError(exp);
}
finally
{
// signal that the message listener thread has stopped
_messageListenerCompleted.Set();
}
}
private byte SocketReadByte()
{
var buffer = new byte[1];
SocketRead(buffer, 0, 1);
return buffer[0];
}
private void SocketWriteByte(byte data)
{
SocketAbstraction.Send(_socket, new[] {data});
}
private void ConnectSocks4()
{
// Send socks version number
SocketWriteByte(0x04);
// Send command code
SocketWriteByte(0x01);
// Send port
SocketWriteByte((byte)(ConnectionInfo.Port / 0xFF));
SocketWriteByte((byte)(ConnectionInfo.Port % 0xFF));
// Send IP
var ipAddress = DnsAbstraction.GetHostAddresses(ConnectionInfo.Host)[0];
SocketAbstraction.Send(_socket, ipAddress.GetAddressBytes());
// Send username
var username = SshData.Ascii.GetBytes(ConnectionInfo.ProxyUsername);
SocketAbstraction.Send(_socket, username);
SocketWriteByte(0x00);
// Read 0
if (SocketReadByte() != 0)
{
throw new ProxyException("SOCKS4: Null is expected.");
}
// Read response code
var code = SocketReadByte();
switch (code)
{
case 0x5a:
break;
case 0x5b:
throw new ProxyException("SOCKS4: Connection rejected.");
case 0x5c:
throw new ProxyException("SOCKS4: Client is not running identd or not reachable from the server.");
case 0x5d:
throw new ProxyException("SOCKS4: Client's identd could not confirm the user ID string in the request.");
default:
throw new ProxyException("SOCKS4: Not valid response.");
}
var dummyBuffer = new byte[6]; // field 3 (2 bytes) and field 4 (4) should be ignored
SocketRead(dummyBuffer, 0, 6);
}
private void ConnectSocks5()
{
// Send socks version number
SocketWriteByte(0x05);
// Send number of supported authentication methods
SocketWriteByte(0x02);
// Send supported authentication methods
SocketWriteByte(0x00); // No authentication
SocketWriteByte(0x02); // Username/Password
var socksVersion = SocketReadByte();
if (socksVersion != 0x05)
throw new ProxyException(string.Format("SOCKS Version '{0}' is not supported.", socksVersion));
var authenticationMethod = SocketReadByte();
switch (authenticationMethod)
{
case 0x00:
break;
case 0x02:
// Send version
SocketWriteByte(0x01);
var username = SshData.Ascii.GetBytes(ConnectionInfo.ProxyUsername);
if (username.Length > byte.MaxValue)
throw new ProxyException("Proxy username is too long.");
// Send username length
SocketWriteByte((byte)username.Length);
// Send username
SocketAbstraction.Send(_socket, username);
var password = SshData.Ascii.GetBytes(ConnectionInfo.ProxyPassword);
if (password.Length > byte.MaxValue)
throw new ProxyException("Proxy password is too long.");
// Send username length
SocketWriteByte((byte)password.Length);
// Send username
SocketAbstraction.Send(_socket, password);
var serverVersion = SocketReadByte();
if (serverVersion != 1)
throw new ProxyException("SOCKS5: Server authentication version is not valid.");
var statusCode = SocketReadByte();
if (statusCode != 0)
throw new ProxyException("SOCKS5: Username/Password authentication failed.");
break;
case 0xFF:
throw new ProxyException("SOCKS5: No acceptable authentication methods were offered.");
}
// Send socks version number
SocketWriteByte(0x05);
// Send command code
SocketWriteByte(0x01); // establish a TCP/IP stream connection
// Send reserved, must be 0x00
SocketWriteByte(0x00);
var ip = DnsAbstraction.GetHostAddresses(ConnectionInfo.Host)[0];
// Send address type and address
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
SocketWriteByte(0x01);
var address = ip.GetAddressBytes();
SocketAbstraction.Send(_socket, address);
}
else if (ip.AddressFamily == AddressFamily.InterNetworkV6)
{
SocketWriteByte(0x04);
var address = ip.GetAddressBytes();
SocketAbstraction.Send(_socket, address);
}
else
{
throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", ip));
}
// Send port
SocketWriteByte((byte)(ConnectionInfo.Port / 0xFF));
SocketWriteByte((byte)(ConnectionInfo.Port % 0xFF));
// Read Server SOCKS5 version
if (SocketReadByte() != 5)
{
throw new ProxyException("SOCKS5: Version 5 is expected.");
}
// Read response code
var status = SocketReadByte();
switch (status)
{
case 0x00:
break;
case 0x01:
throw new ProxyException("SOCKS5: General failure.");
case 0x02:
throw new ProxyException("SOCKS5: Connection not allowed by ruleset.");
case 0x03:
throw new ProxyException("SOCKS5: Network unreachable.");
case 0x04:
throw new ProxyException("SOCKS5: Host unreachable.");
case 0x05:
throw new ProxyException("SOCKS5: Connection refused by destination host.");
case 0x06:
throw new ProxyException("SOCKS5: TTL expired.");
case 0x07:
throw new ProxyException("SOCKS5: Command not supported or protocol error.");
case 0x08:
throw new ProxyException("SOCKS5: Address type not supported.");
default:
throw new ProxyException("SOCKS5: Not valid response.");
}
// Read 0
if (SocketReadByte() != 0)
{
throw new ProxyException("SOCKS5: 0 byte is expected.");
}
var addressType = SocketReadByte();
var responseIp = new byte[16];
switch (addressType)
{
case 0x01:
SocketRead(responseIp, 0, 4);
break;
case 0x04:
SocketRead(responseIp, 0, 16);
break;
default:
throw new ProxyException(string.Format("Address type '{0}' is not supported.", addressType));
}
var port = new byte[2];
// Read 2 bytes to be ignored
SocketRead(port, 0, 2);
}
private void ConnectHttp()
{
var httpResponseRe = new Regex(@"HTTP/(?<version>\d[.]\d) (?<statusCode>\d{3}) (?<reasonPhrase>.+)$");
var httpHeaderRe = new Regex(@"(?<fieldName>[^\[\]()<>@,;:\""/?={} \t]+):(?<fieldValue>.+)?");
SocketAbstraction.Send(_socket, SshData.Ascii.GetBytes(string.Format("CONNECT {0}:{1} HTTP/1.0\r\n", ConnectionInfo.Host, ConnectionInfo.Port)));
// Sent proxy authorization is specified
if (!string.IsNullOrEmpty(ConnectionInfo.ProxyUsername))
{
var authorization = string.Format("Proxy-Authorization: Basic {0}\r\n",
Convert.ToBase64String(SshData.Ascii.GetBytes(string.Format("{0}:{1}", ConnectionInfo.ProxyUsername, ConnectionInfo.ProxyPassword)))
);
SocketAbstraction.Send(_socket, SshData.Ascii.GetBytes(authorization));
}
SocketAbstraction.Send(_socket, SshData.Ascii.GetBytes("\r\n"));
HttpStatusCode? statusCode = null;
var contentLength = 0;
while (true)
{
var response = SocketReadLine(ConnectionInfo.Timeout);
if (response == null)
// server shut down socket
break;
if (statusCode == null)
{
var statusMatch = httpResponseRe.Match(response);
if (statusMatch.Success)
{
var httpStatusCode = statusMatch.Result("${statusCode}");
statusCode = (HttpStatusCode) int.Parse(httpStatusCode);
if (statusCode != HttpStatusCode.OK)
{
var reasonPhrase = statusMatch.Result("${reasonPhrase}");
throw new ProxyException(string.Format("HTTP: Status code {0}, \"{1}\"", httpStatusCode,
reasonPhrase));
}
}
continue;
}
// continue on parsing message headers coming from the server
var headerMatch = httpHeaderRe.Match(response);
if (headerMatch.Success)
{
var fieldName = headerMatch.Result("${fieldName}");
if (fieldName.Equals("Content-Length", StringComparison.OrdinalIgnoreCase))
{
contentLength = int.Parse(headerMatch.Result("${fieldValue}"));
}
continue;
}
// check if we've reached the CRLF which separates request line and headers from the message body
if (response.Length == 0)
{
// read response body if specified
if (contentLength > 0)
{
var contentBody = new byte[contentLength];
SocketRead(contentBody, 0, contentLength);
}
break;
}
}
if (statusCode == null)
throw new ProxyException("HTTP response does not contain status line.");
}
/// <summary>
/// Raises the <see cref="ErrorOccured"/> event.
/// </summary>
/// <param name="exp">The <see cref="Exception"/>.</param>
private void RaiseError(Exception exp)
{
var connectionException = exp as SshConnectionException;
DiagnosticAbstraction.Log(string.Format("[{0}] Raised exception: {1}", ToHex(SessionId), exp));
if (_isDisconnecting)
{
// a connection exception which is raised while isDisconnecting is normal and
// should be ignored
if (connectionException != null)
return;
// any timeout while disconnecting can be caused by loss of connectivity
// altogether and should be ignored
var socketException = exp as SocketException;
if (socketException != null && socketException.SocketErrorCode == SocketError.TimedOut)
return;
}
// "save" exception and set exception wait handle to ensure any waits are interrupted
_exception = exp;
_exceptionWaitHandle.Set();
var errorOccured = ErrorOccured;
if (errorOccured != null)
errorOccured(this, new ExceptionEventArgs(exp));
if (connectionException != null)
{
DiagnosticAbstraction.Log(string.Format("[{0}] Disconnecting after exception: {1}", ToHex(SessionId), exp));
Disconnect(connectionException.DisconnectReason, exp.ToString());
}
}
/// <summary>
/// Resets connection-specific information to ensure state of a previous connection
/// does not affect new connections.
/// </summary>
private void Reset()
{
if (_exceptionWaitHandle != null)
_exceptionWaitHandle.Reset();
if (_keyExchangeCompletedWaitHandle != null)
_keyExchangeCompletedWaitHandle.Reset();
if (_messageListenerCompleted != null)
_messageListenerCompleted.Set();
SessionId = null;
_isDisconnectMessageSent = false;
_isDisconnecting = false;
_isAuthenticated = false;
_exception = null;
_keyExchangeInProgress = false;
}
private static SshConnectionException CreateConnectionAbortedByServerException()
{
return new SshConnectionException("An established connection was aborted by the server.",
DisconnectReason.ConnectionLost);
}
#region IDisposable implementation
private bool _disposed;
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
DiagnosticAbstraction.Log(string.Format("[{0}] Disposing session.", ToHex(SessionId)));
Disconnect();
var serviceAccepted = _serviceAccepted;
if (serviceAccepted != null)
{
serviceAccepted.Dispose();
_serviceAccepted = null;
}
var exceptionWaitHandle = _exceptionWaitHandle;
if (exceptionWaitHandle != null)
{
exceptionWaitHandle.Dispose();
_exceptionWaitHandle = null;
}
var keyExchangeCompletedWaitHandle = _keyExchangeCompletedWaitHandle;
if (keyExchangeCompletedWaitHandle != null)
{
keyExchangeCompletedWaitHandle.Dispose();
_keyExchangeCompletedWaitHandle = null;
}
var serverMac = _serverMac;
if (serverMac != null)
{
serverMac.Dispose();
_serverMac = null;
}
var clientMac = _clientMac;
if (clientMac != null)
{
clientMac.Dispose();
_clientMac = null;
}
var keyExchange = _keyExchange;
if (keyExchange != null)
{
keyExchange.HostKeyReceived -= KeyExchange_HostKeyReceived;
keyExchange.Dispose();
_keyExchange = null;
}
var messageListenerCompleted = _messageListenerCompleted;
if (messageListenerCompleted != null)
{
messageListenerCompleted.Dispose();
_messageListenerCompleted = null;
}
_disposed = true;
}
}
/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="Session"/> is reclaimed by garbage collection.
/// </summary>
~Session()
{
Dispose(false);
}
#endregion IDisposable implementation
#region ISession implementation
/// <summary>
/// Gets or sets the connection info.
/// </summary>
/// <value>The connection info.</value>
IConnectionInfo ISession.ConnectionInfo
{
get { return ConnectionInfo; }
}
WaitHandle ISession.MessageListenerCompleted
{
get { return _messageListenerCompleted; }
}
/// <summary>
/// Create a new SSH session channel.
/// </summary>
/// <returns>
/// A new SSH session channel.
/// </returns>
IChannelSession ISession.CreateChannelSession()
{
return new ChannelSession(this, NextChannelNumber, InitialLocalWindowSize, LocalChannelDataPacketSize);
}
/// <summary>
/// Create a new channel for a locally forwarded TCP/IP port.
/// </summary>
/// <returns>
/// A new channel for a locally forwarded TCP/IP port.
/// </returns>
IChannelDirectTcpip ISession.CreateChannelDirectTcpip()
{
return new ChannelDirectTcpip(this, NextChannelNumber, InitialLocalWindowSize, LocalChannelDataPacketSize);
}
/// <summary>
/// Creates a "forwarded-tcpip" SSH channel.
/// </summary>
/// <returns>
/// A new "forwarded-tcpip" SSH channel.
/// </returns>
IChannelForwardedTcpip ISession.CreateChannelForwardedTcpip(uint remoteChannelNumber, uint remoteWindowSize,
uint remoteChannelDataPacketSize)
{
return new ChannelForwardedTcpip(this,
NextChannelNumber,
InitialLocalWindowSize,
LocalChannelDataPacketSize,
remoteChannelNumber,
remoteWindowSize,
remoteChannelDataPacketSize);
}
/// <summary>
/// Sends a message to the server.
/// </summary>
/// <param name="message">The message to send.</param>
/// <exception cref="SshConnectionException">The client is not connected.</exception>
/// <exception cref="SshOperationTimeoutException">The operation timed out.</exception>
/// <exception cref="InvalidOperationException">The size of the packet exceeds the maximum size defined by the protocol.</exception>
void ISession.SendMessage(Message message)
{
SendMessage(message);
}
/// <summary>
/// Sends a message to the server.
/// </summary>
/// <param name="message">The message to send.</param>
/// <returns>
/// <c>true</c> if the message was sent to the server; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="InvalidOperationException">The size of the packet exceeds the maximum size defined by the protocol.</exception>
/// <remarks>
/// This methods returns <c>false</c> when the attempt to send the message results in a
/// <see cref="SocketException"/> or a <see cref="SshException"/>.
/// </remarks>
bool ISession.TrySendMessage(Message message)
{
return TrySendMessage(message);
}
#endregion ISession implementation
}
}