using System; using System.Globalization; using System.Linq; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using Renci.SshNet.Abstractions; using Renci.SshNet.Channels; using Renci.SshNet.Common; using Renci.SshNet.Compression; using Renci.SshNet.Connection; using Renci.SshNet.Messages; using Renci.SshNet.Messages.Authentication; using Renci.SshNet.Messages.Connection; using Renci.SshNet.Messages.Transport; using Renci.SshNet.Security; using Renci.SshNet.Security.Cryptography; namespace Renci.SshNet { /// /// Provides functionality to connect and interact with SSH server. /// public class Session : ISession { internal const byte CarriageReturn = 0x0d; internal const byte LineFeed = 0x0a; /// /// Specifies an infinite waiting period. /// /// /// The value of this field is -1. /// internal const int Infinite = -1; /// /// Specifies maximum packet size defined by the protocol. /// /// /// 68536 (64 KB + 3000 bytes). /// internal const int MaximumSshPacketSize = LocalChannelDataPacketSize + 3000; /// /// Holds the initial local window size for the channels. /// /// /// 2147483647 (2^31 - 1) bytes. /// /// /// We currently do not define a maximum (remote) window size. /// private const int InitialLocalWindowSize = 0x7FFFFFFF; /// /// Holds the maximum size of channel data packets that we receive. /// /// /// 64 KB. /// /// /// /// This is the maximum size (in bytes) we support for the data (payload) of a /// SSH_MSG_CHANNEL_DATA message we receive. /// /// /// We currently do not enforce this limit. /// /// private const int LocalChannelDataPacketSize = 1024 * 64; /// /// Specifies an infinite waiting period. /// /// /// The value of this field is -1 millisecond. /// internal static readonly TimeSpan InfiniteTimeSpan = new TimeSpan(0, 0, 0, 0, -1); /// /// Controls how many authentication attempts can take place at the same time. /// /// /// Some server may restrict number to prevent authentication attacks. /// private static readonly SemaphoreLight AuthenticationConnection = new SemaphoreLight(3); /// /// Holds the factory to use for creating new services. /// private readonly IServiceFactory _serviceFactory; private readonly ISocketFactory _socketFactory; /// /// Holds an object that is used to ensure only a single thread can read from /// at any given time. /// private readonly object _socketReadLock = new object(); /// /// Holds an object that is used to ensure only a single thread can write to /// at any given time. /// /// /// This is also used to ensure that is /// incremented atomatically. /// private readonly object _socketWriteLock = new object(); /// /// Holds an object that is used to ensure only a single thread can dispose /// at any given time. /// /// /// This is also used to ensure that will not be disposed /// while performing a given operation or set of operations on . /// private readonly object _socketDisposeLock = new object(); /// /// Holds metadata about session messages. /// private SshMessageFactory _sshMessageFactory; /// /// Holds a that is signaled when the message listener loop has completed. /// private ManualResetEvent _messageListenerCompleted; /// /// Specifies outbound packet number. /// private volatile uint _outboundPacketSequence; /// /// Specifies incoming packet number. /// private uint _inboundPacketSequence; /// /// WaitHandle to signal that last service request was accepted. /// private EventWaitHandle _serviceAccepted = new AutoResetEvent(initialState: false); /// /// WaitHandle to signal that exception was thrown by another thread. /// private EventWaitHandle _exceptionWaitHandle = new ManualResetEvent(initialState: false); /// /// WaitHandle to signal that key exchange was completed. /// private EventWaitHandle _keyExchangeCompletedWaitHandle = new ManualResetEvent(initialState: false); /// /// WaitHandle to signal that key exchange is in progress. /// private bool _keyExchangeInProgress; /// /// Exception that need to be thrown by waiting thread. /// private Exception _exception; /// /// Specifies whether connection is authenticated. /// private bool _isAuthenticated; /// /// Specifies whether user issued Disconnect command or not. /// 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; /// /// Holds connection socket. /// private Socket _socket; /// /// Gets the session semaphore that controls session channels. /// /// /// The session semaphore. /// public SemaphoreLight SessionSemaphore { get { if (_sessionSemaphore is null) { lock (this) { _sessionSemaphore ??= new SemaphoreLight(ConnectionInfo.MaxSessions); } } return _sessionSemaphore; } } private bool _isDisconnectMessageSent; private uint _nextChannelNumber; /// /// Gets the next channel number. /// /// /// The next channel number. /// private uint NextChannelNumber { get { uint result; lock (this) { result = _nextChannelNumber++; } return result; } } /// /// Gets a value indicating whether the session is connected. /// /// /// true if the session is connected; otherwise, false. /// /// /// This methods returns true in all but the following cases: /// /// /// The is disposed. /// /// /// The SSH_MSG_DISCONNECT message - which is used to disconnect from the server - has been sent. /// /// /// The client has not been authenticated successfully. /// /// /// The listener thread - which is used to receive messages from the server - has stopped. /// /// /// The socket used to communicate with the server is no longer connected. /// /// /// public bool IsConnected { get { if (_disposed || _isDisconnectMessageSent || !_isAuthenticated) { return false; } if (_messageListenerCompleted is null || _messageListenerCompleted.WaitOne(0)) { return false; } return IsSocketConnected(); } } /// /// Gets the session id. /// /// /// The session id, or null if the client has not been authenticated. /// public byte[] SessionId { get; private set; } private Message _clientInitMessage; /// /// Gets the client init message. /// /// The client init message. public Message ClientInitMessage { get { _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; } } /// /// Gets the server version string. /// /// /// The server version. /// public string ServerVersion { get; private set; } /// /// Gets the client version string. /// /// /// The client version. /// public string ClientVersion { get; private set; } /// /// Gets the connection info. /// /// /// The connection info. /// public ConnectionInfo ConnectionInfo { get; private set; } /// /// Occurs when an error occurred. /// public event EventHandler ErrorOccured; /// /// Occurs when session has been disconnected from the server. /// public event EventHandler Disconnected; /// /// Occurs when host key received. /// public event EventHandler HostKeyReceived; /// /// Occurs when message is received from the server. /// public event EventHandler> UserAuthenticationBannerReceived; /// /// Occurs when message is received from the server. /// internal event EventHandler> UserAuthenticationInformationRequestReceived; /// /// Occurs when message is received from the server. /// internal event EventHandler> UserAuthenticationPasswordChangeRequiredReceived; /// /// Occurs when message is received from the server. /// internal event EventHandler> UserAuthenticationPublicKeyReceived; /// /// Occurs when message is received from the server. /// internal event EventHandler> KeyExchangeDhGroupExchangeGroupReceived; /// /// Occurs when message is received from the server. /// internal event EventHandler> KeyExchangeDhGroupExchangeReplyReceived; /// /// Occurs when message received /// internal event EventHandler> DisconnectReceived; /// /// Occurs when message received /// internal event EventHandler> IgnoreReceived; /// /// Occurs when message received /// internal event EventHandler> UnimplementedReceived; /// /// Occurs when message received /// internal event EventHandler> DebugReceived; /// /// Occurs when message received /// internal event EventHandler> ServiceRequestReceived; /// /// Occurs when message received /// internal event EventHandler> ServiceAcceptReceived; /// /// Occurs when message received /// internal event EventHandler> KeyExchangeInitReceived; /// /// Occurs when a message is received from the SSH server. /// internal event EventHandler> KeyExchangeDhReplyMessageReceived; /// /// Occurs when a message is received from the SSH server. /// internal event EventHandler> KeyExchangeEcdhReplyMessageReceived; /// /// Occurs when message received /// internal event EventHandler> NewKeysReceived; /// /// Occurs when message received /// internal event EventHandler> UserAuthenticationRequestReceived; /// /// Occurs when message received /// internal event EventHandler> UserAuthenticationFailureReceived; /// /// Occurs when message received /// internal event EventHandler> UserAuthenticationSuccessReceived; /// /// Occurs when message received /// internal event EventHandler> GlobalRequestReceived; /// /// Occurs when message received /// public event EventHandler> RequestSuccessReceived; /// /// Occurs when message received /// public event EventHandler> RequestFailureReceived; /// /// Occurs when message received /// public event EventHandler> ChannelOpenReceived; /// /// Occurs when message received /// public event EventHandler> ChannelOpenConfirmationReceived; /// /// Occurs when message received /// public event EventHandler> ChannelOpenFailureReceived; /// /// Occurs when message received /// public event EventHandler> ChannelWindowAdjustReceived; /// /// Occurs when message received /// public event EventHandler> ChannelDataReceived; /// /// Occurs when message received /// public event EventHandler> ChannelExtendedDataReceived; /// /// Occurs when message received /// public event EventHandler> ChannelEofReceived; /// /// Occurs when message received /// public event EventHandler> ChannelCloseReceived; /// /// Occurs when message received /// public event EventHandler> ChannelRequestReceived; /// /// Occurs when message received /// public event EventHandler> ChannelSuccessReceived; /// /// Occurs when message received /// public event EventHandler> ChannelFailureReceived; /// /// Initializes a new instance of the class. /// /// The connection info. /// The factory to use for creating new services. /// A factory to create instances. /// is null. /// is null. /// is null. internal Session(ConnectionInfo connectionInfo, IServiceFactory serviceFactory, ISocketFactory socketFactory) { if (connectionInfo is null) { throw new ArgumentNullException(nameof(connectionInfo)); } if (serviceFactory is null) { throw new ArgumentNullException(nameof(serviceFactory)); } if (socketFactory is null) { throw new ArgumentNullException(nameof(socketFactory)); } ClientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1"; ConnectionInfo = connectionInfo; _serviceFactory = serviceFactory; _socketFactory = socketFactory; _messageListenerCompleted = new ManualResetEvent(initialState: true); } /// /// Connects to the server. /// /// Socket connection to the SSH server or proxy server could not be established, or an error occurred while resolving the hostname. /// SSH session could not be established. /// Authentication of SSH session failed. /// Failed to establish proxy connection. 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(); _socket = _serviceFactory.CreateConnector(ConnectionInfo, _socketFactory) .Connect(ConnectionInfo); var serverIdentification = _serviceFactory.CreateProtocolVersionExchange() .Start(ClientVersion, _socket, ConnectionInfo.Timeout); // Set connection versions ServerVersion = ConnectionInfo.ServerVersion = serverIdentification.ToString(); ConnectionInfo.ClientVersion = ClientVersion; DiagnosticAbstraction.Log(string.Format("Server version '{0}' on '{1}'.", serverIdentification.ProtocolVersion, serverIdentification.SoftwareVersion)); if (!(serverIdentification.ProtocolVersion.Equals("2.0") || serverIdentification.ProtocolVersion.Equals("1.99"))) { throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Server version '{0}' is not supported.", serverIdentification.ProtocolVersion), DisconnectReason.ProtocolVersionNotSupported); } // 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 to establishing encryption algorithm RegisterMessage("SSH_MSG_USERAUTH_BANNER"); // Mark the message listener threads as started _ = _messageListenerCompleted.Reset(); // Start incoming request listener // ToDo: Make message pump async, to not consume a thread for every session ThreadAbstraction.ExecuteThreadLongRunning(MessageListener); // Wait for key exchange to be completed WaitOnHandle(_keyExchangeCompletedWaitHandle); // If sessionId is not set then its not connected if (SessionId is 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(); } } /// /// Asynchronously connects to the server. /// /// /// Please note this function is NOT thread safe.
/// The caller SHOULD limit the number of simultaneous connection attempts to a server to a single connection attempt.
/// The to observe. /// A that represents the asynchronous connect operation. /// Socket connection to the SSH server or proxy server could not be established, or an error occurred while resolving the hostname. /// SSH session could not be established. /// Authentication of SSH session failed. /// Failed to establish proxy connection. public async Task ConnectAsync(CancellationToken cancellationToken) { // If connected don't connect again if (IsConnected) { return; } // Reset connection specific information Reset(); // Build list of available messages while connecting _sshMessageFactory = new SshMessageFactory(); _socket = await _serviceFactory.CreateConnector(ConnectionInfo, _socketFactory) .ConnectAsync(ConnectionInfo, cancellationToken).ConfigureAwait(false); var serverIdentification = await _serviceFactory.CreateProtocolVersionExchange() .StartAsync(ClientVersion, _socket, cancellationToken).ConfigureAwait(false); // Set connection versions ServerVersion = ConnectionInfo.ServerVersion = serverIdentification.ToString(); ConnectionInfo.ClientVersion = ClientVersion; DiagnosticAbstraction.Log(string.Format("Server version '{0}' on '{1}'.", serverIdentification.ProtocolVersion, serverIdentification.SoftwareVersion)); if (!(serverIdentification.ProtocolVersion.Equals("2.0") || serverIdentification.ProtocolVersion.Equals("1.99"))) { throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Server version '{0}' is not supported.", serverIdentification.ProtocolVersion), DisconnectReason.ProtocolVersionNotSupported); } // 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 to establishing encryption algorithm RegisterMessage("SSH_MSG_USERAUTH_BANNER"); // Mark the message listener threads as started _ = _messageListenerCompleted.Reset(); // Start incoming request listener // ToDo: Make message pump async, to not consume a thread for every session ThreadAbstraction.ExecuteThreadLongRunning(MessageListener); // Wait for key exchange to be completed WaitOnHandle(_keyExchangeCompletedWaitHandle); // If sessionId is not set then its not connected if (SessionId is 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"); } /// /// Disconnects from the server. /// /// /// This sends a SSH_MSG_DISCONNECT message to the server, waits for the /// server to close the socket on its end and subsequently closes the client socket. /// 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(); } /// /// Waits for the specified handle or the exception handle for the receive thread /// to signal within the connection timeout. /// /// The wait handle. /// A received package was invalid or failed the message integrity check. /// None of the handles are signaled in time and the session is not disconnecting. /// A socket error was signaled while receiving messages from the server. /// /// When neither handles are signaled in time and the session is not closing, then the /// session is disconnected. /// void ISession.WaitOnHandle(WaitHandle waitHandle) { WaitOnHandle(waitHandle, ConnectionInfo.Timeout); } /// /// Waits for the specified handle or the exception handle for the receive thread /// to signal within the specified timeout. /// /// The wait handle. /// The time to wait for any of the handles to become signaled. /// A received package was invalid or failed the message integrity check. /// None of the handles are signaled in time and the session is not disconnecting. /// A socket error was signaled while receiving messages from the server. /// /// When neither handles are signaled in time and the session is not closing, then the /// session is disconnected. /// void ISession.WaitOnHandle(WaitHandle waitHandle, TimeSpan timeout) { WaitOnHandle(waitHandle, timeout); } /// /// Waits for the specified to receive a signal, using a /// to specify the time interval. /// /// The that should be signaled. /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. /// /// A . /// WaitResult ISession.TryWait(WaitHandle waitHandle, TimeSpan timeout) { return TryWait(waitHandle, timeout, out _); } /// /// Waits for the specified to receive a signal, using a /// to specify the time interval. /// /// The that should be signaled. /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. /// When this method returns , contains the . /// /// A . /// WaitResult ISession.TryWait(WaitHandle waitHandle, TimeSpan timeout, out Exception exception) { return TryWait(waitHandle, timeout, out exception); } /// /// Waits for the specified to receive a signal, using a /// to specify the time interval. /// /// The that should be signaled. /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. /// When this method returns , contains the . /// /// A . /// private WaitResult TryWait(WaitHandle waitHandle, TimeSpan timeout, out Exception exception) { if (waitHandle is null) { throw new ArgumentNullException(nameof(waitHandle)); } var waitHandles = new[] { _exceptionWaitHandle, _messageListenerCompleted, waitHandle }; switch (WaitHandle.WaitAny(waitHandles, timeout)) { case 0: if (_exception is SshConnectionException) { exception = null; return WaitResult.Disconnected; } exception = _exception; return WaitResult.Failed; case 1: exception = null; return WaitResult.Disconnected; case 2: exception = null; return WaitResult.Success; case WaitHandle.WaitTimeout: exception = null; return WaitResult.TimedOut; default: throw new InvalidOperationException("Unexpected result."); } } /// /// Waits for the specified handle or the exception handle for the receive thread /// to signal within the connection timeout. /// /// The wait handle. /// A received package was invalid or failed the message integrity check. /// None of the handles are signaled in time and the session is not disconnecting. /// A socket error was signaled while receiving messages from the server. /// /// When neither handles are signaled in time and the session is not closing, then the /// session is disconnected. /// internal void WaitOnHandle(WaitHandle waitHandle) { WaitOnHandle(waitHandle, ConnectionInfo.Timeout); } /// /// Waits for the specified handle or the exception handle for the receive thread /// to signal within the specified timeout. /// /// The wait handle. /// The time to wait for any of the handles to become signaled. /// A received package was invalid or failed the message integrity check. /// None of the handles are signaled in time and the session is not disconnecting. /// A socket error was signaled while receiving messages from the server. internal void WaitOnHandle(WaitHandle waitHandle, TimeSpan timeout) { if (waitHandle is null) { throw new ArgumentNullException(nameof(waitHandle)); } var waitHandles = new[] { _exceptionWaitHandle, _messageListenerCompleted, waitHandle }; var signaledElement = WaitHandle.WaitAny(waitHandles, timeout); switch (signaledElement) { case 0: throw _exception; case 1: throw new SshConnectionException("Client not connected."); case 2: // Specified waithandle was signaled break; 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; default: throw new SshException($"Unexpected element '{signaledElement.ToString(CultureInfo.InvariantCulture)}' signaled."); } } /// /// Sends a message to the server. /// /// The message to send. /// The client is not connected. /// The operation timed out. /// The size of the packet exceeds the maximum size defined by the protocol. internal void SendMessage(Message message) { if (!_socket.CanWrite()) { throw new SshConnectionException("Client not connected."); } if (_keyExchangeInProgress && message is not 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 is 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 is 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++; } } /// /// Sends an SSH packet to the server. /// /// A byte array containing the packet to send. /// The offset of the packet. /// The length of the packet. /// Client is not connected to the server. /// /// /// The send is performed in a dispose lock to avoid /// and/or when sending the packet. /// /// /// This method is only to be used when the connection is established, as the locking /// overhead is not required while establising the connection. /// /// 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); } } /// /// Sends a message to the server. /// /// The message to send. /// /// true if the message was sent to the server; otherwise, false. /// /// The size of the packet exceeds the maximum size defined by the protocol. /// /// This methods returns false when the attempt to send the message results in a /// or a . /// 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; } } /// /// Receives the message from the server. /// /// /// The incoming SSH message, or null if the connection with the SSH server was closed. /// /// /// We need no locking here since all messages are read by a single thread. /// private Message ReceiveMessage(Socket socket) { // 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 is null ? (byte) 8 : Math.Max((byte) 8, _serverCipher.MinimumSize); var serverMacLength = _serverMac != null ? _serverMac.HashSize/8 : 0; byte[] data; uint packetLength; // 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) { // Read first block - which starts with the packet length var firstBlock = new byte[blockSize]; if (TrySocketRead(socket, 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(socket, data, blockSize + inboundPacketSequenceLength, bytesToRead) == 0) { return null; } } } 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 extra 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; } /// /// Called when received. /// /// message. 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(); DisconnectReceived?.Invoke(this, new MessageEventArgs(message)); Disconnected?.Invoke(this, EventArgs.Empty); // disconnect socket, and dispose it SocketDisconnectAndDispose(); } /// /// Called when received. /// /// message. internal void OnIgnoreReceived(IgnoreMessage message) { IgnoreReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnUnimplementedReceived(UnimplementedMessage message) { UnimplementedReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnDebugReceived(DebugMessage message) { DebugReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnServiceRequestReceived(ServiceRequestMessage message) { ServiceRequestReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnServiceAcceptReceived(ServiceAcceptMessage message) { ServiceAcceptReceived?.Invoke(this, new MessageEventArgs(message)); _ = _serviceAccepted.Set(); } internal void OnKeyExchangeDhGroupExchangeGroupReceived(KeyExchangeDhGroupExchangeGroup message) { KeyExchangeDhGroupExchangeGroupReceived?.Invoke(this, new MessageEventArgs(message)); } internal void OnKeyExchangeDhGroupExchangeReplyReceived(KeyExchangeDhGroupExchangeReply message) { KeyExchangeDhGroupExchangeReplyReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. 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); KeyExchangeInitReceived?.Invoke(this, new MessageEventArgs(message)); } internal void OnKeyExchangeDhReplyMessageReceived(KeyExchangeDhReplyMessage message) { KeyExchangeDhReplyMessageReceived?.Invoke(this, new MessageEventArgs(message)); } internal void OnKeyExchangeEcdhReplyMessageReceived(KeyExchangeEcdhReplyMessage message) { KeyExchangeEcdhReplyMessageReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnNewKeysReceived(NewKeysMessage message) { // Update sessionId 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(); NewKeysReceived?.Invoke(this, new MessageEventArgs(message)); // Signal that key exchange completed _ = _keyExchangeCompletedWaitHandle.Set(); _keyExchangeInProgress = false; } /// /// Called when client is disconnecting from the server. /// void ISession.OnDisconnecting() { _isDisconnecting = true; } /// /// Called when message received. /// /// message. internal void OnUserAuthenticationRequestReceived(RequestMessage message) { UserAuthenticationRequestReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnUserAuthenticationFailureReceived(FailureMessage message) { UserAuthenticationFailureReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnUserAuthenticationSuccessReceived(SuccessMessage message) { UserAuthenticationSuccessReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnUserAuthenticationBannerReceived(BannerMessage message) { UserAuthenticationBannerReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnUserAuthenticationInformationRequestReceived(InformationRequestMessage message) { UserAuthenticationInformationRequestReceived?.Invoke(this, new MessageEventArgs(message)); } internal void OnUserAuthenticationPasswordChangeRequiredReceived(PasswordChangeRequiredMessage message) { UserAuthenticationPasswordChangeRequiredReceived?.Invoke(this, new MessageEventArgs(message)); } internal void OnUserAuthenticationPublicKeyReceived(PublicKeyMessage message) { UserAuthenticationPublicKeyReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnGlobalRequestReceived(GlobalRequestMessage message) { GlobalRequestReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnRequestSuccessReceived(RequestSuccessMessage message) { RequestSuccessReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnRequestFailureReceived(RequestFailureMessage message) { RequestFailureReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnChannelOpenReceived(ChannelOpenMessage message) { ChannelOpenReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnChannelOpenConfirmationReceived(ChannelOpenConfirmationMessage message) { ChannelOpenConfirmationReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnChannelOpenFailureReceived(ChannelOpenFailureMessage message) { ChannelOpenFailureReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnChannelWindowAdjustReceived(ChannelWindowAdjustMessage message) { ChannelWindowAdjustReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnChannelDataReceived(ChannelDataMessage message) { ChannelDataReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnChannelExtendedDataReceived(ChannelExtendedDataMessage message) { ChannelExtendedDataReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnChannelEofReceived(ChannelEofMessage message) { ChannelEofReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnChannelCloseReceived(ChannelCloseMessage message) { ChannelCloseReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnChannelRequestReceived(ChannelRequestMessage message) { ChannelRequestReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnChannelSuccessReceived(ChannelSuccessMessage message) { ChannelSuccessReceived?.Invoke(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. internal void OnChannelFailureReceived(ChannelFailureMessage message) { ChannelFailureReceived?.Invoke(this, new MessageEventArgs(message)); } private void KeyExchange_HostKeyReceived(object sender, HostKeyEventArgs e) { HostKeyReceived?.Invoke(this, e); } /// /// Registers SSH message with the session. /// /// The name of the message to register with the session. public void RegisterMessage(string messageName) { _sshMessageFactory.EnableAndActivateMessage(messageName); } /// /// Unregister SSH message from the session. /// /// The name of the message to unregister with the session. public void UnRegisterMessage(string messageName) { _sshMessageFactory.DisableAndDeactivateMessage(messageName); } /// /// Loads a message from a given buffer. /// /// An array of bytes from which to construct the message. /// The zero-based byte offset in at which to begin reading. /// The number of bytes to load. /// /// A message constructed from . /// /// The type of the message is not supported. 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 is null) { return null; } return ToHex(bytes, 0); } /// /// Gets a value indicating whether the socket is connected. /// /// /// true if the socket is connected; otherwise, false. /// /// /// /// As a first check we verify whether is /// true. However, this only returns the state of the socket as of /// the last I/O operation. /// /// /// Therefore we use the combination of with mode /// and to verify if the socket is still connected. /// /// /// The MSDN doc mention the following on the return value of /// with mode : /// /// /// true if data is available for reading; /// /// /// true if the connection has been closed, reset, or terminated; otherwise, returns false. /// /// /// /// /// Conclusion: when the return value is true - but no data is available for reading - then /// the socket is no longer connected. /// /// /// When a is used from multiple threads, there's a race condition /// between the invocation of and the moment /// when the value of is obtained. To workaround this issue /// we synchronize reads from the . /// /// private bool IsSocketConnected() { lock (_socketDisposeLock) { if (!_socket.IsConnected()) { return false; } lock (_socketReadLock) { var connectionClosedOrDataAvailable = _socket.Poll(0, SelectMode.SelectRead); return !(connectionClosedOrDataAvailable && _socket.Available == 0); } } } /// /// Performs a blocking read on the socket until bytes are received. /// /// The to read from. /// An array of type that is the storage location for the received data. /// The position in parameter to store the received data. /// The number of bytes to read. /// /// The number of bytes read. /// /// The read has timed-out. /// The read failed. private static int TrySocketRead(Socket socket, byte[] buffer, int offset, int length) { return SocketAbstraction.Read(socket, buffer, offset, length, InfiniteTimeSpan); } /// /// Shuts down and disposes the socket. /// 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; } } } } /// /// Listens for incoming message from the server and handles them. This method run as a task on separate thread. /// private void MessageListener() { try { // remain in message loop until socket is shut down or until we're disconnecting while (true) { var socket = _socket; if (socket is null || !socket.Connected) { break; } try { // Block until either data is available or the socket is closed var connectionClosedOrDataAvailable = socket.Poll(-1, SelectMode.SelectRead); if (connectionClosedOrDataAvailable && socket.Available == 0) { // connection with SSH server was closed or connection was reset break; } } catch (ObjectDisposedException) { // The socket was disposed by either: // * a call to Disconnect() // * a call to Dispose() // * a SSH_MSG_DISCONNECT received from server break; } var message = ReceiveMessage(socket); if (message is null) { // Connection with SSH server was closed, so 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(); } } /// /// Raises the event. /// /// The . 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 if (exp is SocketException socketException && socketException.SocketErrorCode == SocketError.TimedOut) { return; } } // "save" exception and set exception wait handle to ensure any waits are interrupted _exception = exp; _ = _exceptionWaitHandle.Set(); ErrorOccured?.Invoke(this, new ExceptionEventArgs(exp)); if (connectionException != null) { DiagnosticAbstraction.Log(string.Format("[{0}] Disconnecting after exception: {1}", ToHex(SessionId), exp)); Disconnect(connectionException.DisconnectReason, exp.ToString()); } } /// /// Resets connection-specific information to ensure state of a previous connection /// does not affect new connections. /// private void Reset() { _ = _exceptionWaitHandle?.Reset(); _ = _keyExchangeCompletedWaitHandle?.Reset(); _ = _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); } private bool _disposed; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. 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; } } /// /// Finalizes an instance of the class. /// ~Session() { Dispose(disposing: false); } /// /// Gets the connection info. /// /// The connection info. IConnectionInfo ISession.ConnectionInfo { get { return ConnectionInfo; } } WaitHandle ISession.MessageListenerCompleted { get { return _messageListenerCompleted; } } /// /// Create a new SSH session channel. /// /// /// A new SSH session channel. /// IChannelSession ISession.CreateChannelSession() { return new ChannelSession(this, NextChannelNumber, InitialLocalWindowSize, LocalChannelDataPacketSize); } /// /// Create a new channel for a locally forwarded TCP/IP port. /// /// /// A new channel for a locally forwarded TCP/IP port. /// IChannelDirectTcpip ISession.CreateChannelDirectTcpip() { return new ChannelDirectTcpip(this, NextChannelNumber, InitialLocalWindowSize, LocalChannelDataPacketSize); } /// /// Creates a "forwarded-tcpip" SSH channel. /// /// /// A new "forwarded-tcpip" SSH channel. /// IChannelForwardedTcpip ISession.CreateChannelForwardedTcpip(uint remoteChannelNumber, uint remoteWindowSize, uint remoteChannelDataPacketSize) { return new ChannelForwardedTcpip(this, NextChannelNumber, InitialLocalWindowSize, LocalChannelDataPacketSize, remoteChannelNumber, remoteWindowSize, remoteChannelDataPacketSize); } /// /// Sends a message to the server. /// /// The message to send. /// The client is not connected. /// The operation timed out. /// The size of the packet exceeds the maximum size defined by the protocol. void ISession.SendMessage(Message message) { SendMessage(message); } /// /// Sends a message to the server. /// /// The message to send. /// /// true if the message was sent to the server; otherwise, false. /// /// The size of the packet exceeds the maximum size defined by the protocol. /// /// This methods returns false when the attempt to send the message results in a /// or a . /// bool ISession.TrySendMessage(Message message) { return TrySendMessage(message); } } /// /// Represents the result of a wait operations. /// internal enum WaitResult { /// /// The was signaled within the specified interval. /// Success = 1, /// /// The was not signaled within the specified interval. /// TimedOut = 2, /// /// The session is in a disconnected state. /// Disconnected = 3, /// /// The session is in a failed state. /// Failed = 4 } }