using System; using System.Collections.Generic; using System.Linq; 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 Renci.SshNet.Security.Cryptography; using ASCIIEncoding = Renci.SshNet.Common.ASCIIEncoding; namespace Renci.SshNet { /// /// Provides functionality to connect and interact with SSH server. /// public partial class Session : ISession { /// /// 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); /// /// Specifies an infinite waiting period. /// /// /// The value of this field is -1. /// internal static readonly int Infinite = -1; /// /// Specifies maximum packet size defined by the protocol. /// private const int MaximumSshPacketSize = LocalChannelDataPacketSize + 3000; /// /// Holds the initial local window size for the channels. /// /// /// 2 MB. /// private const int InitialLocalWindowSize = LocalChannelDataPacketSize * 32; /// /// Holds the maximum size of channel data packets that we receive. /// /// /// 64 KB. /// private const int LocalChannelDataPacketSize = 1024*64; #if !TUNING private static readonly RNGCryptoServiceProvider Randomizer = new RNGCryptoServiceProvider(); #endif #if SILVERLIGHT private static readonly Regex ServerVersionRe = new Regex("^SSH-(?[^-]+)-(?.+)( SP.+)?$"); #else private static readonly Regex ServerVersionRe = new Regex("^SSH-(?[^-]+)-(?.+)( SP.+)?$", RegexOptions.Compiled); #endif /// /// 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 metada about session messages /// private IEnumerable _messagesMetadata; /// /// Holds connection socket. /// private Socket _socket; /// /// Holds locker object for the socket /// private readonly object _socketLock = new object(); /// /// Holds a that is signaled when the message listener loop has completed. /// private EventWaitHandle _messageListenerCompleted; /// /// Specifies outbound packet number /// private volatile UInt32 _outboundPacketSequence; /// /// Specifies incoming packet number /// private UInt32 _inboundPacketSequence; /// /// WaitHandle to signal that last service request was accepted /// private EventWaitHandle _serviceAccepted = new AutoResetEvent(false); /// /// WaitHandle to signal that exception was thrown by another thread. /// private EventWaitHandle _exceptionWaitHandle = new ManualResetEvent(false); /// /// WaitHandle to signal that key exchange was completed. /// private EventWaitHandle _keyExchangeCompletedWaitHandle = new ManualResetEvent(false); /// /// WaitHandle to signal that bytes have been read from the socket. /// private EventWaitHandle _bytesReadFromSocket = new ManualResetEvent(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 the factory to use for creating new services. /// private readonly IServiceFactory _serviceFactory; /// /// Gets the session semaphore that controls session channels. /// /// /// The session semaphore. /// 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; /// /// 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 == null || _messageListenerCompleted.WaitOne(0)) return false; var isSocketConnected = false; IsSocketConnected(ref isSocketConnected); 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 { 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; } } /// /// Gets or sets the server version string. /// /// The server version. public string ServerVersion { get; private set; } /// /// Gets or sets the client version string. /// /// The client version. public string ClientVersion { get; private set; } /// /// Gets or sets 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; #region Message events /// /// 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 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; /// /// Occurs when message received and is not handled by any of the event handlers /// internal event EventHandler> MessageReceived; #endregion /// /// Initializes a new instance of the class. /// /// The connection info. /// The factory to use for creating new services. /// is null. /// is null. 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); } /// /// 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 _messagesMetadata = GetMessagesMetadata(); 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 = string.Empty; SocketReadLine(ref serverVersion, 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}"); 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); } SocketWrite(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 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."); } ConnectionInfo.Authenticate(this, _serviceFactory); _isAuthenticated = true; // Register Connection messages RegisterMessage("SSH_MSG_GLOBAL_REQUEST"); 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"); Monitor.Pulse(this); } } finally { AuthenticationConnection.Release(); } } /// /// 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() { 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) { _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 thread to be stopped as // the server should respond by closing the socket if (reason == DisconnectReason.ByApplication) { SendDisconnect(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 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 == 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; } } /// /// 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 == null || !_socket.CanWrite()) throw new SshConnectionException("Client not connected."); if (_keyExchangeInProgress && !(message is IKeyExchangedAllowed)) { // Wait for key exchange to be completed WaitOnHandle(_keyExchangeCompletedWaitHandle); } Log(string.Format("SendMessage to server '{0}': '{1}'.", message.GetType().Name, message)); // Messages can be sent by different thread so we need to synchronize it var paddingMultiplier = _clientCipher == null ? (byte)8 : Math.Max((byte)8, _serverCipher.MinimumSize); // Should be recalculate base on cipher min length if cipher specified #if TUNING var packetData = message.GetPacket(paddingMultiplier, _clientCompression); #else var messageData = message.GetBytes(); if (_clientCompression != null) { messageData = _clientCompression.Compress(messageData); } var packetLength = messageData.Length + 4 + 1; // add length bytes and padding byte var paddingLength = (byte)((-packetLength) & (paddingMultiplier - 1)); if (paddingLength < paddingMultiplier) { paddingLength += paddingMultiplier; } // Build Packet data var packetData = new byte[4 + 1 + messageData.Length + paddingLength]; // Add packet length ((uint)packetData.Length - 4).GetBytes().CopyTo(packetData, 0); // Add packet padding length packetData[4] = paddingLength; // Add packet payload messageData.CopyTo(packetData, 4 + 1); // Add random padding var paddingBytes = new byte[paddingLength]; Randomizer.GetBytes(paddingBytes); paddingBytes.CopyTo(packetData, 4 + 1 + messageData.Length); #endif // Lock handling of _outboundPacketSequence since it must be sent sequently to server lock (_socketLock) { if (_socket == null || !_socket.Connected) throw new SshConnectionException("Client not connected."); #if TUNING 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 _outboundPacketSequence.Write(packetData, 0); // calculate packet hash hash = _clientMac.ComputeHash(packetData); } #else // Calculate packet hash var hashData = new byte[4 + packetData.Length]; _outboundPacketSequence.GetBytes().CopyTo(hashData, 0); packetData.CopyTo(hashData, 4); #endif // Encrypt packet data if (_clientCipher != null) { #if TUNING packetData = _clientCipher.Encrypt(packetData, packetDataOffset, (packetData.Length - packetDataOffset)); packetDataOffset = 0; #else packetData = _clientCipher.Encrypt(packetData); #endif } if (packetData.Length > MaximumSshPacketSize) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Packet is too big. Maximum packet size is {0} bytes.", MaximumSshPacketSize)); } #if TUNING var packetLength = packetData.Length - packetDataOffset; if (hash == null) { SocketWrite(packetData, packetDataOffset, packetLength); } #else if (_clientMac == null) { SocketWrite(packetData); } #endif else { #if TUNING var data = new byte[packetLength + (_clientMac.HashSize / 8)]; Buffer.BlockCopy(packetData, packetDataOffset, data, 0, packetLength); Buffer.BlockCopy(hash, 0, data, packetLength, hash.Length); #else var hash = _clientMac.ComputeHash(hashData.ToArray()); var data = new byte[packetData.Length + _clientMac.HashSize / 8]; packetData.CopyTo(data, 0); hash.CopyTo(data, packetData.Length); #endif SocketWrite(data); } _outboundPacketSequence++; Monitor.Pulse(_socketLock); } } /// /// 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) { Log(string.Format("Failure sending message server '{0}': '{1}' => {2}", message.GetType().Name, message, ex)); return false; } catch (SocketException ex) { Log(string.Format("Failure sending message server '{0}': '{1}' => {2}", message.GetType().Name, message, ex)); return false; } } private static IEnumerable GetMessagesMetadata() { return new [] { new MessageMetadata { Name = "SSH_MSG_NEWKEYS", Number = 21, Type = typeof(NewKeysMessage) }, new MessageMetadata { Name = "SSH_MSG_REQUEST_FAILURE", Number = 82, Type = typeof(RequestFailureMessage) }, new MessageMetadata { Name = "SSH_MSG_KEXINIT", Number = 20, Type = typeof(KeyExchangeInitMessage) }, new MessageMetadata { Name = "SSH_MSG_CHANNEL_OPEN_FAILURE", Number = 92, Type = typeof(ChannelOpenFailureMessage) }, new MessageMetadata { Name = "SSH_MSG_CHANNEL_FAILURE", Number = 100, Type = typeof(ChannelFailureMessage) }, new MessageMetadata { Name = "SSH_MSG_CHANNEL_EXTENDED_DATA", Number = 95, Type = typeof(ChannelExtendedDataMessage) }, new MessageMetadata { Name = "SSH_MSG_CHANNEL_DATA", Number = 94, Type = typeof(ChannelDataMessage) }, new MessageMetadata { Name = "SSH_MSG_USERAUTH_REQUEST", Number = 50, Type = typeof(RequestMessage) }, new MessageMetadata { Name = "SSH_MSG_CHANNEL_REQUEST", Number = 98, Type = typeof(ChannelRequestMessage) }, new MessageMetadata { Name = "SSH_MSG_USERAUTH_BANNER", Number = 53, Type = typeof(BannerMessage) }, new MessageMetadata { Name = "SSH_MSG_USERAUTH_INFO_RESPONSE", Number = 61, Type = typeof(InformationResponseMessage) }, new MessageMetadata { Name = "SSH_MSG_USERAUTH_FAILURE", Number = 51, Type = typeof(FailureMessage) }, new MessageMetadata { Name = "SSH_MSG_DEBUG", Number = 4, Type = typeof(DebugMessage), }, new MessageMetadata { Name = "SSH_MSG_KEXDH_INIT", Number = 30, Type = typeof(KeyExchangeDhInitMessage) }, new MessageMetadata { Name = "SSH_MSG_GLOBAL_REQUEST", Number = 80, Type = typeof(GlobalRequestMessage) }, new MessageMetadata { Name = "SSH_MSG_CHANNEL_OPEN", Number = 90, Type = typeof(ChannelOpenMessage) }, new MessageMetadata { Name = "SSH_MSG_CHANNEL_OPEN_CONFIRMATION", Number = 91, Type = typeof(ChannelOpenConfirmationMessage) }, new MessageMetadata { Name = "SSH_MSG_USERAUTH_INFO_REQUEST", Number = 60, Type = typeof(InformationRequestMessage) }, new MessageMetadata { Name = "SSH_MSG_UNIMPLEMENTED", Number = 3, Type = typeof(UnimplementedMessage) }, new MessageMetadata { Name = "SSH_MSG_REQUEST_SUCCESS", Number = 81, Type = typeof(RequestSuccessMessage) }, new MessageMetadata { Name = "SSH_MSG_CHANNEL_SUCCESS", Number = 99, Type = typeof(ChannelSuccessMessage) }, new MessageMetadata { Name = "SSH_MSG_USERAUTH_PASSWD_CHANGEREQ", Number = 60, Type = typeof(PasswordChangeRequiredMessage) }, new MessageMetadata { Name = "SSH_MSG_DISCONNECT", Number = 1, Type = typeof(DisconnectMessage) }, new MessageMetadata { Name = "SSH_MSG_SERVICE_REQUEST", Number = 5, Type = typeof(ServiceRequestMessage) }, new MessageMetadata { Name = "SSH_MSG_KEX_DH_GEX_REQUEST", Number = 34, Type = typeof(KeyExchangeDhGroupExchangeRequest) }, new MessageMetadata { Name = "SSH_MSG_KEX_DH_GEX_GROUP", Number = 31, Type = typeof(KeyExchangeDhGroupExchangeGroup) }, new MessageMetadata { Name = "SSH_MSG_USERAUTH_SUCCESS", Number = 52, Type = typeof(SuccessMessage) }, new MessageMetadata { Name = "SSH_MSG_USERAUTH_PK_OK", Number = 60, Type = typeof(PublicKeyMessage) }, new MessageMetadata { Name = "SSH_MSG_IGNORE", Number = 2, Type = typeof(IgnoreMessage) }, new MessageMetadata { Name = "SSH_MSG_CHANNEL_WINDOW_ADJUST", Number = 93, Type = typeof(ChannelWindowAdjustMessage) }, new MessageMetadata { Name = "SSH_MSG_CHANNEL_EOF", Number = 96, Type = typeof(ChannelEofMessage) }, new MessageMetadata { Name = "SSH_MSG_CHANNEL_CLOSE", Number = 97, Type = typeof(ChannelCloseMessage) }, new MessageMetadata { Name = "SSH_MSG_SERVICE_ACCEPT", Number = 6, Type = typeof(ServiceAcceptMessage) }, new MessageMetadata { Name = "SSH_MSG_KEXDH_REPLY", Number = 31, Type = typeof(KeyExchangeDhReplyMessage) }, new MessageMetadata { Name = "SSH_MSG_KEX_DH_GEX_INIT", Number = 32, Type = typeof(KeyExchangeDhGroupExchangeInit) }, new MessageMetadata { Name = "SSH_MSG_KEX_DH_GEX_REPLY", Number = 33, Type = typeof(KeyExchangeDhGroupExchangeReply) } }; } /// /// Receives the message from the server. /// /// Incoming SSH message. /// private Message ReceiveMessage() { #if TUNING const int inboundPacketSequenceLength = 4; #endif // No lock needed since all messages read by only one thread var blockSize = _serverCipher == null ? (byte)8 : Math.Max((byte)8, _serverCipher.MinimumSize); // Read packet length first var firstBlock = Read(blockSize); if (_serverCipher != null) { firstBlock = _serverCipher.Decrypt(firstBlock); } var packetLength = (uint)(firstBlock[0] << 24 | firstBlock[1] << 16 | firstBlock[2] << 8 | firstBlock[3]); // 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); // Read rest of the packet data var bytesToRead = (int)(packetLength - (blockSize - 4)); #if TUNING var data = new byte[bytesToRead + blockSize + inboundPacketSequenceLength]; _inboundPacketSequence.Write(data, 0); Buffer.BlockCopy(firstBlock, 0, data, inboundPacketSequenceLength, firstBlock.Length); #else var data = new byte[bytesToRead + blockSize]; firstBlock.CopyTo(data, 0); #endif byte[] serverHash = null; if (_serverMac != null) { serverHash = new byte[_serverMac.HashSize / 8]; bytesToRead += serverHash.Length; } if (bytesToRead > 0) { var nextBlocks = Read(bytesToRead); if (serverHash != null) { Buffer.BlockCopy(nextBlocks, nextBlocks.Length - serverHash.Length, serverHash, 0, serverHash.Length); nextBlocks = nextBlocks.Take(nextBlocks.Length - serverHash.Length).ToArray(); } if (nextBlocks.Length > 0) { if (_serverCipher != null) { nextBlocks = _serverCipher.Decrypt(nextBlocks); } #if TUNING nextBlocks.CopyTo(data, blockSize + inboundPacketSequenceLength); #else nextBlocks.CopyTo(data, blockSize); #endif } } #if TUNING var paddingLength = data[inboundPacketSequenceLength + 4]; #else var paddingLength = data[4]; #endif var messagePayloadLength = (int) (packetLength - paddingLength - 1); #if TUNING const int messagePayloadOffset = inboundPacketSequenceLength + 4 + 1; #else var messagePayload = new byte[messagePayloadLength]; Buffer.BlockCopy(data, 5, messagePayload, 0, messagePayload.Length); #endif // Validate message against MAC if (_serverMac != null) { #if TUNING var clientHash = _serverMac.ComputeHash(data); #else var clientHashData = new byte[4 + data.Length]; var lengthBytes = _inboundPacketSequence.GetBytes(); lengthBytes.CopyTo(clientHashData, 0); data.CopyTo(clientHashData, 4); // Calculate packet hash var clientHash = _serverMac.ComputeHash(clientHashData); #endif if (!serverHash.SequenceEqual(clientHash)) { throw new SshConnectionException("MAC error", DisconnectReason.MacError); } } if (_serverDecompression != null) { #if TUNING data = _serverDecompression.Decompress(data, inboundPacketSequenceLength + 4 + 1, messagePayloadLength); #else messagePayload = _serverDecompression.Decompress(messagePayload); #endif } _inboundPacketSequence++; #if TUNING return LoadMessage(data, messagePayloadOffset); #else return LoadMessage(messagePayload); #endif } private void SendDisconnect(DisconnectReason reasonCode, string message) { // only send a disconnect message if it wasn't already sent, and we're // still connected if (_isDisconnectMessageSent || !IsConnected) return; var disconnectMessage = new DisconnectMessage(reasonCode, message); // send the disconnect message, but ignore the outcome TrySendMessage(disconnectMessage); _isDisconnectMessageSent = true; } partial void HandleMessageCore(Message message); /// /// Handles the message. /// /// /// The message. private void HandleMessage(T message) where T : Message { OnMessageReceived(message); } #region Handle transport messages private void HandleMessage(DisconnectMessage message) { OnDisconnectReceived(message); Disconnect(message.ReasonCode, message.Description); } private void HandleMessage(IgnoreMessage message) { OnIgnoreReceived(message); } private void HandleMessage(UnimplementedMessage message) { OnUnimplementedReceived(message); } private void HandleMessage(DebugMessage message) { OnDebugReceived(message); } private void HandleMessage(ServiceRequestMessage message) { OnServiceRequestReceived(message); } private void HandleMessage(ServiceAcceptMessage message) { // TODO: Refactor to avoid this method here OnServiceAcceptReceived(message); _serviceAccepted.Set(); } private void HandleMessage(KeyExchangeInitMessage message) { OnKeyExchangeInitReceived(message); } private void HandleMessage(NewKeysMessage message) { OnNewKeysReceived(message); } #endregion #region Handle User Authentication messages private void HandleMessage(RequestMessage message) { OnUserAuthenticationRequestReceived(message); } private void HandleMessage(FailureMessage message) { OnUserAuthenticationFailureReceived(message); } private void HandleMessage(SuccessMessage message) { OnUserAuthenticationSuccessReceived(message); } private void HandleMessage(BannerMessage message) { OnUserAuthenticationBannerReceived(message); } #endregion #region Handle connection messages private void HandleMessage(GlobalRequestMessage message) { OnGlobalRequestReceived(message); } private void HandleMessage(RequestSuccessMessage message) { OnRequestSuccessReceived(message); } private void HandleMessage(RequestFailureMessage message) { OnRequestFailureReceived(message); } private void HandleMessage(ChannelOpenMessage message) { OnChannelOpenReceived(message); } private void HandleMessage(ChannelOpenConfirmationMessage message) { OnChannelOpenConfirmationReceived(message); } private void HandleMessage(ChannelOpenFailureMessage message) { OnChannelOpenFailureReceived(message); } private void HandleMessage(ChannelWindowAdjustMessage message) { OnChannelWindowAdjustReceived(message); } private void HandleMessage(ChannelDataMessage message) { OnChannelDataReceived(message); } private void HandleMessage(ChannelExtendedDataMessage message) { OnChannelExtendedDataReceived(message); } private void HandleMessage(ChannelEofMessage message) { OnChannelEofReceived(message); } private void HandleMessage(ChannelCloseMessage message) { OnChannelCloseReceived(message); } private void HandleMessage(ChannelRequestMessage message) { OnChannelRequestReceived(message); } private void HandleMessage(ChannelSuccessMessage message) { OnChannelSuccessReceived(message); } private void HandleMessage(ChannelFailureMessage message) { OnChannelFailureReceived(message); } #endregion #region Handle received message events /// /// Called when received. /// /// message. protected virtual void OnDisconnectReceived(DisconnectMessage message) { Log(string.Format("Disconnect received: {0} {1}", message.ReasonCode, message.Description)); _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(message)); var disconnected = Disconnected; if (disconnected != null) disconnected(this, new EventArgs()); } /// /// Called when received. /// /// message. protected virtual void OnIgnoreReceived(IgnoreMessage message) { var handlers = IgnoreReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnUnimplementedReceived(UnimplementedMessage message) { var handlers = UnimplementedReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnDebugReceived(DebugMessage message) { var handlers = DebugReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnServiceRequestReceived(ServiceRequestMessage message) { var handlers = ServiceRequestReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnServiceAcceptReceived(ServiceAcceptMessage message) { var handlers = ServiceAcceptReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnKeyExchangeInitReceived(KeyExchangeInitMessage message) { _keyExchangeInProgress = true; _keyExchangeCompletedWaitHandle.Reset(); // Disable all registered messages except key exchange related foreach (var messageMetadata in _messagesMetadata) { if (messageMetadata.Activated && messageMetadata.Number > 2 && (messageMetadata.Number < 20 || messageMetadata.Number > 30)) messageMetadata.Enabled = false; } _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(message)); } /// /// Called when message received. /// /// message. protected virtual void OnNewKeysReceived(NewKeysMessage message) { // Update sessionId if (SessionId == null) { SessionId = _keyExchange.ExchangeHash; } // Dispose of old ciphers and hash algorithms if (_serverMac != null) { _serverMac.Clear(); _serverMac = null; } if (_clientMac != null) { _clientMac.Clear(); _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 all active registered messages foreach (var messageMetadata in _messagesMetadata) { if (messageMetadata.Activated) messageMetadata.Enabled = true; } var newKeysReceived = NewKeysReceived; if (newKeysReceived != null) newKeysReceived(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. protected virtual void OnUserAuthenticationRequestReceived(RequestMessage message) { var handlers = UserAuthenticationRequestReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnUserAuthenticationFailureReceived(FailureMessage message) { var handlers = UserAuthenticationFailureReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnUserAuthenticationSuccessReceived(SuccessMessage message) { var handlers = UserAuthenticationSuccessReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnUserAuthenticationBannerReceived(BannerMessage message) { var handlers = UserAuthenticationBannerReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnGlobalRequestReceived(GlobalRequestMessage message) { var handlers = GlobalRequestReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnRequestSuccessReceived(RequestSuccessMessage message) { var handlers = RequestSuccessReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnRequestFailureReceived(RequestFailureMessage message) { var handlers = RequestFailureReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnChannelOpenReceived(ChannelOpenMessage message) { var handlers = ChannelOpenReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnChannelOpenConfirmationReceived(ChannelOpenConfirmationMessage message) { var handlers = ChannelOpenConfirmationReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnChannelOpenFailureReceived(ChannelOpenFailureMessage message) { var handlers = ChannelOpenFailureReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnChannelWindowAdjustReceived(ChannelWindowAdjustMessage message) { var handlers = ChannelWindowAdjustReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnChannelDataReceived(ChannelDataMessage message) { var handlers = ChannelDataReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnChannelExtendedDataReceived(ChannelExtendedDataMessage message) { var handlers = ChannelExtendedDataReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnChannelEofReceived(ChannelEofMessage message) { var handlers = ChannelEofReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnChannelCloseReceived(ChannelCloseMessage message) { var handlers = ChannelCloseReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnChannelRequestReceived(ChannelRequestMessage message) { var handlers = ChannelRequestReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnChannelSuccessReceived(ChannelSuccessMessage message) { var handlers = ChannelSuccessReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnChannelFailureReceived(ChannelFailureMessage message) { var handlers = ChannelFailureReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } /// /// Called when message received. /// /// message. protected virtual void OnMessageReceived(Message message) { var handlers = MessageReceived; if (handlers != null) handlers(this, new MessageEventArgs(message)); } #endregion private void KeyExchange_HostKeyReceived(object sender, HostKeyEventArgs e) { var handlers = HostKeyReceived; if (handlers != null) handlers(this, e); } /// /// Reads the specified length of bytes from the server. /// /// The length. /// /// The bytes read from the server. /// private byte[] Read(int length) { var buffer = new byte[length]; SocketRead(length, ref buffer); return buffer; } #region Message loading functions /// /// Registers SSH message with the session. /// /// The name of the message to register with the session. public void RegisterMessage(string messageName) { InternalRegisterMessage(messageName); } /// /// Unregister SSH message from the session. /// /// The name of the message to unregister with the session. public void UnRegisterMessage(string messageName) { InternalUnRegisterMessage(messageName); } #if TUNING /// /// 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. /// /// A message constructed from . /// /// The type of the message is not supported. private Message LoadMessage(byte[] data, int offset) { var messageType = data[offset]; var messageMetadata = (from m in _messagesMetadata where m.Number == messageType && m.Enabled && m.Activated select m).FirstOrDefault(); if (messageMetadata == null) throw new SshException(string.Format(CultureInfo.CurrentCulture, "Message type {0} is not valid.", messageType)); var message = messageMetadata.Type.CreateInstance(); message.Load(data, offset); Log(string.Format("ReceiveMessage from server: '{0}': '{1}'.", message.GetType().Name, message)); return message; } #else /// /// Loads the message. /// /// Message data. /// New message private Message LoadMessage(byte[] data) { var messageType = data[0]; var messageMetadata = (from m in _messagesMetadata where m.Number == messageType && m.Enabled && m.Activated select m).SingleOrDefault(); if (messageMetadata == null) throw new SshException(string.Format(CultureInfo.CurrentCulture, "Message type {0} is not valid.", messageType)); var message = messageMetadata.Type.CreateInstance(); message.Load(data); Log(string.Format("ReceiveMessage from server: '{0}': '{1}'.", message.GetType().Name, message)); return message; } #endif partial void InternalRegisterMessage(string messageName); partial void InternalUnRegisterMessage(string messageName); #endregion partial void ExecuteThread(Action action); /// /// Gets a value indicating whether the socket is connected. /// /// /// true if the socket is connected; otherwise, false. /// partial void IsSocketConnected(ref bool isConnected); /// /// Establishes a socket connection to the specified host and port. /// /// The host name of the server to connect to. /// The port to connect to. /// The connection failed to establish within the configured . /// An error occurred trying to establish the connection. partial void SocketConnect(string host, int port); /// /// Closes the socket. /// /// An error occurred when trying to access the socket. partial void SocketDisconnect(); /// /// Performs a blocking read on the socket until bytes are received. /// /// The number of bytes to read. /// The buffer to read to. /// The socket is closed. /// The read has timed-out. /// The read failed. partial void SocketRead(int length, ref byte[] buffer); /// /// Performs a blocking read on the socket until a line is read. /// /// The line read from the socket, or null when the remote server has shutdown and all data has been received. /// A that represents the time to wait until a line is read. /// The read has timed-out. /// An error occurred when trying to access the socket. partial void SocketReadLine(ref string response, TimeSpan timeout); partial void Log(string text); /// /// Writes the specified data to the server. /// /// The data to write to the server. /// The write has timed-out. /// The write failed. private void SocketWrite(byte[] data) { SocketWrite(data, 0, data.Length); } /// /// Disconnects and disposes the socket. /// private void SocketDisconnectAndDispose() { if (_socket != null) { lock (_socketLock) { if (_socket != null) { if (_socket.Connected) SocketDisconnect(); _socket.Dispose(); _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 { while (_socket != null && _socket.Connected) { var message = ReceiveMessage(); HandleMessageCore(message); } } 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(1, ref buffer); return buffer[0]; } private void SocketWriteByte(byte data) { SocketWrite(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 = ConnectionInfo.Host.GetIPAddress(); SocketWrite(ipAddress.GetAddressBytes()); // Send username var username = new ASCIIEncoding().GetBytes(ConnectionInfo.ProxyUsername); SocketWrite(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[4]; // Read 2 bytes to be ignored SocketRead(2, ref dummyBuffer); // Read 4 bytes to be ignored SocketRead(4, ref dummyBuffer); } 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 encoding = new ASCIIEncoding(); var username = encoding.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 SocketWrite(username); var password = encoding.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 SocketWrite(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 = ConnectionInfo.Host.GetIPAddress(); // Send address type and address if (ip.AddressFamily == AddressFamily.InterNetwork) { SocketWriteByte(0x01); var address = ip.GetAddressBytes(); SocketWrite(address); } else if (ip.AddressFamily == AddressFamily.InterNetworkV6) { SocketWriteByte(0x04); var address = ip.GetAddressBytes(); SocketWrite(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("SOCKS4: 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(4, ref responseIp); break; case 0x04: SocketRead(16, ref responseIp); 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(2, ref port); } private void ConnectHttp() { var httpResponseRe = new Regex(@"HTTP/(?\d[.]\d) (?\d{3}) (?.+)$"); var httpHeaderRe = new Regex(@"(?[^\[\]()<>@,;:\""/?={} \t]+):(?.+)?"); var encoding = new ASCIIEncoding(); SocketWrite(encoding.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(encoding.GetBytes(string.Format("{0}:{1}", ConnectionInfo.ProxyUsername, ConnectionInfo.ProxyPassword))) ); SocketWrite(encoding.GetBytes(authorization)); } SocketWrite(encoding.GetBytes("\r\n")); HttpStatusCode? statusCode = null; var response = string.Empty; var contentLength = 0; while (true) { SocketReadLine(ref response, 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.InvariantCultureIgnoreCase)) { 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(contentLength, ref contentBody); } break; } } if (statusCode == null) throw new ProxyException("HTTP response does not contain status line."); } /// /// Raises the event. /// /// The exp. private void RaiseError(Exception exp) { var connectionException = exp as SshConnectionException; 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; } _exception = exp; _exceptionWaitHandle.Set(); var errorOccured = ErrorOccured; if (errorOccured != null) errorOccured(this, new ExceptionEventArgs(exp)); if (connectionException != null) { Disconnect(connectionException.DisconnectReason, exp.ToString()); } } /// /// Resets connection-specific information to ensure state of a previous connection /// does not affect new connections. /// 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; } #region IDisposable implementation private bool _disposed; /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged ResourceMessages. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged ResourceMessages. protected virtual void Dispose(bool disposing) { // Check to see if Dispose has already been called. if (!_disposed) { // If disposing equals true, dispose all managed // and unmanaged ResourceMessages. if (disposing) { Disconnect(); if (_serviceAccepted != null) { _serviceAccepted.Dispose(); _serviceAccepted = null; } if (_exceptionWaitHandle != null) { _exceptionWaitHandle.Dispose(); _exceptionWaitHandle = null; } if (_keyExchangeCompletedWaitHandle != null) { _keyExchangeCompletedWaitHandle.Dispose(); _keyExchangeCompletedWaitHandle = null; } if (_serverMac != null) { _serverMac.Clear(); _serverMac = null; } if (_clientMac != null) { _clientMac.Clear(); _clientMac = null; } if (_keyExchange != null) { _keyExchange.HostKeyReceived -= KeyExchange_HostKeyReceived; _keyExchange.Dispose(); _keyExchange = null; } if (_bytesReadFromSocket != null) { _bytesReadFromSocket.Dispose(); _bytesReadFromSocket = null; } if (_messageListenerCompleted != null) { _messageListenerCompleted.Dispose(); _messageListenerCompleted = null; } } // Note disposing has been done. _disposed = true; } } /// /// Releases unmanaged resources and performs other cleanup operations before the /// is reclaimed by garbage collection. /// ~Session() { // Do not re-create Dispose clean-up code here. // Calling Dispose(false) is optimal in terms of // readability and maintainability. Dispose(false); } #endregion IDisposable implementation #region ISession implementation /// /// Gets or sets 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); } #endregion ISession implementation private class MessageMetadata { public string Name { get; set; } public byte Number { get; set; } public bool Enabled { get; set; } public bool Activated { get; set; } public Type Type { get; set; } } } }