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;
namespace Renci.SshNet
{
///
/// Provides functionality to connect and interact with SSH server.
///
public partial class Session : IDisposable, ISession
{
///
/// 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;
private static readonly RNGCryptoServiceProvider Randomizer = new RNGCryptoServiceProvider();
#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 reference to task that listens for incoming messages
///
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 KeyExchange _keyExchange;
private HashAlgorithm _serverMac;
private HashAlgorithm _clientMac;
private Cipher _clientCipher;
private Cipher _serverCipher;
private Compressor _serverDecompression;
private Compressor _clientCompression;
private SemaphoreLight _sessionSemaphore;
///
/// Gets the session semaphore that controls session channels.
///
/// The session semaphore.
public SemaphoreLight SessionSemaphore
{
get
{
if (this._sessionSemaphore == null)
{
lock (this)
{
if (this._sessionSemaphore == null)
{
this._sessionSemaphore = new SemaphoreLight(this.ConnectionInfo.MaxSessions);
}
}
}
return this._sessionSemaphore;
}
}
private bool _isDisconnectMessageSent;
private uint _nextChannelNumber;
///
/// Gets the next channel number.
///
/// The next channel number.
internal uint NextChannelNumber
{
get
{
uint result;
lock (this)
{
result = this._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 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 (_isDisconnectMessageSent || !this._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 (this._clientInitMessage == null)
{
this._clientInitMessage = new KeyExchangeInitMessage
{
KeyExchangeAlgorithms = this.ConnectionInfo.KeyExchangeAlgorithms.Keys.ToArray(),
ServerHostKeyAlgorithms = this.ConnectionInfo.HostKeyAlgorithms.Keys.ToArray(),
EncryptionAlgorithmsClientToServer = this.ConnectionInfo.Encryptions.Keys.ToArray(),
EncryptionAlgorithmsServerToClient = this.ConnectionInfo.Encryptions.Keys.ToArray(),
MacAlgorithmsClientToServer = this.ConnectionInfo.HmacAlgorithms.Keys.ToArray(),
MacAlgorithmsServerToClient = this.ConnectionInfo.HmacAlgorithms.Keys.ToArray(),
CompressionAlgorithmsClientToServer = this.ConnectionInfo.CompressionAlgorithms.Keys.ToArray(),
CompressionAlgorithmsServerToClient = this.ConnectionInfo.CompressionAlgorithms.Keys.ToArray(),
LanguagesClientToServer = new[] {string.Empty},
LanguagesServerToClient = new[] {string.Empty},
FirstKexPacketFollows = false,
Reserved = 0
};
}
return this._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
///
internal event EventHandler> RequestSuccessReceived;
///
/// Occurs when message received
///
internal event EventHandler> RequestFailureReceived;
///
/// Occurs when message received
///
internal event EventHandler> ChannelOpenReceived;
///
/// Occurs when message received
///
internal event EventHandler> ChannelOpenConfirmationReceived;
///
/// Occurs when message received
///
internal event EventHandler> ChannelOpenFailureReceived;
///
/// Occurs when message received
///
internal event EventHandler> ChannelWindowAdjustReceived;
///
/// Occurs when message received
///
internal event EventHandler> ChannelDataReceived;
///
/// Occurs when message received
///
internal event EventHandler> ChannelExtendedDataReceived;
///
/// Occurs when message received
///
internal event EventHandler> ChannelEofReceived;
///
/// Occurs when message received
///
internal event EventHandler> ChannelCloseReceived;
///
/// Occurs when message received
///
internal event EventHandler> ChannelRequestReceived;
///
/// Occurs when message received
///
internal event EventHandler> ChannelSuccessReceived;
///
/// Occurs when message received
///
internal 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.
/// is null.
internal Session(ConnectionInfo connectionInfo)
{
if (connectionInfo == null)
throw new ArgumentNullException("connectionInfo");
this.ConnectionInfo = connectionInfo;
//this.ClientVersion = string.Format(CultureInfo.CurrentCulture, "SSH-2.0-Renci.SshNet.SshClient.{0}", this.GetType().Assembly.GetName().Version);
this.ClientVersion = string.Format(CultureInfo.CurrentCulture, "SSH-2.0-Renci.SshNet.SshClient.0.0.1");
}
///
/// Connects to the server.
///
public void Connect()
{
if (this.IsConnected)
return;
try
{
AuthenticationConnection.Wait();
if (this.IsConnected)
return;
lock (this)
{
// If connected don't connect again
if (this.IsConnected)
return;
// reset connection specific information
Reset();
// Build list of available messages while connecting
this._messagesMetadata = GetMessagesMetadata();
switch (this.ConnectionInfo.ProxyType)
{
case ProxyTypes.None:
this.SocketConnect(this.ConnectionInfo.Host, this.ConnectionInfo.Port);
break;
case ProxyTypes.Socks4:
this.SocketConnect(this.ConnectionInfo.ProxyHost, this.ConnectionInfo.ProxyPort);
this.ConnectSocks4();
break;
case ProxyTypes.Socks5:
this.SocketConnect(this.ConnectionInfo.ProxyHost, this.ConnectionInfo.ProxyPort);
this.ConnectSocks5();
break;
case ProxyTypes.Http:
this.SocketConnect(this.ConnectionInfo.ProxyHost, this.ConnectionInfo.ProxyPort);
this.ConnectHttp();
break;
}
Match versionMatch;
// Get server version from the server,
// ignore text lines which are sent before if any
while (true)
{
string serverVersion = string.Empty;
this.SocketReadLine(ref serverVersion, ConnectionInfo.Timeout);
if (serverVersion == null)
throw new SshConnectionException("Server response does not contain SSH protocol identification.");
versionMatch = ServerVersionRe.Match(serverVersion);
if (versionMatch.Success)
{
this.ServerVersion = serverVersion;
break;
}
}
// Set connection versions
this.ConnectionInfo.ServerVersion = this.ServerVersion;
this.ConnectionInfo.ClientVersion = this.ClientVersion;
// Get server SSH version
var version = versionMatch.Result("${protoversion}");
var softwareName = versionMatch.Result("${softwareversion}");
this.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);
}
this.SocketWrite(Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0}\x0D\x0A", this.ClientVersion)));
// Register Transport response messages
this.RegisterMessage("SSH_MSG_DISCONNECT");
this.RegisterMessage("SSH_MSG_IGNORE");
this.RegisterMessage("SSH_MSG_UNIMPLEMENTED");
this.RegisterMessage("SSH_MSG_DEBUG");
this.RegisterMessage("SSH_MSG_SERVICE_ACCEPT");
this.RegisterMessage("SSH_MSG_KEXINIT");
this.RegisterMessage("SSH_MSG_NEWKEYS");
// Some server implementations might sent this message first, prior establishing encryption algorithm
this.RegisterMessage("SSH_MSG_USERAUTH_BANNER");
// Start incoming request listener
this._messageListenerCompleted = new ManualResetEvent(false);
this.ExecuteThread(() =>
{
try
{
this.MessageListener();
}
finally
{
this._messageListenerCompleted.Set();
}
});
// Wait for key exchange to be completed
this.WaitOnHandle(this._keyExchangeCompletedWaitHandle);
// If sessionId is not set then its not connected
if (this.SessionId == null)
{
this.Disconnect();
return;
}
// Request user authorization service
this.SendMessage(new ServiceRequestMessage(ServiceName.UserAuthentication));
// Wait for service to be accepted
this.WaitOnHandle(this._serviceAccepted);
if (string.IsNullOrEmpty(this.ConnectionInfo.Username))
{
throw new SshException("Username is not specified.");
}
this.ConnectionInfo.Authenticate(this);
this._isAuthenticated = true;
// Register Connection messages
this.RegisterMessage("SSH_MSG_GLOBAL_REQUEST");
this.RegisterMessage("SSH_MSG_REQUEST_SUCCESS");
this.RegisterMessage("SSH_MSG_REQUEST_FAILURE");
this.RegisterMessage("SSH_MSG_CHANNEL_OPEN_CONFIRMATION");
this.RegisterMessage("SSH_MSG_CHANNEL_OPEN_FAILURE");
this.RegisterMessage("SSH_MSG_CHANNEL_WINDOW_ADJUST");
this.RegisterMessage("SSH_MSG_CHANNEL_EXTENDED_DATA");
this.RegisterMessage("SSH_MSG_CHANNEL_REQUEST");
this.RegisterMessage("SSH_MSG_CHANNEL_SUCCESS");
this.RegisterMessage("SSH_MSG_CHANNEL_FAILURE");
this.RegisterMessage("SSH_MSG_CHANNEL_DATA");
this.RegisterMessage("SSH_MSG_CHANNEL_EOF");
this.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.");
}
private void Disconnect(DisconnectReason reason, string message)
{
this._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
SendDisconnect(reason, message);
// disconnect socket, and dispose it
SocketDisconnectAndDispose();
if (_messageListenerCompleted != null)
{
// at this point, we are sure that the listener thread will stop
// as we've disconnected the socket
_messageListenerCompleted.WaitOne();
_messageListenerCompleted.Dispose();
_messageListenerCompleted = null;
}
}
///
/// Create a new SSH session channel.
///
///
/// A new SSH session channel.
///
IChannelSession ISession.CreateChannelSession()
{
return CreateClientChannel();
}
///
/// Create a new client channel.
///
/// The type of the channel.
///
/// A new client channel.
///
internal T CreateClientChannel() where T : ClientChannel, new()
{
var channel = new T();
lock (this)
{
channel.Initialize(this, InitialLocalWindowSize, LocalChannelDataPacketSize);
}
return channel;
}
internal T CreateServerChannel(uint remoteChannelNumber, uint remoteWindowSize, uint remoteChannelDataPacketSize) where T : ServerChannel, new()
{
var channel = new T();
lock (this)
{
channel.Initialize(this, InitialLocalWindowSize, LocalChannelDataPacketSize, remoteChannelNumber, remoteWindowSize,
remoteChannelDataPacketSize);
}
return channel;
}
///
/// Sends "keep alive" message to keep connection alive.
///
internal void SendKeepAlive()
{
this.SendMessage(new IgnoreMessage());
}
///
/// 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.
///
/// When neither handles are signaled in time and the session is not closing, then the
/// session is disconnected.
///
internal void WaitOnHandle(WaitHandle waitHandle, TimeSpan timeout)
{
if (waitHandle == null)
throw new ArgumentNullException("waitHandle");
var waitHandles = new[]
{
this._exceptionWaitHandle,
this._messageListenerCompleted,
waitHandle
};
switch (WaitHandle.WaitAny(waitHandles, timeout))
{
case 0:
throw this._exception;
case 1:
// when the session is NOT disconnecting, the listener should actually
// never complete without setting the exception wait handle and should
// end up in case 0...
//
// when the session is disconnecting, the completion of the listener
// should not be considered an error (quite the oppposite actually)
if (!_isDisconnecting)
{
throw new SshConnectionException("Client not connected.");
}
break;
case WaitHandle.WaitTimeout:
// when the session is NOT disconnecting, then we want to disconnect
// the session altogether in case of a timeout, and throw a
// SshOperationTimeoutException
//
// 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
// disconnect while we're already disconnecting and we do not want
// to report an exception to the client when we're disconnecting
// anyway
if (!_isDisconnecting)
{
this.Disconnect(DisconnectReason.ByApplication, "Operation timeout");
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.
void ISession.SendMessage(Message message)
{
SendMessage(message);
}
///
/// 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 (this._socket == null || !this._socket.CanWrite())
throw new SshConnectionException("Client not connected.");
if (this._keyExchangeInProgress && !(message is IKeyExchangedAllowed))
{
// Wait for key exchange to be completed
this.WaitOnHandle(this._keyExchangeCompletedWaitHandle);
}
this.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 = this._clientCipher == null ? (byte)8 : Math.Max((byte)8, this._serverCipher.MinimumSize); // Should be recalculate base on cipher min length if cipher specified
var messageData = message.GetBytes();
if (this._clientCompression != null)
{
messageData = this._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);
// Lock handling of _outboundPacketSequence since it must be sent sequently to server
lock (this._socketLock)
{
if (this._socket == null || !this._socket.Connected)
throw new SshConnectionException("Client not connected.");
// Calculate packet hash
var hashData = new byte[4 + packetData.Length];
this._outboundPacketSequence.GetBytes().CopyTo(hashData, 0);
packetData.CopyTo(hashData, 4);
// Encrypt packet data
if (this._clientCipher != null)
{
packetData = this._clientCipher.Encrypt(packetData);
}
if (packetData.Length > MaximumSshPacketSize)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Packet is too big. Maximum packet size is {0} bytes.", MaximumSshPacketSize));
}
if (this._clientMac == null)
{
this.SocketWrite(packetData);
}
else
{
var hash = this._clientMac.ComputeHash(hashData.ToArray());
var data = new byte[packetData.Length + this._clientMac.HashSize / 8];
packetData.CopyTo(data, 0);
hash.CopyTo(data, packetData.Length);
this.SocketWrite(data);
}
this._outboundPacketSequence++;
Monitor.Pulse(this._socketLock);
}
}
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()
{
// No lock needed since all messages read by only one thread
var blockSize = this._serverCipher == null ? (byte)8 : Math.Max((byte)8, this._serverCipher.MinimumSize);
// Read packet length first
var firstBlock = this.Read(blockSize);
if (this._serverCipher != null)
{
firstBlock = this._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));
var data = new byte[bytesToRead + blockSize];
firstBlock.CopyTo(data, 0);
byte[] serverHash = null;
if (this._serverMac != null)
{
serverHash = new byte[this._serverMac.HashSize / 8];
bytesToRead += serverHash.Length;
}
if (bytesToRead > 0)
{
var nextBlocks = this.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 (this._serverCipher != null)
{
nextBlocks = this._serverCipher.Decrypt(nextBlocks);
}
nextBlocks.CopyTo(data, blockSize);
}
}
var paddingLength = data[4];
var messagePayload = new byte[packetLength - paddingLength - 1];
Buffer.BlockCopy(data, 5, messagePayload, 0, messagePayload.Length);
if (this._serverDecompression != null)
{
messagePayload = this._serverDecompression.Decompress(messagePayload);
}
// Validate message against MAC
if (this._serverMac != null)
{
var clientHashData = new byte[4 + data.Length];
var lengthBytes = this._inboundPacketSequence.GetBytes();
lengthBytes.CopyTo(clientHashData, 0);
data.CopyTo(clientHashData, 4);
// Calculate packet hash
var clientHash = this._serverMac.ComputeHash(clientHashData);
if (!serverHash.SequenceEqual(clientHash))
{
throw new SshConnectionException("MAC error", DisconnectReason.MacError);
}
}
this._inboundPacketSequence++;
return this.LoadMessage(messagePayload);
}
private void SendDisconnect(DisconnectReason reasonCode, string message)
{
// only send a disconnect message if it wasn't already sent, and we're
// still connected
if (this._isDisconnectMessageSent || !IsConnected)
return;
var disconnectMessage = new DisconnectMessage(reasonCode, message);
SendMessage(disconnectMessage);
this._isDisconnectMessageSent = true;
}
partial void HandleMessageCore(Message message);
///
/// Handles the message.
///
///
/// The message.
private void HandleMessage(T message) where T : Message
{
this.OnMessageReceived(message);
}
#region Handle transport messages
private void HandleMessage(DisconnectMessage message)
{
this.OnDisconnectReceived(message);
// disconnect from the socket, and dispose it
SocketDisconnectAndDispose();
}
private void HandleMessage(IgnoreMessage message)
{
this.OnIgnoreReceived(message);
}
private void HandleMessage(UnimplementedMessage message)
{
this.OnUnimplementedReceived(message);
}
private void HandleMessage(DebugMessage message)
{
this.OnDebugReceived(message);
}
private void HandleMessage(ServiceRequestMessage message)
{
this.OnServiceRequestReceived(message);
}
private void HandleMessage(ServiceAcceptMessage message)
{
// TODO: Refactor to avoid this method here
this.OnServiceAcceptReceived(message);
this._serviceAccepted.Set();
}
private void HandleMessage(KeyExchangeInitMessage message)
{
this.OnKeyExchangeInitReceived(message);
}
private void HandleMessage(NewKeysMessage message)
{
this.OnNewKeysReceived(message);
}
#endregion
#region Handle User Authentication messages
private void HandleMessage(RequestMessage message)
{
this.OnUserAuthenticationRequestReceived(message);
}
private void HandleMessage(FailureMessage message)
{
this.OnUserAuthenticationFailureReceived(message);
}
private void HandleMessage(SuccessMessage message)
{
this.OnUserAuthenticationSuccessReceived(message);
}
private void HandleMessage(BannerMessage message)
{
this.OnUserAuthenticationBannerReceived(message);
}
#endregion
#region Handle connection messages
private void HandleMessage(GlobalRequestMessage message)
{
this.OnGlobalRequestReceived(message);
}
private void HandleMessage(RequestSuccessMessage message)
{
this.OnRequestSuccessReceived(message);
}
private void HandleMessage(RequestFailureMessage message)
{
this.OnRequestFailureReceived(message);
}
private void HandleMessage(ChannelOpenMessage message)
{
this.OnChannelOpenReceived(message);
}
private void HandleMessage(ChannelOpenConfirmationMessage message)
{
this.OnChannelOpenConfirmationReceived(message);
}
private void HandleMessage(ChannelOpenFailureMessage message)
{
this.OnChannelOpenFailureReceived(message);
}
private void HandleMessage(ChannelWindowAdjustMessage message)
{
this.OnChannelWindowAdjustReceived(message);
}
private void HandleMessage(ChannelDataMessage message)
{
this.OnChannelDataReceived(message);
}
private void HandleMessage(ChannelExtendedDataMessage message)
{
this.OnChannelExtendedDataReceived(message);
}
private void HandleMessage(ChannelEofMessage message)
{
this.OnChannelEofReceived(message);
}
private void HandleMessage(ChannelCloseMessage message)
{
this.OnChannelCloseReceived(message);
}
private void HandleMessage(ChannelRequestMessage message)
{
this.OnChannelRequestReceived(message);
}
private void HandleMessage(ChannelSuccessMessage message)
{
this.OnChannelSuccessReceived(message);
}
private void HandleMessage(ChannelFailureMessage message)
{
this.OnChannelFailureReceived(message);
}
#endregion
#region Handle received message events
///
/// Called when received.
///
/// message.
protected virtual void OnDisconnectReceived(DisconnectMessage message)
{
this.Log(string.Format("Disconnect received: {0} {1}", message.ReasonCode, message.Description));
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)
{
this._keyExchangeInProgress = true;
this._keyExchangeCompletedWaitHandle.Reset();
// Disable all registered messages except key exchange related
foreach (var messageMetadata in this._messagesMetadata)
{
if (messageMetadata.Activated && messageMetadata.Number > 2 && (messageMetadata.Number < 20 || messageMetadata.Number > 30))
messageMetadata.Enabled = false;
}
var keyExchangeAlgorithmName = (from c in this.ConnectionInfo.KeyExchangeAlgorithms.Keys
from s in message.KeyExchangeAlgorithms
where s == c
select c).FirstOrDefault();
if (keyExchangeAlgorithmName == null)
{
throw new SshConnectionException("Failed to negotiate key exchange algorithm.", DisconnectReason.KeyExchangeFailed);
}
// Create instance of key exchange algorithm that will be used
this._keyExchange = this.ConnectionInfo.KeyExchangeAlgorithms[keyExchangeAlgorithmName].CreateInstance();
this.ConnectionInfo.CurrentKeyExchangeAlgorithm = keyExchangeAlgorithmName;
this._keyExchange.HostKeyReceived += KeyExchange_HostKeyReceived;
// Start the algorithm implementation
this._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 (this.SessionId == null)
{
this.SessionId = this._keyExchange.ExchangeHash;
}
// Dispose of old ciphers and hash algorithms
if (this._serverMac != null)
{
this._serverMac.Clear();
this._serverMac = null;
}
if (this._clientMac != null)
{
this._clientMac.Clear();
this._clientMac = null;
}
// Update negotiated algorithms
this._serverCipher = this._keyExchange.CreateServerCipher();
this._clientCipher = this._keyExchange.CreateClientCipher();
this._serverMac = this._keyExchange.CreateServerHash();
this._clientMac = this._keyExchange.CreateClientHash();
this._clientCompression = this._keyExchange.CreateCompressor();
this._serverDecompression = this._keyExchange.CreateDecompressor();
// Dispose of old KeyExchange object as it is no longer needed.
if (this._keyExchange != null)
{
this._keyExchange.HostKeyReceived -= KeyExchange_HostKeyReceived;
this._keyExchange.Dispose();
this._keyExchange = null;
}
// Enable all active registered messages
foreach (var messageMetadata in this._messagesMetadata)
{
if (messageMetadata.Activated)
messageMetadata.Enabled = true;
}
var newKeysReceived = NewKeysReceived;
if (newKeysReceived != null)
newKeysReceived(this, new MessageEventArgs(message));
// Signal that key exchange completed
this._keyExchangeCompletedWaitHandle.Set();
this._keyExchangeInProgress = false;
}
///
/// Called when client is disconnecting from the server.
///
internal void 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];
this.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)
{
this.InternalRegisterMessage(messageName);
}
///
/// Unregister SSH message from the session.
///
/// The name of the message to unregister with the session.
public void UnRegisterMessage(string messageName)
{
this.InternalUnRegisterMessage(messageName);
}
///
/// Loads the message.
///
/// Message data.
/// New message
private Message LoadMessage(byte[] data)
{
var messageType = data[0];
var messageMetadata = (from m in this._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);
this.Log(string.Format("ReceiveMessage from server: '{0}': '{1}'.", message.GetType().Name, message));
return message;
}
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.
partial void SocketWrite(byte[] data);
///
/// Disconnects and disposes the socket.
///
private void SocketDisconnectAndDispose()
{
if (_socket != null)
{
lock (_socketLock)
{
if (_socket != null)
{
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 (this._socket != null && this._socket.Connected)
{
var message = this.ReceiveMessage();
this.HandleMessageCore(message);
}
}
catch (Exception exp)
{
this.RaiseError(exp);
}
}
private byte SocketReadByte()
{
var buffer = new byte[1];
this.SocketRead(1, ref buffer);
return buffer[0];
}
private void SocketWriteByte(byte data)
{
this.SocketWrite(new[] {data});
}
private void ConnectSocks4()
{
// Send socks version number
this.SocketWriteByte(0x04);
// Send command code
this.SocketWriteByte(0x01);
// Send port
this.SocketWriteByte((byte)(this.ConnectionInfo.Port / 0xFF));
this.SocketWriteByte((byte)(this.ConnectionInfo.Port % 0xFF));
// Send IP
IPAddress ipAddress = this.ConnectionInfo.Host.GetIPAddress();
this.SocketWrite(ipAddress.GetAddressBytes());
// Send username
var username = new Common.ASCIIEncoding().GetBytes(this.ConnectionInfo.ProxyUsername);
this.SocketWrite(username);
this.SocketWriteByte(0x00);
// Read 0
if (this.SocketReadByte() != 0)
{
throw new ProxyException("SOCKS4: Null is expected.");
}
// Read response code
var code = this.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
this.SocketRead(2, ref dummyBuffer);
// Read 4 bytes to be ignored
this.SocketRead(4, ref dummyBuffer);
}
private void ConnectSocks5()
{
// Send socks version number
this.SocketWriteByte(0x05);
// Send number of supported authentication methods
this.SocketWriteByte(0x02);
// Send supported authentication methods
this.SocketWriteByte(0x00); // No authentication
this.SocketWriteByte(0x02); // Username/Password
var socksVersion = this.SocketReadByte();
if (socksVersion != 0x05)
throw new ProxyException(string.Format("SOCKS Version '{0}' is not supported.", socksVersion));
var authenticationMethod = this.SocketReadByte();
switch (authenticationMethod)
{
case 0x00:
break;
case 0x02:
// Send version
this.SocketWriteByte(0x01);
var encoding = new Common.ASCIIEncoding();
var username = encoding.GetBytes(this.ConnectionInfo.ProxyUsername);
if (username.Length > byte.MaxValue)
throw new ProxyException("Proxy username is too long.");
// Send username length
this.SocketWriteByte((byte)username.Length);
// Send username
this.SocketWrite(username);
var password = encoding.GetBytes(this.ConnectionInfo.ProxyPassword);
if (password.Length > byte.MaxValue)
throw new ProxyException("Proxy password is too long.");
// Send username length
this.SocketWriteByte((byte)password.Length);
// Send username
this.SocketWrite(password);
var serverVersion = this.SocketReadByte();
if (serverVersion != 1)
throw new ProxyException("SOCKS5: Server authentication version is not valid.");
var statusCode = this.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
this.SocketWriteByte(0x05);
// Send command code
this.SocketWriteByte(0x01); // establish a TCP/IP stream connection
// Send reserved, must be 0x00
this.SocketWriteByte(0x00);
IPAddress ip = this.ConnectionInfo.Host.GetIPAddress();
// Send address type and address
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
this.SocketWriteByte(0x01);
var address = ip.GetAddressBytes();
this.SocketWrite(address);
}
else if (ip.AddressFamily == AddressFamily.InterNetworkV6)
{
this.SocketWriteByte(0x04);
var address = ip.GetAddressBytes();
this.SocketWrite(address);
}
else
{
throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", ip));
}
// Send port
this.SocketWriteByte((byte)(this.ConnectionInfo.Port / 0xFF));
this.SocketWriteByte((byte)(this.ConnectionInfo.Port % 0xFF));
// Read Server SOCKS5 version
if (this.SocketReadByte() != 5)
{
throw new ProxyException("SOCKS5: Version 5 is expected.");
}
// Read response code
var status = this.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 (this.SocketReadByte() != 0)
{
throw new ProxyException("SOCKS5: 0 byte is expected.");
}
var addressType = this.SocketReadByte();
var responseIp = new byte[16];
switch (addressType)
{
case 0x01:
this.SocketRead(4, ref responseIp);
break;
case 0x04:
this.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
this.SocketRead(2, ref port);
}
private void ConnectHttp()
{
var httpResponseRe = new Regex(@"HTTP/(?\d[.]\d) (?\d{3}) (?.+)$");
var httpHeaderRe = new Regex(@"(?[^\[\]()<>@,;:\""/?={} \t]+):(?.+)?");
var encoding = new Common.ASCIIEncoding();
this.SocketWrite(encoding.GetBytes(string.Format("CONNECT {0}:{1} HTTP/1.0\r\n", this.ConnectionInfo.Host, this.ConnectionInfo.Port)));
// Sent proxy authorization is specified
if (!string.IsNullOrEmpty(this.ConnectionInfo.ProxyUsername))
{
var authorization = string.Format("Proxy-Authorization: Basic {0}\r\n",
Convert.ToBase64String(encoding.GetBytes(string.Format("{0}:{1}", this.ConnectionInfo.ProxyUsername, this.ConnectionInfo.ProxyPassword)))
);
this.SocketWrite(encoding.GetBytes(authorization));
}
this.SocketWrite(encoding.GetBytes("\r\n"));
HttpStatusCode? statusCode = null;
var response = string.Empty;
var contentLength = 0;
while (true)
{
this.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;
}
this._exception = exp;
this._exceptionWaitHandle.Set();
var errorOccured = ErrorOccured;
if (errorOccured != null)
errorOccured(this, new ExceptionEventArgs(exp));
if (connectionException != null && connectionException.DisconnectReason != DisconnectReason.ConnectionLost)
{
this.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.Reset();
SessionId = null;
_isDisconnectMessageSent = false;
_isDisconnecting = false;
_isAuthenticated = false;
_exception = null;
_keyExchangeInProgress = false;
}
#region IDisposable Members
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 (!this._disposed)
{
// If disposing equals true, dispose all managed
// and unmanaged ResourceMessages.
if (disposing)
{
Disconnect();
if (this._serviceAccepted != null)
{
this._serviceAccepted.Dispose();
this._serviceAccepted = null;
}
if (this._exceptionWaitHandle != null)
{
this._exceptionWaitHandle.Dispose();
this._exceptionWaitHandle = null;
}
if (this._keyExchangeCompletedWaitHandle != null)
{
this._keyExchangeCompletedWaitHandle.Dispose();
this._keyExchangeCompletedWaitHandle = null;
}
if (this._serverMac != null)
{
this._serverMac.Clear();
this._serverMac = null;
}
if (this._clientMac != null)
{
this._clientMac.Clear();
this._clientMac = null;
}
if (this._keyExchange != null)
{
this._keyExchange.HostKeyReceived -= KeyExchange_HostKeyReceived;
this._keyExchange.Dispose();
this._keyExchange = null;
}
if (_bytesReadFromSocket != null)
{
_bytesReadFromSocket.Dispose();
_bytesReadFromSocket = null;
}
}
// Note disposing has been done.
this._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
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; }
}
}
}