diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/Algorithm.cs b/Renci.SshClient/Renci.SshClient/Algorithms/Algorithm.cs
new file mode 100644
index 00000000..28644d9f
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Algorithms/Algorithm.cs
@@ -0,0 +1,7 @@
+namespace Renci.SshClient.Algorithms
+{
+ public abstract class Algorithm
+ {
+ public abstract string Name { get; }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/Compression.cs b/Renci.SshClient/Renci.SshClient/Algorithms/Compression.cs
new file mode 100644
index 00000000..c807fd4c
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Algorithms/Compression.cs
@@ -0,0 +1,23 @@
+namespace Renci.SshClient.Algorithms
+{
+ public abstract class Compression : Algorithm
+ {
+ private class CompressionNone : Compression
+ {
+
+ public override string Name
+ {
+ get { return "none"; }
+ }
+ }
+
+ static Compression()
+ {
+ Compression.None = new CompressionNone
+ {
+ };
+ }
+
+ public static Compression None { get; private set; }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchange.cs b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchange.cs
new file mode 100644
index 00000000..97ba8734
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchange.cs
@@ -0,0 +1,456 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Numerics;
+using System.Security.Cryptography;
+using Renci.SshClient.Common;
+using Renci.SshClient.Messages;
+using Renci.SshClient.Messages.Transport;
+
+namespace Renci.SshClient.Algorithms
+{
+ internal abstract class KeyExchange : Algorithm
+ {
+ ///
+ /// Creates the key exchange algorithm to be used for key exchange.
+ ///
+ /// The message.
+ ///
+ internal static KeyExchange Create(KeyExchangeInitMessage message, SessionInfo sessionInfo)
+ {
+
+ // TODO: Determine key exchange algorithm
+ var keyExchangeAlgorithm = (from s in message.KeyExchangeAlgorithms
+ from c in Settings.KeyExchangeAlgorithms.Keys
+ where s == c
+ select c).FirstOrDefault();
+
+ // TODO: If dont agree on algorithms then send disconnect message
+ if (keyExchangeAlgorithm == null)
+ {
+ throw new InvalidDataException("Failed to negotiate key exchange algorithm.");
+ }
+
+ return Settings.KeyExchangeAlgorithms[keyExchangeAlgorithm](sessionInfo);
+ }
+
+ ///
+ /// Specifies negotiated algorithm to encrypt information when sent to the server
+ ///
+ private Func _clientEncryptionAlgorithm;
+
+ ///
+ /// Specifies negotiated algorithm to decrypt information from the server
+ ///
+ private Func _serverDecryptionAlgorithm;
+
+ ///
+ /// Specifies negotiated HMAC algorithm to use for client
+ ///
+ private Func, HMAC> _clientHmacAlgorithm;
+
+ ///
+ /// Specifies negotiated HMAC algorithm to use for server
+ ///
+ private Func, HMAC> _serverHmacAlgorithm;
+
+ private IEnumerable _exchangeHash;
+ ///
+ /// Gets hash value
+ ///
+ public IEnumerable ExchangeHash
+ {
+ get
+ {
+ if (this._exchangeHash == null)
+ {
+ this._exchangeHash = this.CalculateHash();
+ }
+ return this._exchangeHash;
+ }
+ }
+
+ public HMAC ServerMac { get; set; }
+
+ public HMAC ClientMac { get; set; }
+
+ public ICryptoTransform Encryption { get; set; }
+
+ public ICryptoTransform Decryption { get; set; }
+
+ public Compression ServerDecompression { get; set; }
+
+ public Compression ClientCompression { get; set; }
+
+ public bool IsCompleted { get; protected set; }
+
+ public bool IsSuccessed { get; protected set; }
+
+ protected SessionInfo SessionInfo { get; private set; }
+
+ protected string ClientPayload { get; set; }
+
+ protected string ServerPayload { get; set; }
+
+ protected string HostKey { get; set; }
+
+ protected BigInteger ClientExchangeValue { get; set; }
+
+ protected BigInteger ServerExchangeValue { get; set; }
+
+ protected BigInteger SharedKey { get; set; }
+
+ protected string Signature { get; set; }
+
+ public event EventHandler Completed;
+
+ public event EventHandler Failed;
+
+ public KeyExchange(SessionInfo sessionInfo)
+ {
+ this.SessionInfo = sessionInfo;
+ this.ServerDecompression = Compression.None;
+ this.ClientCompression = Compression.None;
+ }
+
+ public virtual void Start()
+ {
+ // TODO: If key exchange initiated by the client no need to send client message again
+ var clientMessage = new KeyExchangeInitMessage()
+ {
+ KeyExchangeAlgorithms = Settings.KeyExchangeAlgorithms.Keys,
+ ServerHostKeyAlgorithms = Settings.HostKeyAlgorithms.Keys,
+ EncryptionAlgorithmsClientToServer = Settings.Encryptions.Keys,
+ EncryptionAlgorithmsServerToClient = Settings.Encryptions.Keys,
+ MacAlgorithmsClientToSserver = Settings.HmacAlgorithms.Keys,
+ MacAlgorithmsServerToClient = Settings.HmacAlgorithms.Keys,
+ CompressionAlgorithmsClientToServer = new string[] { "none" },
+ CompressionAlgorithmsServerToClient = new string[] { "none" },
+ LanguagesClientToServer = new string[] { string.Empty },
+ LanguagesServerToClient = new string[] { string.Empty },
+ FirstKexPacketFollows = false,
+ Reserved = 0,
+ };
+
+ this.ClientPayload = clientMessage.GetBytes().GetSshString();
+
+ this.SendMessage(clientMessage);
+ }
+
+ public virtual void Start(KeyExchangeInitMessage message)
+ {
+ this.Start();
+
+ // Determine encryption algorithm
+ var clientEncryptionAlgorithmName = (from a in message.EncryptionAlgorithmsClientToServer
+ from b in Settings.Encryptions.Keys
+ where a == b
+ select a).FirstOrDefault();
+ if (string.IsNullOrEmpty(clientEncryptionAlgorithmName))
+ {
+ throw new InvalidOperationException("Client encryption algorithm not found");
+ }
+ this._clientEncryptionAlgorithm = Settings.Encryptions[clientEncryptionAlgorithmName];
+
+ // Determine encryption algorithm
+ var serverDecryptionAlgorithmName = (from a in message.EncryptionAlgorithmsServerToClient
+ from b in Settings.Encryptions.Keys
+ where a == b
+ select a).FirstOrDefault();
+ if (string.IsNullOrEmpty(serverDecryptionAlgorithmName))
+ {
+ throw new InvalidOperationException("Server decryption algorithm not found");
+ }
+ this._serverDecryptionAlgorithm = Settings.Encryptions[clientEncryptionAlgorithmName];
+
+ // Determine client hmac algorithm
+ var clientHmacAlgorithmName = (from a in message.MacAlgorithmsClientToSserver
+ from b in Settings.HmacAlgorithms.Keys
+ where a == b
+ select a).FirstOrDefault();
+ if (string.IsNullOrEmpty(clientHmacAlgorithmName))
+ {
+ throw new InvalidOperationException("Server HMAC algorithm not found");
+ }
+ this._clientHmacAlgorithm = Settings.HmacAlgorithms[clientHmacAlgorithmName];
+
+ // Determine server hmac algorithm
+ var serverHmacAlgorithmName = (from a in message.MacAlgorithmsServerToClient
+ from b in Settings.HmacAlgorithms.Keys
+ where a == b
+ select a).FirstOrDefault();
+ if (string.IsNullOrEmpty(serverHmacAlgorithmName))
+ {
+ throw new InvalidOperationException("Server HMAC algorithm not found");
+ }
+ this._serverHmacAlgorithm = Settings.HmacAlgorithms[serverHmacAlgorithmName];
+
+ }
+
+ public virtual void Finish()
+ {
+ // TODO: Validate that all required properties are set
+ if (this.SessionInfo.SessionId == null)
+ {
+ this.SessionInfo.SessionId = this.ExchangeHash;
+ }
+
+ // Set encryption
+ ICryptoTransform encryption;
+ using (var clientAlgorithm = this._clientEncryptionAlgorithm())
+ {
+ // Calculate client to server initial IV
+ var clientValue = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'A', this.SessionInfo.SessionId));
+
+ // Calculate client to server encryption
+ var clientKey = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'C', this.SessionInfo.SessionId));
+
+ clientKey = this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, clientKey, clientAlgorithm.KeySize / 8);
+
+ clientAlgorithm.Mode = System.Security.Cryptography.CipherMode.CBC;
+ clientAlgorithm.Padding = System.Security.Cryptography.PaddingMode.None;
+
+ encryption = clientAlgorithm.CreateEncryptor(clientKey.Take(clientAlgorithm.KeySize / 8).ToArray(), clientValue.Take(clientAlgorithm.BlockSize / 8).ToArray());
+ }
+
+ // Set decryption
+ ICryptoTransform decryption;
+ using (var serverAlgorithm = this._serverDecryptionAlgorithm())
+ {
+ // Calculate server to client initial IV
+ var serverValue = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'B', this.SessionInfo.SessionId));
+
+ // Calculate server to client encryption
+ var serverKey = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'D', this.SessionInfo.SessionId));
+
+ serverKey = this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, serverKey, serverAlgorithm.KeySize / 8);
+
+ serverAlgorithm.Mode = System.Security.Cryptography.CipherMode.CBC;
+ serverAlgorithm.Padding = System.Security.Cryptography.PaddingMode.None;
+
+ decryption = serverAlgorithm.CreateDecryptor(serverKey.Take(serverAlgorithm.KeySize / 8).ToArray(), serverValue.Take(serverAlgorithm.BlockSize / 8).ToArray());
+ }
+
+ // Calculate client to server integrity
+ var MACc2s = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'E', this.SessionInfo.SessionId));
+ var clientMac = this._clientHmacAlgorithm(MACc2s);
+
+ // Calculate server to client integrity
+ var MACs2c = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'F', this.SessionInfo.SessionId));
+ var serverMac = this._serverHmacAlgorithm(MACs2c);
+
+ // TODO: Create compression and decompression objects if any
+
+ this.Decryption = decryption;
+ this.Encryption = encryption;
+ this.ServerDecompression = Compression.None;
+ this.ClientCompression = Compression.None;
+ this.ServerMac = serverMac;
+ this.ClientMac = clientMac;
+
+ this.IsCompleted = true;
+ this.RaiseCompleted();
+ }
+
+ ///
+ /// Raises the Completed event.
+ ///
+ /// The session id.
+ /// The decryption to be used.
+ /// The encryption to be used.
+ /// The server decompression.
+ /// The client compression.
+ /// The server mac.
+ /// The client mac.
+ protected void RaiseCompleted()
+ {
+ if (this.Completed != null)
+ {
+ this.Completed(this, new KeyExchangeCompletedEventArgs());
+ }
+ }
+
+ ///
+ /// Raises the Failed event.
+ ///
+ /// The fail reason message.
+ protected void RaiseFailed(string message)
+ {
+ if (this.Failed != null)
+ {
+ this.Failed(this, new KeyExchangeFailedEventArgs(message));
+ }
+ }
+
+ protected virtual IEnumerable Hash(IEnumerable hashBytes)
+ {
+ using (var md = new System.Security.Cryptography.SHA1CryptoServiceProvider())
+ {
+ using (var cs = new System.Security.Cryptography.CryptoStream(System.IO.Stream.Null, md, System.Security.Cryptography.CryptoStreamMode.Write))
+ {
+ var hashData = hashBytes.ToArray();
+ cs.Write(hashData, 0, hashData.Length);
+ cs.Close();
+ return md.Hash;
+ }
+ }
+ }
+
+ protected bool ValidateExchangeHash()
+ {
+ var bytes = this.HostKey.GetSshBytes();
+
+ var length = BitConverter.ToUInt32(bytes.Take(4).Reverse().ToArray(), 0);
+
+ var algorithmName = bytes.Skip(4).Take((int)length).GetSshString();
+
+ var data = bytes.Skip(4 + algorithmName.Length);
+
+ var signature = Settings.HostKeyAlgorithms[algorithmName](data);
+
+ return signature.ValidateSignature(this.ExchangeHash, this.Signature.GetSshBytes());
+ }
+
+ protected void SendMessage(Message message)
+ {
+ this.SessionInfo.SendMessage(message);
+ }
+
+ private IEnumerable CalculateHash()
+ {
+ var hashData = new _ExchangeHashData
+ {
+ ClientVersion = this.SessionInfo.ClientVersion,
+ ServerVersion = this.SessionInfo.ServerVersion,
+ ClientPayload = this.ClientPayload,
+ ServerPayload = this.ServerPayload,
+ HostKey = this.HostKey,
+ ClientExchangeValue = this.ClientExchangeValue,
+ ServerExchangeValue = this.ServerExchangeValue,
+ SharedKey = this.SharedKey,
+ }.GetBytes();
+
+ return this.Hash(hashData);
+ }
+
+ private IEnumerable GenerateSessionKey(BigInteger sharedKey, IEnumerable exchangeHash, IEnumerable key, int size)
+ {
+ var result = new List(key);
+ while (size > result.Count)
+ {
+ result.AddRange(this.Hash(new _SessionKeyAdjustment
+ {
+ SharedKey = sharedKey,
+ ExcahngeHash = exchangeHash,
+ Key = key,
+ }.GetBytes()));
+ }
+
+ return result;
+ }
+
+ private IEnumerable GenerateSessionKey(BigInteger sharedKey, IEnumerable exchangeHash, char p, IEnumerable sessionId)
+ {
+ return new _SessionKeyGeneration
+ {
+ SharedKey = sharedKey,
+ ExchangeHash = exchangeHash,
+ Char = p,
+ SessionId = sessionId,
+ }.GetBytes();
+ }
+
+ private class _ExchangeHashData : SshData
+ {
+ public string ServerVersion { get; set; }
+
+ public string ClientVersion { get; set; }
+
+ public string ClientPayload { get; set; }
+
+ public string ServerPayload { get; set; }
+
+ public string HostKey { get; set; }
+
+ public UInt32? MinimumGroupSize { get; set; }
+
+ public UInt32? PreferredGroupSize { get; set; }
+
+ public UInt32? MaximumGroupSize { get; set; }
+
+ public IEnumerable Prime { get; set; }
+
+ public BigInteger ClientExchangeValue { get; set; }
+
+ public BigInteger ServerExchangeValue { get; set; }
+
+ public BigInteger SharedKey { get; set; }
+
+ protected override void LoadData()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ protected override void SaveData()
+ {
+ this.Write(this.ClientVersion);
+ this.Write(this.ServerVersion);
+ this.Write(this.ClientPayload);
+ this.Write(this.ServerPayload);
+ this.Write(this.HostKey);
+ if (this.MinimumGroupSize.HasValue)
+ this.Write(this.MinimumGroupSize.Value);
+ if (this.PreferredGroupSize.HasValue)
+ this.Write(this.PreferredGroupSize.Value);
+ if (this.MaximumGroupSize.HasValue)
+ this.Write(this.MaximumGroupSize.Value);
+ if (this.Prime != null)
+ this.Write(this.Prime);
+ this.Write(this.ClientExchangeValue);
+ this.Write(this.ServerExchangeValue);
+ this.Write(this.SharedKey);
+ }
+ }
+
+ private class _SessionKeyGeneration : SshData
+ {
+ public BigInteger SharedKey { get; set; }
+ public IEnumerable ExchangeHash { get; set; }
+ public char Char { get; set; }
+ public IEnumerable SessionId { get; set; }
+
+ protected override void LoadData()
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override void SaveData()
+ {
+ this.Write(this.SharedKey);
+ this.Write(this.ExchangeHash);
+ this.Write((byte)this.Char);
+ this.Write(this.SessionId);
+ }
+ }
+
+ private class _SessionKeyAdjustment : SshData
+ {
+ public BigInteger SharedKey { get; set; }
+ public IEnumerable ExcahngeHash { get; set; }
+ public IEnumerable Key { get; set; }
+
+ protected override void LoadData()
+ {
+ throw new NotImplementedException();
+ }
+
+ protected override void SaveData()
+ {
+ this.Write(this.SharedKey);
+ this.Write(this.ExcahngeHash);
+ this.Write(this.Key);
+ }
+ }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeCompletedEventArgs.cs b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeCompletedEventArgs.cs
new file mode 100644
index 00000000..af65f644
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeCompletedEventArgs.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace Renci.SshClient.Algorithms
+{
+ internal class KeyExchangeCompletedEventArgs : EventArgs
+ {
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeDiffieHellman.cs b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeDiffieHellman.cs
new file mode 100644
index 00000000..d1f00a35
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeDiffieHellman.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Linq;
+using System.Numerics;
+using System.Security.Cryptography;
+using Renci.SshClient.Common;
+using Renci.SshClient.Messages;
+using Renci.SshClient.Messages.Transport;
+
+namespace Renci.SshClient.Algorithms
+{
+ internal class KeyExchangeDiffieHellman : KeyExchange
+ {
+ private static RNGCryptoServiceProvider _randomizer = new System.Security.Cryptography.RNGCryptoServiceProvider();
+
+ private static BigInteger _prime = new BigInteger(new byte[] { (byte)0x00,
+ (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,
+ (byte)0xC9,(byte)0x0F,(byte)0xDA,(byte)0xA2,(byte)0x21,(byte)0x68,(byte)0xC2,(byte)0x34,
+ (byte)0xC4,(byte)0xC6,(byte)0x62,(byte)0x8B,(byte)0x80,(byte)0xDC,(byte)0x1C,(byte)0xD1,
+ (byte)0x29,(byte)0x02,(byte)0x4E,(byte)0x08,(byte)0x8A,(byte)0x67,(byte)0xCC,(byte)0x74,
+ (byte)0x02,(byte)0x0B,(byte)0xBE,(byte)0xA6,(byte)0x3B,(byte)0x13,(byte)0x9B,(byte)0x22,
+ (byte)0x51,(byte)0x4A,(byte)0x08,(byte)0x79,(byte)0x8E,(byte)0x34,(byte)0x04,(byte)0xDD,
+ (byte)0xEF,(byte)0x95,(byte)0x19,(byte)0xB3,(byte)0xCD,(byte)0x3A,(byte)0x43,(byte)0x1B,
+ (byte)0x30,(byte)0x2B,(byte)0x0A,(byte)0x6D,(byte)0xF2,(byte)0x5F,(byte)0x14,(byte)0x37,
+ (byte)0x4F,(byte)0xE1,(byte)0x35,(byte)0x6D,(byte)0x6D,(byte)0x51,(byte)0xC2,(byte)0x45,
+ (byte)0xE4,(byte)0x85,(byte)0xB5,(byte)0x76,(byte)0x62,(byte)0x5E,(byte)0x7E,(byte)0xC6,
+ (byte)0xF4,(byte)0x4C,(byte)0x42,(byte)0xE9,(byte)0xA6,(byte)0x37,(byte)0xED,(byte)0x6B,
+ (byte)0x0B,(byte)0xFF,(byte)0x5C,(byte)0xB6,(byte)0xF4,(byte)0x06,(byte)0xB7,(byte)0xED,
+ (byte)0xEE,(byte)0x38,(byte)0x6B,(byte)0xFB,(byte)0x5A,(byte)0x89,(byte)0x9F,(byte)0xA5,
+ (byte)0xAE,(byte)0x9F,(byte)0x24,(byte)0x11,(byte)0x7C,(byte)0x4B,(byte)0x1F,(byte)0xE6,
+ (byte)0x49,(byte)0x28,(byte)0x66,(byte)0x51,(byte)0xEC,(byte)0xE6,(byte)0x53,(byte)0x81,
+ (byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF}.Reverse().ToArray());
+
+ private static BigInteger _group = new BigInteger(new byte[] { 2 });
+
+ private BigInteger _randomValue;
+
+ public override string Name
+ {
+ get { return "diffie-hellman-group1-sha1"; }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The session information.
+ internal KeyExchangeDiffieHellman(SessionInfo sessionInfo)
+ : base(sessionInfo)
+ {
+ }
+
+ public override void Start(KeyExchangeInitMessage message)
+ {
+ base.Start(message);
+
+ // TODO: Calculate random value correctly, enforce limits
+ var clientExchangeValue = BigInteger.Zero;
+ while (clientExchangeValue < 1 || clientExchangeValue > ((KeyExchangeDiffieHellman._prime - 1) / 2))
+ {
+ this._randomValue = new BigInteger(new Random().NextDouble() * long.MaxValue);
+ clientExchangeValue = System.Numerics.BigInteger.ModPow(KeyExchangeDiffieHellman._group, this._randomValue, KeyExchangeDiffieHellman._prime);
+ }
+
+ this.ServerPayload = message.GetBytes().GetSshString();
+
+ this.ClientExchangeValue = clientExchangeValue;
+
+ // Register expected message replies
+ Message.RegisterMessageType(MessageTypes.KeyExchangeDhReply);
+
+ this.SendMessage(new KeyExchangeDhInitMessage
+ {
+ E = this.ClientExchangeValue,
+ });
+
+ this.SessionInfo.MessageReceived += SessionInfo_MessageReceived;
+
+ }
+
+ public override void Finish()
+ {
+ base.Finish();
+
+ this.SessionInfo.MessageReceived -= SessionInfo_MessageReceived;
+ }
+
+ private void SessionInfo_MessageReceived(object sender, MessageReceivedEventArgs e)
+ {
+ this.HandleMessage((dynamic)e.Message);
+ }
+
+ private void HandleMessage(T message) where T : Message, new()
+ {
+ // Do nothing, handle only known messages
+ }
+
+ ///
+ /// Handles the KeyExchangeDhReplyMessage message.
+ ///
+ /// The message.
+ private void HandleMessage(KeyExchangeDhReplyMessage message)
+ {
+ // Unregister message once received
+ Message.UnRegisterMessageType(MessageTypes.KeyExchangeDhReply);
+
+ var sharedKey = System.Numerics.BigInteger.ModPow(message.F, this._randomValue, KeyExchangeDiffieHellman._prime);
+
+ this.ServerExchangeValue = message.F;
+ this.HostKey = message.HostKey;
+ this.SharedKey = sharedKey;
+ this.Signature = message.Signature;
+
+ // Validate hash value
+ if (this.ValidateExchangeHash())
+ {
+ this.IsSuccessed = true;
+ this.SendMessage(new NewKeysMessage());
+ }
+ else
+ {
+ this.IsSuccessed = false;
+ this.RaiseFailed("Key negotiationed failed.");
+ }
+ }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeFailedEventArgs.cs b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeFailedEventArgs.cs
new file mode 100644
index 00000000..384b47fe
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeFailedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Renci.SshClient.Algorithms
+{
+ internal class KeyExchangeFailedEventArgs : EventArgs
+ {
+ public string Message { get; private set; }
+
+ public KeyExchangeFailedEventArgs(string message)
+ {
+ this.Message = message;
+ }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeSendMessageEventArgs.cs b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeSendMessageEventArgs.cs
new file mode 100644
index 00000000..34e162b1
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeSendMessageEventArgs.cs
@@ -0,0 +1,15 @@
+using System;
+using Renci.SshClient.Messages;
+
+namespace Renci.SshClient.Algorithms
+{
+ internal class KeyExchangeSendMessageEventArgs : EventArgs
+ {
+ public KeyExchangeSendMessageEventArgs(Message message)
+ {
+ this.Message = message;
+ }
+
+ public Message Message { get; private set; }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/Signature.cs b/Renci.SshClient/Renci.SshClient/Algorithms/Signature.cs
new file mode 100644
index 00000000..0ba52e15
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Algorithms/Signature.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+namespace Renci.SshClient.Algorithms
+{
+ internal abstract class Signature : Algorithm
+ {
+ protected IEnumerable Data { get; private set; }
+
+ public Signature(IEnumerable data)
+ {
+ this.Data = data;
+ }
+
+ public abstract bool ValidateSignature(IEnumerable hash, IEnumerable signature);
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/SignatureDss.cs b/Renci.SshClient/Renci.SshClient/Algorithms/SignatureDss.cs
new file mode 100644
index 00000000..93d85178
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Algorithms/SignatureDss.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+
+namespace Renci.SshClient.Algorithms
+{
+ internal class SignatureDss : Signature
+ {
+ public override string Name
+ {
+ get { return "ssh-dss"; }
+ }
+
+ public SignatureDss(IEnumerable data)
+ : base(data)
+ {
+
+ }
+
+ public override bool ValidateSignature(IEnumerable hash, IEnumerable signature)
+ {
+ var pLength = BitConverter.ToUInt32(this.Data.Take(4).Reverse().ToArray(), 0);
+
+ var pData = this.Data.Skip(4).Take((int)pLength).ToArray();
+
+ var qLength = BitConverter.ToUInt32(this.Data.Skip(4 + (int)pLength).Take(4).Reverse().ToArray(), 0);
+
+ var qData = this.Data.Skip(4 + (int)pLength + 4).Take((int)qLength).ToArray();
+
+ var gLength = BitConverter.ToUInt32(this.Data.Skip(4 + (int)pLength + 4 + (int)qLength).Take(4).Reverse().ToArray(), 0);
+
+ var gData = this.Data.Skip(4 + (int)pLength + 4 + (int)qLength + 4).Take((int)gLength).ToArray();
+
+ var xLength = BitConverter.ToUInt32(this.Data.Skip(4 + (int)pLength + 4 + (int)qLength + 4 + (int)gLength).Take(4).Reverse().ToArray(), 0);
+
+ var xData = this.Data.Skip(4 + (int)pLength + 4 + (int)qLength + 4 + (int)xLength + 4).Take((int)xLength).ToArray();
+
+ using (var sha1 = new SHA1CryptoServiceProvider())
+ {
+ using (var cs = new CryptoStream(System.IO.Stream.Null, sha1, CryptoStreamMode.Write))
+ {
+ var data = hash.ToArray();
+ cs.Write(data, 0, data.Length);
+ cs.Close();
+ }
+
+ using (var dsa = new DSACryptoServiceProvider())
+ {
+ dsa.ImportParameters(new DSAParameters
+ {
+ X = xData.TrimLeadinZero().ToArray(),
+ P = pData.TrimLeadinZero().ToArray(),
+ Q = qData.TrimLeadinZero().ToArray(),
+ G = gData.TrimLeadinZero().ToArray(),
+ });
+ var dsaDeformatter = new DSASignatureDeformatter(dsa);
+ dsaDeformatter.SetHashAlgorithm("SHA1");
+
+ long i = 0;
+ long j = 0;
+ byte[] tmp;
+
+ var sig = signature.ToArray();
+ if (sig[0] == 0 && sig[1] == 0 && sig[2] == 0)
+ {
+ long i1 = (sig[i++] << 24) & 0xff000000;
+ long i2 = (sig[i++] << 16) & 0x00ff0000;
+ long i3 = (sig[i++] << 8) & 0x0000ff00;
+ long i4 = (sig[i++]) & 0x000000ff;
+ j = i1 | i2 | i3 | i4;
+
+ i += j;
+
+ i1 = (sig[i++] << 24) & 0xff000000;
+ i2 = (sig[i++] << 16) & 0x00ff0000;
+ i3 = (sig[i++] << 8) & 0x0000ff00;
+ i4 = (sig[i++]) & 0x000000ff;
+ j = i1 | i2 | i3 | i4;
+
+ tmp = new byte[j];
+ Array.Copy(sig, i, tmp, 0, j);
+ sig = tmp;
+ }
+
+ return dsaDeformatter.VerifySignature(sha1, sig);
+ }
+ }
+ }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/SignatureRsa.cs b/Renci.SshClient/Renci.SshClient/Algorithms/SignatureRsa.cs
new file mode 100644
index 00000000..bf7adf66
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Algorithms/SignatureRsa.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+
+namespace Renci.SshClient.Algorithms
+{
+ internal class SignatureRsa : Signature
+ {
+ public override string Name
+ {
+ get { return "ssh-rsa"; }
+ }
+
+ public SignatureRsa(IEnumerable data)
+ : base(data)
+ {
+
+ }
+
+ public override bool ValidateSignature(IEnumerable hash, IEnumerable signature)
+ {
+ var exponentLength = BitConverter.ToUInt32(this.Data.Take(4).Reverse().ToArray(), 0);
+
+ var exponentData = this.Data.Skip(4).Take((int)exponentLength).ToArray();
+
+ var modulusLength = BitConverter.ToUInt32(this.Data.Skip(4 + (int)exponentLength).Take(4).Reverse().ToArray(), 0);
+
+ var modulusData = this.Data.Skip(4 + (int)exponentLength + 4).Take((int)modulusLength).ToArray();
+
+ using (var sha1 = new SHA1CryptoServiceProvider())
+ {
+ using (var cs = new CryptoStream(System.IO.Stream.Null, sha1, CryptoStreamMode.Write))
+ {
+ var data = hash.ToArray();
+ cs.Write(data, 0, data.Length);
+ cs.Close();
+ }
+
+ using (var rsa = new RSACryptoServiceProvider())
+ {
+ rsa.ImportParameters(new RSAParameters
+ {
+ Exponent = exponentData,
+ Modulus = modulusData.TrimLeadinZero().ToArray(),
+ });
+ var rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
+ rsaDeformatter.SetHashAlgorithm("SHA1");
+
+ long i = 0;
+ long j = 0;
+ byte[] tmp;
+
+ var sig = signature.ToArray();
+ if (sig[0] == 0 && sig[1] == 0 && sig[2] == 0)
+ {
+ long i1 = (sig[i++] << 24) & 0xff000000;
+ long i2 = (sig[i++] << 16) & 0x00ff0000;
+ long i3 = (sig[i++] << 8) & 0x0000ff00;
+ long i4 = (sig[i++]) & 0x000000ff;
+ j = i1 | i2 | i3 | i4;
+
+ i += j;
+
+ i1 = (sig[i++] << 24) & 0xff000000;
+ i2 = (sig[i++] << 16) & 0x00ff0000;
+ i3 = (sig[i++] << 8) & 0x0000ff00;
+ i4 = (sig[i++]) & 0x000000ff;
+ j = i1 | i2 | i3 | i4;
+
+ tmp = new byte[j];
+ Array.Copy(sig, i, tmp, 0, j);
+ sig = tmp;
+ }
+
+ return rsaDeformatter.VerifySignature(sha1, sig);
+ }
+ }
+ }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs
new file mode 100644
index 00000000..8037f966
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs
@@ -0,0 +1,257 @@
+
+using System;
+using System.Text;
+using System.Threading;
+using Renci.SshClient.Common;
+using Renci.SshClient.Messages;
+using Renci.SshClient.Messages.Connection;
+namespace Renci.SshClient.Channels
+{
+ internal abstract class Channel
+ {
+ private static uint _channelCounter = 0;
+
+ private static object _lock = new object();
+
+ private EventWaitHandle _channelOpenWaitHandle = new AutoResetEvent(false);
+
+ private EventWaitHandle _channelClosedWaitHandle = new AutoResetEvent(false);
+
+ private uint _initialWindowSize = 0x100000;
+
+ //private uint _maximumPacketSize = 0x4000;
+ private uint _maximumPacketSize = 1024;
+
+ protected StringBuilder ChannelData { get; private set; }
+
+ protected StringBuilder ChannelExtendedData { get; private set; }
+
+ public abstract ChannelTypes ChannelType { get; }
+
+ public uint ClientChannelNumber { get; set; }
+
+ public uint ServerChannelNumber { get; set; }
+
+ public uint WindowSize { get; set; }
+
+ public uint PacketSize { get; set; }
+
+ public bool IsOpen { get; protected set; }
+
+ protected SessionInfo SessionInfo { get; private set; }
+
+ public Channel(SessionInfo sessionInfo, uint windowSize, uint packetSize)
+ {
+ this._initialWindowSize = windowSize;
+ this._maximumPacketSize = Math.Max(packetSize, 0x8000); // Ensure minimum maximum packet size of 0x8000 bytes
+
+ Message.RegisterMessageType(MessageTypes.ChannelOpenConfirmation);
+ Message.RegisterMessageType(MessageTypes.ChannelOpenFailure);
+ Message.RegisterMessageType(MessageTypes.ChannelWindowAdjust);
+ Message.RegisterMessageType(MessageTypes.ChannelExtendedData);
+ Message.RegisterMessageType(MessageTypes.ChannelRequest);
+ Message.RegisterMessageType(MessageTypes.ChannelSuccess);
+ Message.RegisterMessageType(MessageTypes.ChannelData);
+ Message.RegisterMessageType(MessageTypes.ChannelEof);
+ Message.RegisterMessageType(MessageTypes.ChannelClose);
+
+ lock (_lock)
+ {
+ // TODO: Refactor to make channel number to come from the session, to avoid situation where new session will be open and first channel number will not be 0
+ this.ClientChannelNumber = _channelCounter++;
+ }
+
+ this.SessionInfo = sessionInfo;
+ this.ChannelData = new StringBuilder((int)this._initialWindowSize);
+ this.ChannelExtendedData = new StringBuilder((int)this._initialWindowSize);
+ this.WindowSize = this._initialWindowSize; // Initial window size
+ this.PacketSize = this._maximumPacketSize; // Maximum packet size
+ }
+
+ public Channel(SessionInfo sessionInfo)
+ : this(sessionInfo, 0x100000, 0x8000)
+ {
+ }
+
+ public virtual void Open()
+ {
+ this.SessionInfo.MessageReceived += SessionInfo_MessageReceived;
+
+ // Open session channel
+ if (!this.IsOpen)
+ {
+ this.SendMessage(new ChannelOpenMessage
+ {
+ ChannelName = "session",
+ ChannelNumber = this.ClientChannelNumber,
+ InitialWindowSize = this.WindowSize,
+ MaximumPacketSize = this.PacketSize,
+ });
+
+ this.SessionInfo.WaitHandle(this._channelOpenWaitHandle);
+ }
+ }
+
+ public virtual void Close()
+ {
+ if (this.IsOpen)
+ {
+ this.SendMessage(new ChannelCloseMessage
+ {
+ ChannelNumber = this.ServerChannelNumber,
+ });
+
+ // Wait for channel to be closed
+ this.SessionInfo.WaitHandle(this._channelClosedWaitHandle);
+ }
+
+ this.CloseCleanup();
+ }
+
+ protected virtual void OnChannelData(string data)
+ {
+ }
+
+ protected virtual void OnChannelExtendedData(string data, uint dataTypeCode)
+ {
+ }
+
+ protected virtual void OnChannelSuccess()
+ {
+ }
+
+ protected virtual void OnChannelEof()
+ {
+ }
+
+ protected virtual void OnChannelClose()
+ {
+ }
+
+ protected void SendMessage(Message message)
+ {
+ this.SessionInfo.SendMessage(message);
+ }
+
+ private void SessionInfo_MessageReceived(object sender, MessageReceivedEventArgs e)
+ {
+ ChannelMessage message = e.Message as ChannelMessage;
+
+ // Handle only messages belong to this channel or channel open confirmation
+ if (message.ChannelNumber == this.ClientChannelNumber || e.Message is ChannelOpenConfirmationMessage)
+ {
+ this.HandleMessage((dynamic)e.Message);
+ }
+ }
+
+ #region Message handlers
+
+ private void HandleMessage(T message) where T : Message
+ {
+ throw new NotSupportedException(string.Format("Message type '{0}' is not supported.", message.MessageType));
+ }
+
+ private void HandleMessage(ChannelOpenConfirmationMessage message)
+ {
+ // Make sure we open channel only for requested channel number
+ if (this.ClientChannelNumber != message.ChannelNumber)
+ return;
+
+ this.ServerChannelNumber = message.ServerChannelNumber;
+ this.IsOpen = true;
+ this.WindowSize = message.InitialWindowSize;
+ this.PacketSize = message.MaximumPacketSize;
+ this._channelOpenWaitHandle.Set();
+ }
+
+ private void HandleMessage(ChannelOpenFailureMessage message)
+ {
+ this.IsOpen = false;
+ this._channelOpenWaitHandle.Set();
+ }
+
+ private void HandleMessage(ChannelWindowAdjustMessage message)
+ {
+ this.WindowSize += message.BytesToAdd;
+ }
+
+ private void HandleMessage(ChannelDataMessage message)
+ {
+ this.AdjustDataWindow(message.Data);
+ this.OnChannelData(message.Data);
+ }
+
+ private void HandleMessage(ChannelExtendedDataMessage message)
+ {
+ this.AdjustDataWindow(message.Data);
+ this.OnChannelExtendedData(message.Data, message.DataTypeCode);
+ }
+
+ private void HandleMessage(ChannelRequestMessage message)
+ {
+ Message replyMessage = new ChannelFailureMessage()
+ {
+ ChannelNumber = message.ChannelNumber,
+ };
+
+ if (message.RequestName == RequestNames.ExitStatus)
+ {
+ var exitStatus = message.ExitStatus;
+ replyMessage = new ChannelSuccessMessage()
+ {
+ ChannelNumber = message.ChannelNumber,
+ };
+ }
+
+ if (message.WantReply)
+ {
+ this.SendMessage(replyMessage);
+ }
+ }
+
+ private void HandleMessage(ChannelSuccessMessage message)
+ {
+ this.OnChannelSuccess();
+ }
+
+ private void HandleMessage(ChannelEofMessage message)
+ {
+ this.OnChannelEof();
+ }
+
+ private void HandleMessage(ChannelCloseMessage message)
+ {
+ // TODO: Handle this message
+ this.CloseCleanup();
+
+ this._channelClosedWaitHandle.Set();
+ }
+
+ private void AdjustDataWindow(string messageData)
+ {
+ this.WindowSize -= (uint)messageData.Length;
+
+ // Adjust window if window size is too low
+ if (this.WindowSize < this._initialWindowSize / 2)
+ {
+ this.SendMessage(new ChannelWindowAdjustMessage
+ {
+ ChannelNumber = this.ServerChannelNumber,
+ BytesToAdd = this._initialWindowSize - this.WindowSize,
+ });
+ this.WindowSize = this._initialWindowSize;
+ }
+ }
+
+ #endregion
+
+
+ private void CloseCleanup()
+ {
+
+ this.IsOpen = false;
+
+ this.SessionInfo.MessageReceived -= SessionInfo_MessageReceived;
+ }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs
new file mode 100644
index 00000000..90bb69dc
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs
@@ -0,0 +1,65 @@
+using System.Text;
+using System.Threading;
+using Renci.SshClient.Messages.Connection;
+
+namespace Renci.SshClient.Channels
+{
+ internal class ChannelSession : Channel
+ {
+ private EventWaitHandle _channelEofWaitHandle = new AutoResetEvent(false);
+ private StringBuilder _response = new StringBuilder();
+
+ public override ChannelTypes ChannelType
+ {
+ get { return ChannelTypes.Session; }
+ }
+
+ public ChannelSession(SessionInfo sessionInfo)
+ : base(sessionInfo, 0x100000, 0x1000)
+ {
+ }
+
+ internal string Execute(string command)
+ {
+ this.Open();
+
+ // Send channel command request
+ this.SendMessage(new ChannelRequestMessage
+ {
+ ChannelNumber = this.ServerChannelNumber,
+ RequestName = RequestNames.Exec,
+ WantReply = false,
+ Command = command,
+ });
+
+
+ this.SessionInfo.WaitHandle(this._channelEofWaitHandle);
+
+ this.Close();
+
+
+ return this._response.ToString();
+ }
+
+ protected override void OnChannelEof()
+ {
+ base.OnChannelEof();
+
+ // TODO: All wait handles add timeout and then throw an exception or monitor connection closed event
+ this._channelEofWaitHandle.Set();
+ }
+
+ protected override void OnChannelData(string data)
+ {
+ base.OnChannelData(data);
+
+ this._response.Append(data);
+ }
+
+ private void Init()
+ {
+ this.ChannelData.Length = 0;
+ this.ChannelExtendedData.Length = 0;
+ }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs
new file mode 100644
index 00000000..f22e7f75
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs
@@ -0,0 +1,547 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using Renci.SshClient.Common;
+using Renci.SshClient.Messages.Connection;
+using Renci.SshClient.Messages.Sftp;
+
+namespace Renci.SshClient.Channels
+{
+ internal class ChannelSftp : Channel
+ {
+ private EventWaitHandle _channelRequestSuccessWaitHandle = new AutoResetEvent(false);
+
+ private EventWaitHandle _testWaitHandle = new AutoResetEvent(false);
+
+ private EventWaitHandle _responseMessageReceivedWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
+
+ private uint _requestId;
+
+ private SftpMessage _responseMessage;
+
+ private string _remoteCurrentDir;
+
+ private string _localCurentDir;
+
+ private StringBuilder _packetData;
+
+ public override ChannelTypes ChannelType
+ {
+ get { return ChannelTypes.Session; }
+ }
+
+ public ChannelSftp(SessionInfo sessionInfo, uint windowSize, uint packetSize)
+ : base(sessionInfo, windowSize, packetSize)
+ {
+ }
+
+ public ChannelSftp(SessionInfo sessionInfo)
+ : base(sessionInfo, 0x100000, 0x4000)
+ {
+ }
+
+ public override void Open()
+ {
+ base.Open();
+
+ // Send channel command request
+ this.SendMessage(new ChannelRequestMessage
+ {
+ ChannelNumber = this.ServerChannelNumber,
+ RequestName = RequestNames.Subsystem,
+ WantReply = true,
+ SubsystemName = "sftp",
+ });
+
+ this.SessionInfo.WaitHandle(this._channelRequestSuccessWaitHandle);
+
+ this.SendMessage(new InitMessage
+ {
+ Version = 6,
+ });
+
+ var versionMessage = this.ReceiveMessage();
+
+ if (versionMessage == null)
+ {
+ throw new InvalidOperationException("Version message expected.");
+ }
+
+ if (versionMessage.Version != 3)
+ {
+ throw new NotSupportedException(string.Format("Server SFTP version {0} is not supported.", versionMessage.Version));
+ }
+
+ // Get default current directories
+ var files = this.GetRealPath(".");
+
+ this._remoteCurrentDir = files.First().Name;
+ this._localCurentDir = Directory.GetCurrentDirectory();
+ }
+
+ public void UploadFile(Stream source, string destination)
+ {
+ this.Open();
+
+ string handle = string.Empty;
+
+ try
+ {
+ handle = this.OpenRemoteFile(destination, Flags.Write | Flags.CreateNewOrOpen | Flags.Truncate);
+
+ var buffer = new byte[1024];
+ ulong offset = 0;
+ while (source.Read(buffer, 0, buffer.Length) > 0)
+ {
+ this.RemoteWrite(handle, offset, buffer.GetSshString());
+ offset += (ulong)buffer.Length;
+ }
+ }
+ finally
+ {
+ if (!string.IsNullOrEmpty(handle))
+ this.CloseRemoteHandle(handle);
+ }
+
+ this.Close();
+ }
+
+ internal void DownloadFile(string fileName, Stream destination)
+ {
+ this.Open();
+
+ string handle = string.Empty;
+
+ try
+ {
+ handle = this.OpenRemoteFile(fileName, Flags.Read);
+
+ ulong offset = 0;
+ uint bufferSize = 1024;
+ string data;
+
+ while ((data = this.RemoteRead(handle, offset, bufferSize)) != null)
+ {
+ var fileData = data.GetSshBytes().ToArray();
+ destination.Write(fileData, 0, (int)bufferSize);
+ destination.Flush();
+ offset += (ulong)fileData.Length;
+ }
+
+ }
+ finally
+ {
+ if (!string.IsNullOrEmpty(handle))
+ this.CloseRemoteHandle(handle);
+ }
+
+ this.Close();
+ }
+
+ public void CreateDirectory(string directoryName)
+ {
+ this.Open();
+
+ this.CreateRemoteDirectory(directoryName);
+
+ this.Close();
+ }
+
+ public void RemoveDirectory(string directoryName)
+ {
+ this.Open();
+
+ this.RemoveRemoteDirectory(directoryName);
+
+ this.Close();
+ }
+
+ public void RemoveFile(string fileName)
+ {
+ this.Open();
+
+ this.RemoveRemoteFile(fileName);
+
+ this.Close();
+ }
+
+ public void RenameFile(string oldFileName, string newFileName)
+ {
+ this.Open();
+
+ this.RenameRemoteFile(oldFileName, newFileName);
+
+ this.Close();
+ }
+
+ public IEnumerable ListDirectory(string path)
+ {
+ // Open channel
+ this.Open();
+
+ string handle = string.Empty;
+ IEnumerable files = null;
+
+ try
+ {
+ // Open directory
+ handle = this.OpenRemoteDirectory(path);
+
+ // Read directory data
+ files = this.ReadRemoteDirectory(handle);
+ }
+ finally
+ {
+ // Close directory
+ if (!string.IsNullOrEmpty(handle))
+ this.CloseRemoteHandle(handle);
+ }
+
+ // Read directory
+ this.Close();
+
+ return files;
+
+ }
+
+ protected override void OnChannelSuccess()
+ {
+ base.OnChannelSuccess();
+
+ this._channelRequestSuccessWaitHandle.Set();
+ }
+
+ protected override void OnChannelData(string data)
+ {
+ base.OnChannelData(data);
+
+ if (this._packetData == null)
+ {
+ var packetLength = BitConverter.ToUInt32(data.GetSshBytes().Take(4).Reverse().ToArray(), 0);
+ this._packetData = new StringBuilder((int)packetLength, (int)packetLength);
+ this._packetData.Append(data.GetSshBytes().Skip(4).GetSshString());
+ }
+ else
+ {
+ this._packetData.Append(data);
+ }
+
+
+ if (this._packetData.Length < this._packetData.MaxCapacity)
+ {
+ // Wait for more packet data
+ return;
+ }
+
+
+ dynamic sftpMessage = SftpMessage.Load(this._packetData.ToString().GetSshBytes());
+
+ this._packetData = null;
+
+ // TODO: Handle SSH_FXP_STATUS here
+ // TODO: Validate message request id is correct
+
+ this._responseMessage = sftpMessage;
+
+ this._responseMessageReceivedWaitHandle.Set();
+ }
+
+ private T ReceiveMessage() where T : SftpMessage
+ {
+ var message = this.ReceiveMessage() as T;
+
+ if (message == null)
+ {
+ throw new InvalidOperationException(string.Format("Message of type '{0}' expected in this context.", typeof(T).Name));
+ }
+ return message;
+
+ }
+
+ private SftpMessage ReceiveMessage()
+ {
+ this.SessionInfo.WaitHandle(this._responseMessageReceivedWaitHandle);
+
+ var statusMessage = this._responseMessage as StatusMessage;
+
+ if (statusMessage != null)
+ {
+ // Handle error status messages
+ switch (statusMessage.StatusCode)
+ {
+ case StatusCodes.Ok:
+ break;
+ case StatusCodes.Eof:
+ break;
+ case StatusCodes.NoSuchFile:
+ throw new FileNotFoundException("File or directory not found on the remote server.");
+ case StatusCodes.PermissionDenied:
+ throw new NotImplementedException();
+ case StatusCodes.Failure:
+ throw new InvalidOperationException("Operation failed.");
+ case StatusCodes.BadMessage:
+ throw new NotImplementedException();
+ case StatusCodes.NoConnection:
+ throw new NotImplementedException();
+ case StatusCodes.ConnectionLost:
+ throw new NotImplementedException();
+ case StatusCodes.OperationUnsupported:
+ throw new NotSupportedException("Operation is not supported.");
+ default:
+ break;
+ }
+ }
+
+ return this._responseMessage;
+ }
+
+ private void SendMessage(SftpMessage sftpMessage)
+ {
+ sftpMessage.RequestId = this._requestId++;
+ var message = new SftpDataMessage
+ {
+ ChannelNumber = this.ServerChannelNumber,
+ Data = sftpMessage,
+ };
+
+ this.SendMessage(message);
+
+ this._responseMessageReceivedWaitHandle.Reset();
+ }
+
+ private string OpenRemoteFile(string fileName, Flags flags)
+ {
+ this.SendMessage(new OpenMessage
+ {
+ Filename = fileName,
+ Flags = flags,
+ });
+
+ var handleMessage = this.ReceiveMessage();
+
+ return handleMessage.Handle;
+ }
+
+ private string RemoteRead(string handle, ulong offset, uint length)
+ {
+ this.SendMessage(new ReadMessage
+ {
+ Handle = handle,
+ Offset = offset,
+ Length = length,
+ });
+
+ var message = this.ReceiveMessage();
+
+ var statusMessage = message as StatusMessage;
+ var dataMessage = message as DataMessage;
+
+ if (statusMessage != null)
+ {
+ if (statusMessage.StatusCode == StatusCodes.Eof)
+ {
+ return null;
+ }
+
+ throw new InvalidOperationException("Invalid status code.");
+ }
+ else if (dataMessage != null)
+ {
+ return dataMessage.Data;
+ }
+ else
+ {
+ throw new InvalidOperationException(string.Format("Message type '{0}' is not valid in this context.", message.SftpMessageType));
+ }
+ }
+
+ private void RemoteWrite(string handle, ulong offset, string data)
+ {
+ this.SendMessage(new WriteMessage
+ {
+ Handle = handle,
+ Offset = offset,
+ Data = data,
+ });
+
+ var message = this.ReceiveMessage();
+
+ this.EnsureStatusCode(message, StatusCodes.Ok);
+ }
+
+ private void RemoveRemoteFile(string fileName)
+ {
+ this.SendMessage(new RemoveMessage
+ {
+ Filename = fileName,
+ });
+
+ var message = this.ReceiveMessage();
+
+ this.EnsureStatusCode(message, StatusCodes.Ok);
+ }
+
+ private void RenameRemoteFile(string oldFileName, string newFileName)
+ {
+ this.SendMessage(new RenameMessage
+ {
+ OldPath = oldFileName,
+ NewPath = newFileName,
+ });
+
+ var message = this.ReceiveMessage();
+
+ this.EnsureStatusCode(message, StatusCodes.Ok);
+ }
+
+ private void CreateRemoteDirectory(string directoryName)
+ {
+ this.SendMessage(new MkDirMessage
+ {
+ Path = directoryName,
+ });
+
+ var message = this.ReceiveMessage();
+
+ this.EnsureStatusCode(message, StatusCodes.Ok);
+ }
+
+ private void RemoveRemoteDirectory(string directoryName)
+ {
+ this.SendMessage(new RmDirMessage
+ {
+ Path = directoryName,
+ });
+
+ var message = this.ReceiveMessage();
+
+ this.EnsureStatusCode(message, StatusCodes.Ok);
+ }
+
+ private string OpenRemoteDirectory(string path)
+ {
+ this.SendMessage(new OpenDirMessage
+ {
+ Path = path,
+ });
+
+ var handleMessage = this.ReceiveMessage();
+
+ return handleMessage.Handle;
+ }
+
+ private IEnumerable ReadRemoteDirectory(string handle)
+ {
+ this.SendMessage(new ReadDirMessage
+ {
+ Handle = handle,
+ });
+
+ var message = this.ReceiveMessage();
+
+ return message.Files;
+ }
+
+ private void CloseRemoteHandle(string handle)
+ {
+ this.SendMessage(new CloseMessage
+ {
+ Handle = handle,
+ });
+
+ var status = this.ReceiveMessage();
+ // TODO: If close is fails wait a litle a try to close it again, in case server fluashed data into the file during close
+ }
+
+ private Attributes GetRemoteFileAttributes(string filename)
+ {
+ this.SendMessage(new StatMessage
+ {
+ Path = filename,
+ });
+
+ var message = this.ReceiveMessage();
+
+ return message.Attributes;
+ }
+
+ private Attributes GetRemoteLinkFileAttributes(string filename)
+ {
+ this.SendMessage(new LStatMessage
+ {
+ Path = filename,
+ });
+
+ var message = this.ReceiveMessage();
+
+ return message.Attributes;
+ }
+
+ private Attributes GetRemoteOpenFileAttributes(string handle)
+ {
+ this.SendMessage(new FStatMessage
+ {
+ Handle = handle,
+ });
+
+ var message = this.ReceiveMessage();
+
+ return message.Attributes;
+ }
+
+ private void SetRemoteFileAttributes(string filename, Attributes attributes)
+ {
+ this.SendMessage(new SetStatMessage
+ {
+ Path = filename,
+ Attributes = attributes
+ });
+
+ var message = this.ReceiveMessage();
+
+ this.EnsureStatusCode(message, StatusCodes.Ok);
+ }
+
+ private void SetRemoteOpenFileAttributes(string handle, Attributes attributes)
+ {
+ this.SendMessage(new FSetStatMessage
+ {
+ Handle = handle,
+ Attributes = attributes
+ });
+
+ var message = this.ReceiveMessage();
+
+ this.EnsureStatusCode(message, StatusCodes.Ok);
+ }
+
+ private IEnumerable GetRealPath(string path)
+ {
+ this.SendMessage(new RealPathMessage
+ {
+ Path = path,
+ });
+
+ var message = this.ReceiveMessage();
+
+ return message.Files;
+
+ }
+
+ private void EnsureStatusCode(StatusMessage message, StatusCodes code)
+ {
+ if (message.StatusCode == code)
+ {
+ return;
+ }
+ else
+ {
+ throw new InvalidOperationException("Invalid status code.");
+ }
+ }
+
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelTypes.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelTypes.cs
new file mode 100644
index 00000000..a7180964
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelTypes.cs
@@ -0,0 +1,28 @@
+
+namespace Renci.SshClient.Channels
+{
+ ///
+ ///
+ ///
+ internal enum ChannelTypes
+ {
+ ///
+ /// session
+ ///
+ Session,
+ ///
+ /// x11
+ ///
+ X11,
+ ///
+ /// forwarded-tcpip
+ ///
+ ForwardedTcpip,
+ ///
+ /// direct-tcpip
+ ///
+ DirectTcpip,
+
+
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Common/DataReceivedEventArgs.cs b/Renci.SshClient/Renci.SshClient/Common/DataReceivedEventArgs.cs
new file mode 100644
index 00000000..3ec0e6ec
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Common/DataReceivedEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Renci.SshClient.Common
+{
+ internal class DataReceivedEventArgs : EventArgs
+ {
+ public string Data { get; private set; }
+
+ public DataReceivedEventArgs(string data)
+ {
+ this.Data = data;
+ }
+ }
+}
diff --git a/Renci.SshClient/Renci.SshClient/Common/Extensions.cs b/Renci.SshClient/Renci.SshClient/Common/Extensions.cs
new file mode 100644
index 00000000..5d454d33
--- /dev/null
+++ b/Renci.SshClient/Renci.SshClient/Common/Extensions.cs
@@ -0,0 +1,123 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Renci.SshClient
+{
+ public static class Extensions
+ {
+ ///
+ /// Checks whether a collection is the same as another collection
+ ///
+ /// The current instance object
+ /// The collection to compare with
+ /// The comparer object to use to compare each item in the collection. If null uses EqualityComparer(T).Default
+ /// True if the two collections contain all the same items in the same order
+ public static bool IsEqualTo(this IEnumerable value, IEnumerable compareList, IEqualityComparer comparer)
+ {
+ if (value == compareList)
+ {
+ return true;
+ }
+ else if (value == null || compareList == null)
+ {
+ return false;
+ }
+ else
+ {
+ if (comparer == null)
+ {
+ comparer = EqualityComparer.Default;
+ }
+
+ IEnumerator enumerator1 = value.GetEnumerator();
+ IEnumerator enumerator2 = compareList.GetEnumerator();
+
+ bool enum1HasValue = enumerator1.MoveNext();
+ bool enum2HasValue = enumerator2.MoveNext();
+
+ try
+ {
+ while (enum1HasValue && enum2HasValue)
+ {
+ if (!comparer.Equals(enumerator1.Current, enumerator2.Current))
+ {
+ return false;
+ }
+
+ enum1HasValue = enumerator1.MoveNext();
+ enum2HasValue = enumerator2.MoveNext();
+ }
+
+ return !(enum1HasValue || enum2HasValue);
+ }
+ finally
+ {
+ if (enumerator1 != null) enumerator1.Dispose();
+ if (enumerator2 != null) enumerator2.Dispose();
+ }
+ }
+ }
+
+ public static bool IsEqualTo(this IEnumerable value, IEnumerable compareList)
+ {
+ return IsEqualTo(value, compareList, null);
+ }
+
+ public static bool IsEqualTo(this IEnumerable value, IEnumerable compareList)
+ {
+ return IsEqualTo