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; }
}
}
}