From b86e465fd0c9f2a075cb24ebad27349b0a80eb82 Mon Sep 17 00:00:00 2001 From: olegkap_cp Date: Tue, 10 Aug 2010 18:20:43 +0000 Subject: [PATCH] Initial checkin --- .../Renci.SshClient/Algorithms/Algorithm.cs | 7 + .../Renci.SshClient/Algorithms/Compression.cs | 23 + .../Renci.SshClient/Algorithms/KeyExchange.cs | 456 +++++++++++++++ .../KeyExchangeCompletedEventArgs.cs | 8 + .../Algorithms/KeyExchangeDiffieHellman.cs | 125 ++++ .../Algorithms/KeyExchangeFailedEventArgs.cs | 14 + .../KeyExchangeSendMessageEventArgs.cs | 15 + .../Renci.SshClient/Algorithms/Signature.cs | 15 + .../Algorithms/SignatureDss.cs | 91 +++ .../Algorithms/SignatureRsa.cs | 81 +++ .../Renci.SshClient/Channels/Channel.cs | 257 ++++++++ .../Channels/ChannelSession.cs | 65 +++ .../Renci.SshClient/Channels/ChannelSftp.cs | 547 ++++++++++++++++++ .../Renci.SshClient/Channels/ChannelTypes.cs | 28 + .../Common/DataReceivedEventArgs.cs | 14 + .../Renci.SshClient/Common/Extensions.cs | 123 ++++ .../Renci.SshClient/Common/FtpFileInfo.cs | 29 + .../Common/MessageReceivedEventArgs.cs | 15 + .../Renci.SshClient/Common/SshData.cs | 206 +++++++ Renci.SshClient/Renci.SshClient/Connection.cs | 51 ++ .../Renci.SshClient/ConnectionInfo.cs | 21 + Renci.SshClient/Renci.SshClient/KeyFile.cs | 134 +++++ .../Messages/Authentication/BannerMessage.cs | 28 + .../Messages/Authentication/FailureMessage.cs | 34 ++ .../Authentication/HostRequestMessage.cs | 33 ++ .../InformationRequestMessage.cs | 22 + .../InformationResponseMessage.cs | 22 + .../Messages/Authentication/Methods.cs | 10 + .../PasswordChangeRequiredMessage.cs | 27 + .../Authentication/PasswordRequestMessage.cs | 34 ++ .../Authentication/PublicKeyMessage.cs | 22 + .../Authentication/PublicKeyRequestMessage.cs | 39 ++ .../Messages/Authentication/RequestMessage.cs | 42 ++ .../Messages/Authentication/SuccessMessage.cs | 19 + .../Connection/ChannelCloseMessage.cs | 11 + .../Messages/Connection/ChannelDataMessage.cs | 24 + .../Messages/Connection/ChannelEofMessage.cs | 10 + .../Connection/ChannelExtendedDataMessage.cs | 29 + .../Connection/ChannelFailureMessage.cs | 11 + .../Messages/Connection/ChannelMessage.cs | 18 + .../ChannelOpenConfirmationMessage.cs | 32 + .../Connection/ChannelOpenFailureMessage.cs | 32 + .../Connection/ChannelOpenFailureReasons.cs | 22 + .../Messages/Connection/ChannelOpenMessage.cs | 33 ++ .../Connection/ChannelRequestMessage.cs | 106 ++++ .../Connection/ChannelSuccessMessage.cs | 11 + .../Connection/ChannelWindowAdjustMessage.cs | 25 + .../Connection/GlobalRequestMessage.cs | 22 + .../Connection/RequestFailureMessage.cs | 22 + .../Messages/Connection/RequestNames.cs | 49 ++ .../Connection/RequestSuccessMessage.cs | 22 + .../Renci.SshClient/Messages/Message.cs | 91 +++ .../Renci.SshClient/Messages/MessageTypes.cs | 150 +++++ .../Renci.SshClient/Messages/ServiceNames.cs | 15 + .../Renci.SshClient/Messages/Sftp/AceMasks.cs | 25 + .../Messages/Sftp/Attributes.cs | 24 + .../Messages/Sftp/AttrsMessage.cs | 25 + .../Messages/Sftp/CloseMessage.cs | 25 + .../Messages/Sftp/DataMessage.cs | 35 ++ .../Messages/Sftp/ExtendedMessage.cs | 27 + .../Messages/Sftp/ExtendedReplyMessage.cs | 23 + .../Renci.SshClient/Messages/Sftp/FSetStat.cs | 29 + .../Messages/Sftp/FSetStatMessage.cs | 29 + .../Messages/Sftp/FStatMessage.cs | 26 + .../Renci.SshClient/Messages/Sftp/Flags.cs | 30 + .../Messages/Sftp/HandleMessage.cs | 25 + .../Messages/Sftp/InitMessage.cs | 23 + .../Messages/Sftp/LStatMessage.cs | 25 + .../Messages/Sftp/MkDirMessage.cs | 33 ++ .../Messages/Sftp/NameMessage.cs | 52 ++ .../Messages/Sftp/OpenDirMessage.cs | 26 + .../Messages/Sftp/OpenMessage.cs | 32 + .../Messages/Sftp/ReadDirMessage.cs | 25 + .../Messages/Sftp/ReadLinkMessage.cs | 26 + .../Messages/Sftp/ReadMessage.cs | 34 ++ .../Messages/Sftp/RealPathMessage.cs | 25 + .../Messages/Sftp/RemoveMessage.cs | 26 + .../Messages/Sftp/RenameMessage.cs | 28 + .../Messages/Sftp/RmDirMessage.cs | 26 + .../Messages/Sftp/SetStatMessage.cs | 29 + .../Messages/Sftp/SftpDataMessage.cs | 23 + .../Messages/Sftp/SftpMessage.cs | 200 +++++++ .../Messages/Sftp/SftpMessageTypes.cs | 117 ++++ .../Messages/Sftp/StatMessage.cs | 25 + .../Messages/Sftp/StatusCodes.cs | 43 ++ .../Messages/Sftp/StatusMessage.cs | 67 +++ .../Messages/Sftp/SymLinkMessage.cs | 34 ++ .../Messages/Sftp/VersionMessage.cs | 36 ++ .../Messages/Sftp/WriteMessage.cs | 34 ++ .../Messages/Transport/DebugMessage.cs | 22 + .../Messages/Transport/DisconnectMessage.cs | 32 + .../Transport/DisconnectReasonCodes.cs | 66 +++ .../Messages/Transport/IgnoreMessage.cs | 22 + .../Transport/KeyExchangeDhInitMessage.cs | 28 + .../Transport/KeyExchangeDhReplyMessage.cs | 46 ++ .../Transport/KeyExchangeInitMessage.cs | 89 +++ .../Messages/Transport/NewKeysMessage.cs | 18 + .../Transport/ServiceAcceptMessage.cs | 38 ++ .../Transport/ServiceRequestMessage.cs | 38 ++ .../Transport/UnimplementedMessage.cs | 22 + Renci.SshClient/Renci.SshClient/PrivateKey.cs | 39 ++ .../Renci.SshClient/PrivateKeyDsa.cs | 192 ++++++ .../Renci.SshClient/PrivateKeyRsa.cs | 197 +++++++ .../Properties/AssemblyInfo.cs | 35 ++ .../Renci.SshClient/Renci.SshClient.csproj | 194 +++++++ .../Renci.SshClient.csproj.vspscc | 10 + .../Services/ConnectionService.cs | 29 + .../Renci.SshClient/Services/Service.cs | 22 + .../Services/UserAuthentication.cs | 23 + .../Services/UserAuthenticationHost.cs | 24 + .../Services/UserAuthenticationPassword.cs | 40 ++ .../Services/UserAuthenticationPublicKey.cs | 41 ++ .../Services/UserAuthenticationService.cs | 154 +++++ Renci.SshClient/Renci.SshClient/Session.cs | 418 +++++++++++++ .../Renci.SshClient/SessionInfo.cs | 72 +++ .../Renci.SshClient/SessionSSHv2.cs | 198 +++++++ Renci.SshClient/Renci.SshClient/Settings.cs | 53 ++ Renci.SshClient/Renci.SshClient/Sftp.cs | 59 ++ Renci.SshClient/Renci.SshClient/Shell.cs | 24 + 119 files changed, 6949 insertions(+) create mode 100644 Renci.SshClient/Renci.SshClient/Algorithms/Algorithm.cs create mode 100644 Renci.SshClient/Renci.SshClient/Algorithms/Compression.cs create mode 100644 Renci.SshClient/Renci.SshClient/Algorithms/KeyExchange.cs create mode 100644 Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeCompletedEventArgs.cs create mode 100644 Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeDiffieHellman.cs create mode 100644 Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeFailedEventArgs.cs create mode 100644 Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeSendMessageEventArgs.cs create mode 100644 Renci.SshClient/Renci.SshClient/Algorithms/Signature.cs create mode 100644 Renci.SshClient/Renci.SshClient/Algorithms/SignatureDss.cs create mode 100644 Renci.SshClient/Renci.SshClient/Algorithms/SignatureRsa.cs create mode 100644 Renci.SshClient/Renci.SshClient/Channels/Channel.cs create mode 100644 Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs create mode 100644 Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs create mode 100644 Renci.SshClient/Renci.SshClient/Channels/ChannelTypes.cs create mode 100644 Renci.SshClient/Renci.SshClient/Common/DataReceivedEventArgs.cs create mode 100644 Renci.SshClient/Renci.SshClient/Common/Extensions.cs create mode 100644 Renci.SshClient/Renci.SshClient/Common/FtpFileInfo.cs create mode 100644 Renci.SshClient/Renci.SshClient/Common/MessageReceivedEventArgs.cs create mode 100644 Renci.SshClient/Renci.SshClient/Common/SshData.cs create mode 100644 Renci.SshClient/Renci.SshClient/Connection.cs create mode 100644 Renci.SshClient/Renci.SshClient/ConnectionInfo.cs create mode 100644 Renci.SshClient/Renci.SshClient/KeyFile.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/BannerMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/FailureMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/HostRequestMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/InformationRequestMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/InformationResponseMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/Methods.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/PasswordChangeRequiredMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/PasswordRequestMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/PublicKeyMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/PublicKeyRequestMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/RequestMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Authentication/SuccessMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelCloseMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelDataMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelEofMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelExtendedDataMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelFailureMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenConfirmationMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenFailureMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenFailureReasons.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelSuccessMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelWindowAdjustMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/GlobalRequestMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/RequestFailureMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/RequestNames.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Connection/RequestSuccessMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Message.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/MessageTypes.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/ServiceNames.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/AceMasks.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/Attributes.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/AttrsMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/CloseMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/DataMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/ExtendedMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/ExtendedReplyMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/FSetStat.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/FSetStatMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/FStatMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/Flags.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/HandleMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/InitMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/LStatMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/MkDirMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/NameMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/OpenDirMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/OpenMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadDirMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadLinkMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/RealPathMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/RemoveMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/RenameMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/RmDirMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/SetStatMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpDataMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpMessageTypes.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/StatMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/StatusCodes.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/StatusMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/SymLinkMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/VersionMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Sftp/WriteMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Transport/DebugMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Transport/DisconnectMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Transport/DisconnectReasonCodes.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Transport/IgnoreMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeDhInitMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeDhReplyMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeInitMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Transport/NewKeysMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Transport/ServiceAcceptMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Transport/ServiceRequestMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/Messages/Transport/UnimplementedMessage.cs create mode 100644 Renci.SshClient/Renci.SshClient/PrivateKey.cs create mode 100644 Renci.SshClient/Renci.SshClient/PrivateKeyDsa.cs create mode 100644 Renci.SshClient/Renci.SshClient/PrivateKeyRsa.cs create mode 100644 Renci.SshClient/Renci.SshClient/Properties/AssemblyInfo.cs create mode 100644 Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj create mode 100644 Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj.vspscc create mode 100644 Renci.SshClient/Renci.SshClient/Services/ConnectionService.cs create mode 100644 Renci.SshClient/Renci.SshClient/Services/Service.cs create mode 100644 Renci.SshClient/Renci.SshClient/Services/UserAuthentication.cs create mode 100644 Renci.SshClient/Renci.SshClient/Services/UserAuthenticationHost.cs create mode 100644 Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPassword.cs create mode 100644 Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPublicKey.cs create mode 100644 Renci.SshClient/Renci.SshClient/Services/UserAuthenticationService.cs create mode 100644 Renci.SshClient/Renci.SshClient/Session.cs create mode 100644 Renci.SshClient/Renci.SshClient/SessionInfo.cs create mode 100644 Renci.SshClient/Renci.SshClient/SessionSSHv2.cs create mode 100644 Renci.SshClient/Renci.SshClient/Settings.cs create mode 100644 Renci.SshClient/Renci.SshClient/Sftp.cs create mode 100644 Renci.SshClient/Renci.SshClient/Shell.cs 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(value.OfType(), compareList.OfType()); + } + + public static void DebugPrint(this IEnumerable bytes) + { + foreach (var b in bytes) + { + Debug.Write(string.Format("0x{0:x2}, ", b)); + } + Debug.WriteLine(string.Empty); + } + + public static string GetSshString(this IEnumerable data) + { + List bytes = new List(); + foreach (var b in data) + { + bytes.Add((char)b); + } + + return new string(bytes.ToArray()); + } + + public static IEnumerable GetSshBytes(this string data) + { + List bytes = new List(); + foreach (var c in data.ToCharArray()) + { + bytes.Add((byte)c); + } + return bytes.ToArray(); + } + + public static IEnumerable TrimLeadinZero(this IEnumerable data) + { + bool leadingZero = true; + foreach (var item in data) + { + if (item == 0 & leadingZero) + { + continue; + } + else + { + leadingZero = false; + } + + yield return item; + } + } + + + } +} diff --git a/Renci.SshClient/Renci.SshClient/Common/FtpFileInfo.cs b/Renci.SshClient/Renci.SshClient/Common/FtpFileInfo.cs new file mode 100644 index 00000000..4296eb8a --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Common/FtpFileInfo.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace Renci.SshClient.Common +{ + public class FtpFileInfo + { + public string Name { get; set; } + + public string FullName { get; set; } + + public DateTime CreationTime { get; set; } + + public DateTime LastAccessTime { get; set; } + + public DateTime LastModifyTime { get; set; } + + public ulong Size { get; set; } + + public uint UserId { get; set; } + + public uint GroupId { get; set; } + + public uint Permissions { get; set; } + + public IDictionary Extentions { get; set; } + + } +} diff --git a/Renci.SshClient/Renci.SshClient/Common/MessageReceivedEventArgs.cs b/Renci.SshClient/Renci.SshClient/Common/MessageReceivedEventArgs.cs new file mode 100644 index 00000000..c5b59e21 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Common/MessageReceivedEventArgs.cs @@ -0,0 +1,15 @@ +using System; +using Renci.SshClient.Messages; + +namespace Renci.SshClient.Common +{ + internal class MessageReceivedEventArgs : EventArgs + { + public Message Message { get; private set; } + + public MessageReceivedEventArgs(Message message) + { + this.Message = message; + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Common/SshData.cs b/Renci.SshClient/Renci.SshClient/Common/SshData.cs new file mode 100644 index 00000000..3d874b80 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Common/SshData.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; + +namespace Renci.SshClient.Common +{ + public abstract class SshData + { + /// + /// Data byte array that hold message unencrypted data + /// + private IList _data; + + private int _readerIndex; + + public bool IsEndOfData + { + get + { + return this._readerIndex >= this._data.Count(); + } + } + + public virtual IEnumerable GetBytes() + { + this._data = new List(); + + this.SaveData(); + + return this._data; + } + + protected abstract void LoadData(); + + protected abstract void SaveData(); + + protected void LoadBytes(IEnumerable bytes) + { + this.ResetReader(); + this._data = new List(bytes); + } + + protected void ResetReader() + { + this._readerIndex = 1; // Set to 1 to skip first byte which specifies message type + } + + protected IEnumerable ReadBytes(int length) + { + var result = this._data.Skip(this._readerIndex).Take(length); + this._readerIndex += length; + return result; + } + + protected byte ReadByte() + { + return this.ReadBytes(1).FirstOrDefault(); + } + + protected bool ReadBoolean() + { + return this.ReadByte() == 0 ? false : true; + } + + protected UInt16 ReadUInt16() + { + return BitConverter.ToUInt16(this.ReadBytes(2).Reverse().ToArray(), 0); + } + + protected UInt32 ReadUInt32() + { + return BitConverter.ToUInt32(this.ReadBytes(4).Reverse().ToArray(), 0); + } + + protected UInt64 ReadUInt64() + { + return BitConverter.ToUInt64(this.ReadBytes(8).Reverse().ToArray(), 0); + } + + protected Int64 ReadInt64() + { + return BitConverter.ToInt64(this.ReadBytes(8).Reverse().ToArray(), 0); + + } + + protected string ReadString() + { + var length = this.ReadUInt32(); + + if (length > (UInt32)int.MaxValue) + { + throw new NotSupportedException(string.Format("String that longer that {0} are not supported.", int.MaxValue)); + } + + var result = this._data.Skip(this._readerIndex).Take((int)length).GetSshString(); + this._readerIndex += (int)length; + + return result; + } + + protected BigInteger ReadBigInteger() + { + var length = this.ReadUInt32(); + + var data = this.ReadBytes((int)length); + + return new BigInteger(data.Reverse().ToArray()); + } + + protected IEnumerable ReadNamesList() + { + var namesList = this.ReadString(); + return namesList.Split(','); + } + + protected IDictionary ReadExtensionPair() + { + Dictionary result = new Dictionary(); + while (this._readerIndex < this._data.Count) + { + var extensionName = this.ReadString(); + var extensionData = this.ReadString(); + result.Add(extensionName, extensionData); + } + return result; + } + + protected void Write(IEnumerable data) + { + foreach (var b in data) + this.Write(b); + } + + protected void Write(byte data) + { + this._data.Add(data); + } + + protected void Write(bool data) + { + if (data) + { + this.Write(1); + } + else + { + this.Write(0); + } + } + + protected void Write(UInt16 data) + { + this.Write(BitConverter.GetBytes(data).Reverse()); + } + + protected void Write(UInt32 data) + { + this.Write(BitConverter.GetBytes(data).Reverse()); + } + + protected void Write(UInt64 data) + { + this.Write(BitConverter.GetBytes(data).Reverse()); + } + + protected void Write(Int64 data) + { + this.Write(BitConverter.GetBytes(data).Reverse()); + } + + protected void Write(string data, Encoding encoding) + { + this.Write((uint)data.Length); + this.Write(encoding.GetBytes(data)); + } + + protected void Write(string data) + { + this.Write((uint)data.Length); + this.Write(data.GetSshBytes()); + } + + protected void Write(BigInteger data) + { + var bytes = data.ToByteArray().Reverse().ToList(); + this.Write((uint)bytes.Count); + this.Write(bytes); + } + + protected void Write(IEnumerable data) + { + this.Write(string.Join(",", data)); + } + + protected void Write(IDictionary data) + { + foreach (var item in data) + { + this.Write(item.Key); + this.Write(item.Value); + } + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Connection.cs b/Renci.SshClient/Renci.SshClient/Connection.cs new file mode 100644 index 00000000..91e1e762 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Connection.cs @@ -0,0 +1,51 @@ +namespace Renci.SshClient +{ + public class Connection + { + private Session _session; + + public ConnectionInfo ConnectionInfo { get; private set; } + + private Shell _shell; + public Shell Shell + { + get + { + if (this._shell == null) + { + this._shell = new Shell(this._session); + } + return this._shell; + } + } + + private Sftp _sftp; + + public Sftp Sftp + { + get + { + if (this._sftp == null) + { + this._sftp = new Sftp(this._session); + } + return this._sftp; + } + } + + public Connection(ConnectionInfo connectionInfo) + { + this._session = Session.CreateSession(connectionInfo); + } + + public void Connect() + { + this._session.Connect(); + } + + public void Disconnect() + { + this._session.Disconnect(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/ConnectionInfo.cs b/Renci.SshClient/Renci.SshClient/ConnectionInfo.cs new file mode 100644 index 00000000..cc5720b1 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/ConnectionInfo.cs @@ -0,0 +1,21 @@ +namespace Renci.SshClient +{ + public class ConnectionInfo + { + public string Host { get; set; } + + public int Port { get; set; } + + public string Username { get; set; } + + public string Password { get; set; } + + public KeyFile KeyFile { get; set; } + + public ConnectionInfo() + { + // Set default connection values + this.Port = 22; + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/KeyFile.cs b/Renci.SshClient/Renci.SshClient/KeyFile.cs new file mode 100644 index 00000000..774ea3de --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/KeyFile.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +namespace Renci.SshClient +{ + public class KeyFile + { + private Regex _beginKeyLine = new Regex(@"----[ ]*BEGIN (?.+) PRIVATE KEY[ ]*----"); + private Regex _headerLine = new Regex(@"(?[^:]{1,64}):[ ](?[^:]+(?\\)?)"); + private Regex _headerLineContinue = new Regex(@"(?[^:]+(?\\)?)"); + private Regex _endKeyLine = new Regex(@"----[ ]*END (?.+) PRIVATE KEY[ ]*----"); + + private PrivateKey _key; + + public string AlgorithmName + { + get + { + return this._key.AlgorithmName; + } + } + + public IEnumerable PublicKey + { + get + { + return this._key.PublicKey; + } + } + + public IEnumerable GetSignature(IEnumerable sessionId) + { + return this._key.GetSignature(sessionId); + } + + public KeyFile() + { + + } + + public void Open(string fileName) + { + using (var keyFile = File.OpenText(fileName)) + { + var headerTag = string.Empty; + var headerValue = string.Empty; + var headerValueContinue = false; + var data = new StringBuilder(); + var keyName = string.Empty; + + var fileLine = string.Empty; + while ((fileLine = keyFile.ReadLine()) != null) + { + var match = _beginKeyLine.Match(fileLine); + if (match.Success) + { + keyName = match.Result("${keyName}"); + continue; + } + + match = _endKeyLine.Match(fileLine); + if (match.Success) + { + var endKeyName = match.Result("${keyName}"); + if (!endKeyName.Equals(keyName)) + throw new InvalidDataException("Invalid data key file."); + break; + } + + + // Ignore everything if BEGIN was not found yet + if (string.IsNullOrEmpty(keyName)) + { + continue; + } + + match = _headerLine.Match(fileLine); + if (match.Success) + { + headerTag = match.Result("${headerTag}"); + headerValue = match.Result("${headerValue}"); + if (match.Result("${continue}") == @"\") + { + headerValueContinue = true; + } + else + { + headerValueContinue = false; + } + continue; + } + + if (headerValueContinue) + { + headerValue += fileLine; + if (match.Result("${continue}") == @"\") + { + headerValueContinue = true; + } + else + { + headerValueContinue = false; + } + continue; + } + + data.Append(fileLine); + } + + if (string.IsNullOrEmpty(keyName)) + { + throw new InvalidDataException("Invalid Public key file"); + } + + switch (keyName) + { + case "RSA": + this._key = new PrivateKeyRsa(System.Convert.FromBase64String(data.ToString())); + break; + case "DSA": + this._key = new PrivateKeyDsa(System.Convert.FromBase64String(data.ToString())); + break; + default: + throw new NotSupportedException(string.Format("Key '{0}' is not supported.", keyName)); + } + + } + } + + } +} \ No newline at end of file diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/BannerMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/BannerMessage.cs new file mode 100644 index 00000000..25918387 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/BannerMessage.cs @@ -0,0 +1,28 @@ +using System.Text; + +namespace Renci.SshClient.Messages.Authentication +{ + internal class BannerMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.UserAuthenticationBanner; } + } + + public string Message { get; set; } + + public string Language { get; set; } + + protected override void LoadData() + { + this.Message = this.ReadString(); + this.Language = this.ReadString(); + } + + protected override void SaveData() + { + this.Write(this.Message, Encoding.UTF8); + this.Write(this.Language); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/FailureMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/FailureMessage.cs new file mode 100644 index 00000000..d4293762 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/FailureMessage.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace Renci.SshClient.Messages.Authentication +{ + internal class FailureMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.UserAuthenticationFailure; } + } + + public IEnumerable AllowedAuthentications { get; set; } + + public string Message { get; set; } + + public bool PartialSuccess { get; set; } + + protected override void LoadData() + { + this.AllowedAuthentications = this.ReadNamesList(); + this.PartialSuccess = this.ReadBoolean(); + if (this.PartialSuccess) + { + this.Message = string.Join(",", this.AllowedAuthentications); + } + } + + protected override void SaveData() + { + throw new NotImplementedException(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/HostRequestMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/HostRequestMessage.cs new file mode 100644 index 00000000..23a4ce16 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/HostRequestMessage.cs @@ -0,0 +1,33 @@ +namespace Renci.SshClient.Messages.Authentication +{ + internal class HostRequestMessage : RequestMessage + { + } + + + + //byte SSH_MSG_USERAUTH_REQUEST + //string user name + //string service name + //string "hostbased" + //string public key algorithm for host key + //string public host key and certificates for client host + //string client host name expressed as the FQDN in US-ASCII + //string user name on the client host in ISO-10646 UTF-8 encoding + // [RFC3629] + //string signature + + + //string session identifier + //byte SSH_MSG_USERAUTH_REQUEST + //string user name + //string service name + //string "hostbased" + //string public key algorithm for host key + //string public host key and certificates for client host + //string client host name expressed as the FQDN in US-ASCII + //string user name on the client host in ISO-10646 UTF-8 encoding + // [RFC3629] + + +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/InformationRequestMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/InformationRequestMessage.cs new file mode 100644 index 00000000..f603c916 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/InformationRequestMessage.cs @@ -0,0 +1,22 @@ +using System; + +namespace Renci.SshClient.Messages.Authentication +{ + internal class InformationRequestMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.UserAuthenticationInformationRequest; } + } + + protected override void LoadData() + { + throw new NotImplementedException(); + } + + protected override void SaveData() + { + throw new NotImplementedException(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/InformationResponseMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/InformationResponseMessage.cs new file mode 100644 index 00000000..17e4416a --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/InformationResponseMessage.cs @@ -0,0 +1,22 @@ +using System; + +namespace Renci.SshClient.Messages.Authentication +{ + internal class InformationResponseMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.UserAuthenticationInformationResponse; } + } + + protected override void LoadData() + { + throw new NotImplementedException(); + } + + protected override void SaveData() + { + throw new NotImplementedException(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/Methods.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/Methods.cs new file mode 100644 index 00000000..361973ad --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/Methods.cs @@ -0,0 +1,10 @@ +namespace Renci.SshClient.Messages.Authentication +{ + public enum Methods + { + None, + PublicKey, + Password, + Hostbased + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/PasswordChangeRequiredMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/PasswordChangeRequiredMessage.cs new file mode 100644 index 00000000..4174c703 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/PasswordChangeRequiredMessage.cs @@ -0,0 +1,27 @@ + +namespace Renci.SshClient.Messages.Authentication +{ + internal class PasswordChangeRequiredMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.UserAuthenticationPasswordChangeRequired; } + } + + public string Message { get; set; } + + public string Language { get; set; } + + protected override void LoadData() + { + this.Message = this.ReadString(); + this.Language = this.ReadString(); + } + + protected override void SaveData() + { + this.Write(this.Message); + this.Write(this.Language); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/PasswordRequestMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/PasswordRequestMessage.cs new file mode 100644 index 00000000..52b9d1a3 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/PasswordRequestMessage.cs @@ -0,0 +1,34 @@ +using System.Text; + +namespace Renci.SshClient.Messages.Authentication +{ + internal class PasswordRequestMessage : RequestMessage + { + public override string MethodName + { + get + { + return "password"; + } + } + + public string Password { get; set; } + + public string NewPassword { get; set; } + + protected override void SaveData() + { + base.SaveData(); + + this.Write(!string.IsNullOrEmpty(this.NewPassword)); + + this.Write(this.Password, Encoding.UTF8); + + if (!string.IsNullOrEmpty(this.NewPassword)) + { + this.Write(this.NewPassword, Encoding.UTF8); + } + } + + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/PublicKeyMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/PublicKeyMessage.cs new file mode 100644 index 00000000..03f4b0fc --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/PublicKeyMessage.cs @@ -0,0 +1,22 @@ +using System; + +namespace Renci.SshClient.Messages.Authentication +{ + internal class PublicKeyMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.UserAuthenticationPublicKey; } + } + + protected override void LoadData() + { + throw new NotImplementedException(); + } + + protected override void SaveData() + { + throw new NotImplementedException(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/PublicKeyRequestMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/PublicKeyRequestMessage.cs new file mode 100644 index 00000000..3f67bd3c --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/PublicKeyRequestMessage.cs @@ -0,0 +1,39 @@ + +using System.Collections.Generic; +namespace Renci.SshClient.Messages.Authentication +{ + internal class PublicKeyRequestMessage : RequestMessage + { + public override string MethodName + { + get + { + return "publickey"; + } + } + + public string PublicKeyAlgorithmName { get; set; } + + public IEnumerable PublicKeyData { get; set; } + + public IEnumerable Signature { get; set; } + + protected override void SaveData() + { + base.SaveData(); + + if (this.Signature == null) + { + this.Write(false); + } + else + { + this.Write(true); + } + this.Write(this.PublicKeyAlgorithmName); + this.Write(this.PublicKeyData.GetSshString()); + if (this.Signature != null) + this.Write(this.Signature.GetSshString()); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/RequestMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/RequestMessage.cs new file mode 100644 index 00000000..35336bd5 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/RequestMessage.cs @@ -0,0 +1,42 @@ +using System; +using System.Text; + +namespace Renci.SshClient.Messages.Authentication +{ + internal class RequestMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.UserAuthenticationRequest; } + } + + public string Username { get; set; } + + public ServiceNames ServiceName { get; set; } + + public virtual string MethodName { get { return "none"; } } + + protected override void LoadData() + { + throw new InvalidOperationException("Load data is not supported."); + } + + protected override void SaveData() + { + this.Write(this.Username, Encoding.UTF8); + switch (this.ServiceName) + { + case ServiceNames.UserAuthentication: + this.Write("ssh-userauth", Encoding.UTF8); + break; + case ServiceNames.Connection: + this.Write("ssh-connection", Encoding.UTF8); + break; + default: + throw new NotSupportedException("Not supported service name"); + } + this.Write(this.MethodName, Encoding.ASCII); + } + } +} + diff --git a/Renci.SshClient/Renci.SshClient/Messages/Authentication/SuccessMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Authentication/SuccessMessage.cs new file mode 100644 index 00000000..a3dc29d5 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Authentication/SuccessMessage.cs @@ -0,0 +1,19 @@ + +namespace Renci.SshClient.Messages.Authentication +{ + internal class SuccessMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.UserAuthenticationSuccess; } + } + + protected override void LoadData() + { + } + + protected override void SaveData() + { + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelCloseMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelCloseMessage.cs new file mode 100644 index 00000000..b1ed8eb8 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelCloseMessage.cs @@ -0,0 +1,11 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelCloseMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelClose; } + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelDataMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelDataMessage.cs new file mode 100644 index 00000000..3e642527 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelDataMessage.cs @@ -0,0 +1,24 @@ +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelDataMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelData; } + } + + public string Data { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Data = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Data); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelEofMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelEofMessage.cs new file mode 100644 index 00000000..69c67805 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelEofMessage.cs @@ -0,0 +1,10 @@ +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelEofMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelEof; } + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelExtendedDataMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelExtendedDataMessage.cs new file mode 100644 index 00000000..d0e0c664 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelExtendedDataMessage.cs @@ -0,0 +1,29 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelExtendedDataMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelExtendedData; } + } + + public uint DataTypeCode { get; set; } + + public string Data { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.DataTypeCode = this.ReadUInt32(); + this.Data = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.DataTypeCode); + this.Write(this.Data); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelFailureMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelFailureMessage.cs new file mode 100644 index 00000000..c9569529 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelFailureMessage.cs @@ -0,0 +1,11 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelFailureMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelFailure; } + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelMessage.cs new file mode 100644 index 00000000..8ffb00f2 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelMessage.cs @@ -0,0 +1,18 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal abstract class ChannelMessage : Message + { + public uint ChannelNumber { get; set; } + + protected override void LoadData() + { + this.ChannelNumber = this.ReadUInt32(); + } + + protected override void SaveData() + { + this.Write(this.ChannelNumber); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenConfirmationMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenConfirmationMessage.cs new file mode 100644 index 00000000..68146998 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenConfirmationMessage.cs @@ -0,0 +1,32 @@ +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelOpenConfirmationMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelOpenConfirmation; } + } + + public uint ServerChannelNumber { get; set; } + + public uint InitialWindowSize { get; set; } + + public uint MaximumPacketSize { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.ServerChannelNumber = this.ReadUInt32(); + this.InitialWindowSize = this.ReadUInt32(); + this.MaximumPacketSize = this.ReadUInt32(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.ServerChannelNumber); + this.Write(this.InitialWindowSize); + this.Write(this.MaximumPacketSize); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenFailureMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenFailureMessage.cs new file mode 100644 index 00000000..8a22ea17 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenFailureMessage.cs @@ -0,0 +1,32 @@ +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelOpenFailureMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelOpenFailure; } + } + + public uint ReasconCode { get; set; } + + public string Description { get; set; } + + public string Language { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.ReasconCode = this.ReadUInt32(); + this.Description = this.ReadString(); + this.Language = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.ReasconCode); + this.Write(this.Description); + this.Write(this.Language); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenFailureReasons.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenFailureReasons.cs new file mode 100644 index 00000000..35f1ec64 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenFailureReasons.cs @@ -0,0 +1,22 @@ +namespace Renci.SshClient.Messages.Connection +{ + internal enum ChannelOpenFailureReasons : uint + { + /// + /// SSH_OPEN_ADMINISTRATIVELY_PROHIBITED + /// + AdministativelyProhibited = 1, + /// + /// SSH_OPEN_CONNECT_FAILED + /// + ConnectFailed = 2, + /// + /// SSH_OPEN_UNKNOWN_CHANNEL_TYPE + /// + UnknownChannelType = 3, + /// + /// SSH_OPEN_RESOURCE_SHORTAGE + /// + ResourceShortage = 4 + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenMessage.cs new file mode 100644 index 00000000..bc058588 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelOpenMessage.cs @@ -0,0 +1,33 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelOpenMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelOpen; } + } + + public string ChannelName { get; set; } + + public uint InitialWindowSize { get; set; } + + public uint MaximumPacketSize { get; set; } + + protected override void LoadData() + { + this.ChannelName = this.ReadString(); + this.ChannelNumber = this.ReadUInt32(); + this.InitialWindowSize = this.ReadUInt32(); + this.MaximumPacketSize = this.ReadUInt32(); + } + + protected override void SaveData() + { + this.Write(this.ChannelName); + this.Write(this.ChannelNumber); + this.Write(this.InitialWindowSize); + this.Write(this.MaximumPacketSize); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestMessage.cs new file mode 100644 index 00000000..0cc1d3fd --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelRequestMessage.cs @@ -0,0 +1,106 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelRequestMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelRequest; } + } + + public RequestNames RequestName { get; set; } + + public bool WantReply { get; set; } + + public string Command { get; set; } + + public string SubsystemName { get; set; } + + public uint ExitStatus { get; set; } + + protected override void LoadData() + { + base.LoadData(); + + var requestName = this.ReadString(); + switch (requestName) + { + case "pty-req": + break; + case "x11-req": + break; + case "env": + break; + case "shell": + break; + case "exec": + this.RequestName = RequestNames.Exec; + this.WantReply = this.ReadBoolean(); + this.Command = this.ReadString(); + break; + case "subsystem": + this.RequestName = RequestNames.Subsystem; + this.WantReply = this.ReadBoolean(); + this.SubsystemName = this.ReadString(); + break; + case "window-change": + break; + case "xon-xoff": + break; + case "signal": + break; + case "exit-status": + this.RequestName = RequestNames.ExitStatus; + this.WantReply = this.ReadBoolean(); + this.ExitStatus = this.ReadUInt32(); + break; + case "exit-signal": + break; + default: + break; + } + } + + protected override void SaveData() + { + base.SaveData(); + + switch (this.RequestName) + { + case RequestNames.PseudoTerminal: + break; + case RequestNames.X11Forwarding: + break; + case RequestNames.EnvironmentVariable: + break; + case RequestNames.Shell: + break; + case RequestNames.Exec: + this.Write("exec"); + this.Write(this.WantReply); + this.Write(this.Command); + break; + case RequestNames.Subsystem: + this.Write("subsystem"); + this.Write(this.WantReply); + this.Write(this.SubsystemName); + break; + case RequestNames.WindowChange: + break; + case RequestNames.XonXoff: + break; + case RequestNames.Signal: + break; + case RequestNames.ExitStatus: + this.Write("exit-status"); + this.Write(this.WantReply); + this.Write(this.ExitStatus); + break; + case RequestNames.ExitSignal: + break; + default: + break; + } + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelSuccessMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelSuccessMessage.cs new file mode 100644 index 00000000..9721179f --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelSuccessMessage.cs @@ -0,0 +1,11 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelSuccessMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelSuccess; } + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelWindowAdjustMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelWindowAdjustMessage.cs new file mode 100644 index 00000000..8752db8d --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/ChannelWindowAdjustMessage.cs @@ -0,0 +1,25 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal class ChannelWindowAdjustMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelWindowAdjust; } + } + + public uint BytesToAdd { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.BytesToAdd = this.ReadUInt32(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.BytesToAdd); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/GlobalRequestMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/GlobalRequestMessage.cs new file mode 100644 index 00000000..e3c7a52f --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/GlobalRequestMessage.cs @@ -0,0 +1,22 @@ +using System; + +namespace Renci.SshClient.Messages.Connection +{ + internal class GlobalRequestMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.GlobalRequest; } + } + + protected override void LoadData() + { + throw new NotImplementedException(); + } + + protected override void SaveData() + { + throw new NotImplementedException(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/RequestFailureMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/RequestFailureMessage.cs new file mode 100644 index 00000000..ff66fe1b --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/RequestFailureMessage.cs @@ -0,0 +1,22 @@ +using System; + +namespace Renci.SshClient.Messages.Connection +{ + internal class RequestFailureMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.RequestFailure; } + } + + protected override void LoadData() + { + throw new NotImplementedException(); + } + + protected override void SaveData() + { + throw new NotImplementedException(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/RequestNames.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/RequestNames.cs new file mode 100644 index 00000000..5c5f3cc4 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/RequestNames.cs @@ -0,0 +1,49 @@ + +namespace Renci.SshClient.Messages.Connection +{ + internal enum RequestNames + { + /// + /// pty-req + /// + PseudoTerminal, + ///x11-req + X11Forwarding, + /// + /// env + /// + EnvironmentVariable, + /// + /// shell + /// + Shell, + /// + /// exec + /// + Exec, + /// + /// subsystem + /// + Subsystem, + /// + /// window-change + /// + WindowChange, + /// + /// xon-xoff + /// + XonXoff, + /// + /// signal + /// + Signal, + /// + /// exit-status + /// + ExitStatus, + /// + /// exit-signal + /// + ExitSignal + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Connection/RequestSuccessMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Connection/RequestSuccessMessage.cs new file mode 100644 index 00000000..db0bb8a7 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Connection/RequestSuccessMessage.cs @@ -0,0 +1,22 @@ + +using System; +namespace Renci.SshClient.Messages.Connection +{ + internal class RequestSuccessMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.RequestSuccess; } + } + + protected override void LoadData() + { + throw new NotImplementedException(); + } + + protected override void SaveData() + { + throw new NotImplementedException(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Message.cs b/Renci.SshClient/Renci.SshClient/Messages/Message.cs new file mode 100644 index 00000000..f6522058 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Message.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Renci.SshClient.Common; + +namespace Renci.SshClient.Messages +{ + public delegate void SendMessageDelegate(Message message); + + public abstract class Message : SshData + { + private static object _lock = new object(); + + private delegate T LoadFunc(IEnumerable data); + + public abstract MessageTypes MessageType { get; } + + private static IDictionary> _registeredMessageTypes = new Dictionary>(); + + /// + /// Registers the message type. This will allow message type to be recognized by and handled by the system. + /// + /// Some message types are not allowed during cirtain times or same code can be used for different type of message + /// + /// Type of the message. + public static void RegisterMessageType(MessageTypes messageType) where T : Message, new() + { + lock (_lock) + { + if (Message._registeredMessageTypes.ContainsKey(messageType)) + { + Message.UnRegisterMessageType(messageType); + } + + Message._registeredMessageTypes.Add(messageType, new LoadFunc(Load)); + } + } + + public static void UnRegisterMessageType(MessageTypes messageType) + { + Message._registeredMessageTypes.Remove(messageType); + } + + public static Message Load(IEnumerable data) + { + var messageType = (MessageTypes)data.FirstOrDefault(); + + return Load(data, messageType); + } + + private static Message Load(IEnumerable data, MessageTypes messageType) + { + lock (_lock) + { + if (Message._registeredMessageTypes.ContainsKey(messageType)) + { + return Message._registeredMessageTypes[messageType](data); + } + else + { + throw new NotSupportedException(string.Format("Message type '{0}' is not registered.", messageType)); + } + } + } + + private static T Load(IEnumerable data) where T : Message, new() + { + var messageType = (MessageTypes)data.FirstOrDefault(); + + T message = new T(); + + message.LoadBytes(data); + + message.ResetReader(); + + message.LoadData(); + + return message; + } + + public override IEnumerable GetBytes() + { + var data = new List(base.GetBytes()); + + data.Insert(0, (byte)this.MessageType); + + return data; + } + + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/MessageTypes.cs b/Renci.SshClient/Renci.SshClient/Messages/MessageTypes.cs new file mode 100644 index 00000000..75a3e542 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/MessageTypes.cs @@ -0,0 +1,150 @@ +namespace Renci.SshClient.Messages +{ + /// + /// + /// + public enum MessageTypes : byte + { + /// + /// {35A90EBF-F421-44A3-BE3A-47C72AFE47FE} + /// + None = 0, + /// + /// SSH_MSG_DISCONNECT + /// + Disconnect = 1, + /// + /// SSH_MSG_IGNORE + /// + Ignore = 2, + /// + /// SSH_MSG_UNIMPLEMENTED + /// + Unimplemented = 3, + /// + /// SSH_MSG_DEBUG + /// + Debug = 4, + /// + /// SSH_MSG_SERVICE_REQUEST + /// + ServiceRequest = 5, + /// + /// SSH_MSG_SERVICE_ACCEPT + /// + ServiceAcceptRequest = 6, + + /// + /// SSH_MSG_KEXINIT + /// + KeyExchangeInit = 20, + /// + /// SSH_MSG_NEWKEYS + /// + NewKeys = 21, + /// + /// SSH_MSG_KEXDH_INIT + /// + DiffieHellmanKeyExchangeInit = 30, + /// + /// SSH_MSG_KEXDH_REPLY + /// + KeyExchangeDhReply = 31, + + SSH_MSG_KEX_DH_GEX_GROUP = 31, + SSH_MSG_KEX_DH_GEX_INIT = 32, + SSH_MSG_KEX_DH_GEX_REPLY = 33, + SSH_MSG_KEX_DH_GEX_REQUEST = 34, + + /// + /// SSH_MSG_USERAUTH_REQUEST + /// + UserAuthenticationRequest = 50, + /// + /// SSH_MSG_USERAUTH_FAILURE + /// + UserAuthenticationFailure = 51, + /// + /// SSH_MSG_USERAUTH_SUCCESS + /// + UserAuthenticationSuccess = 52, + /// + /// SSH_MSG_USERAUTH_BANNER + /// + UserAuthenticationBanner = 53, + /// + /// SSH_MSG_USERAUTH_INFO_REQUEST + /// + UserAuthenticationInformationRequest = 60, + /// + /// SSH_MSG_USERAUTH_INFO_RESPONSE + /// + UserAuthenticationInformationResponse = 61, + /// + /// SSH_MSG_USERAUTH_PK_OK + /// + UserAuthenticationPublicKey = 60, + /// + /// SSH_MSG_USERAUTH_PASSWD_CHANGEREQ + /// + UserAuthenticationPasswordChangeRequired = 60, + + + /// + /// SSH_MSG_GLOBAL_REQUEST + /// + GlobalRequest = 80, + /// + /// SSH_MSG_REQUEST_SUCCESS + /// + RequestSuccess = 81, + /// + /// SSH_MSG_REQUEST_FAILURE + /// + RequestFailure = 82, + /// + /// SSH_MSG_CHANNEL_OPEN + /// + ChannelOpen = 90, + /// + /// SSH_MSG_CHANNEL_OPEN_CONFIRMATION + /// + ChannelOpenConfirmation = 91, + /// + /// SSH_MSG_CHANNEL_OPEN_FAILURE + /// + ChannelOpenFailure = 92, + /// + /// SSH_MSG_CHANNEL_WINDOW_ADJUST + /// + ChannelWindowAdjust = 93, + /// + /// SSH_MSG_CHANNEL_DATA + /// + ChannelData = 94, + /// + /// SSH_MSG_CHANNEL_EXTENDED_DATA + /// + ChannelExtendedData = 95, + /// + /// SSH_MSG_CHANNEL_EOF + /// + ChannelEof = 96, + /// + /// SSH_MSG_CHANNEL_CLOSE + /// + ChannelClose = 97, + /// + /// SSH_MSG_CHANNEL_REQUEST + /// + ChannelRequest = 98, + /// + /// SSH_MSG_CHANNEL_SUCCESS + /// + ChannelSuccess = 99, + /// + /// SSH_MSG_CHANNEL_FAILURE + /// + ChannelFailure = 100, + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/ServiceNames.cs b/Renci.SshClient/Renci.SshClient/Messages/ServiceNames.cs new file mode 100644 index 00000000..fc7ba08f --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/ServiceNames.cs @@ -0,0 +1,15 @@ +namespace Renci.SshClient.Messages +{ + internal enum ServiceNames + { + /// + /// ssh-userauth + /// + UserAuthentication, + + /// + /// ssh-connection + /// + Connection + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/AceMasks.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/AceMasks.cs new file mode 100644 index 00000000..cf9e881a --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/AceMasks.cs @@ -0,0 +1,25 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal enum AceMasks + { + ACE4_READ_DATA = 0x00000001, + ACE4_LIST_DIRECTORY = 0x00000001, + ACE4_WRITE_DATA = 0x00000002, + ACE4_ADD_FILE = 0x00000002, + ACE4_APPEND_DATA = 0x00000004, + ACE4_ADD_SUBDIRECTORY = 0x00000004, + ACE4_READ_NAMED_ATTRS = 0x00000008, + ACE4_WRITE_NAMED_ATTRS = 0x00000010, + ACE4_EXECUTE = 0x00000020, + ACE4_DELETE_CHILD = 0x00000040, + ACE4_READ_ATTRIBUTES = 0x00000080, + ACE4_WRITE_ATTRIBUTES = 0x00000100, + ACE4_DELETE = 0x00010000, + ACE4_READ_ACL = 0x00020000, + ACE4_WRITE_ACL = 0x00040000, + ACE4_WRITE_OWNER = 0x00080000, + ACE4_SYNCHRONIZE = 0x00100000 + + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/Attributes.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/Attributes.cs new file mode 100644 index 00000000..cc53809e --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/Attributes.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace Renci.SshClient.Messages.Sftp +{ + internal class Attributes + { + public UInt32 Flag { get; set; } + + public ulong Size { get; set; } + + public uint UserId { get; set; } + + public uint GroupId { get; set; } + + public uint Permissions { get; set; } + + public DateTime AccessTime { get; set; } + + public DateTime ModifyTime { get; set; } + + public IDictionary Extentions { get; set; } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/AttrsMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/AttrsMessage.cs new file mode 100644 index 00000000..45eb64e9 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/AttrsMessage.cs @@ -0,0 +1,25 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class AttrsMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Attrs; } + } + + public Attributes Attributes { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Attributes = this.ReadAttributes(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Attributes); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/CloseMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/CloseMessage.cs new file mode 100644 index 00000000..877719af --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/CloseMessage.cs @@ -0,0 +1,25 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class CloseMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Close; } + } + + public string Handle { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Handle = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Handle); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/DataMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/DataMessage.cs new file mode 100644 index 00000000..29105202 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/DataMessage.cs @@ -0,0 +1,35 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class DataMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Data; } + } + + public string Data { get; set; } + + public bool IsEof { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Data = this.ReadString(); + if (!this.IsEndOfData) + { + this.IsEof = this.ReadBoolean(); + } + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Data); + if (this.IsEof) + { + this.Write(this.IsEof); + } + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/ExtendedMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/ExtendedMessage.cs new file mode 100644 index 00000000..afc35e01 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/ExtendedMessage.cs @@ -0,0 +1,27 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class ExtendedMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Extended; } + } + + public string ExtendedRequest { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.ExtendedRequest = this.ReadString(); + // TODO: Read extended request data + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.ExtendedRequest); + // TODO: Save extended request data + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/ExtendedReplyMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/ExtendedReplyMessage.cs new file mode 100644 index 00000000..9c64d78e --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/ExtendedReplyMessage.cs @@ -0,0 +1,23 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class ExtendedReplyMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.ExtendedReply; } + } + + protected override void LoadData() + { + base.LoadData(); + // TODO: Load request specific reply + } + + protected override void SaveData() + { + base.SaveData(); + // TODO: Save request specific reply + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/FSetStat.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/FSetStat.cs new file mode 100644 index 00000000..a82d61fa --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/FSetStat.cs @@ -0,0 +1,29 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class FSetStat : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.FSetStat; } + } + + public string Handle { get; set; } + + public Attributes Attributes { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Handle = this.ReadString(); + this.Attributes = this.ReadAttributes(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Handle); + this.Write(this.Attributes); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/FSetStatMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/FSetStatMessage.cs new file mode 100644 index 00000000..4ffed0b7 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/FSetStatMessage.cs @@ -0,0 +1,29 @@ +namespace Renci.SshClient.Messages.Sftp +{ + internal class FSetStatMessage : SftpMessage + { + + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.FSetStat; } + } + + public string Handle { get; set; } + + public Attributes Attributes { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Handle = this.ReadString(); + this.Attributes = this.ReadAttributes(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Handle); + this.Write(this.Attributes); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/FStatMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/FStatMessage.cs new file mode 100644 index 00000000..8ab1c6ca --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/FStatMessage.cs @@ -0,0 +1,26 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class FStatMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.FStat; } + } + + public string Handle { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Handle = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Handle); + } + } + +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/Flags.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/Flags.cs new file mode 100644 index 00000000..64b783b7 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/Flags.cs @@ -0,0 +1,30 @@ +namespace Renci.SshClient.Messages.Sftp +{ + internal enum Flags + { + /// + /// SSH_FXF_READ + /// + Read = 0x00000001, + /// + /// SSH_FXF_WRITE + /// + Write = 0x00000002, + /// + /// SSH_FXF_APPEND + /// + Append = 0x00000004, + /// + /// SSH_FXF_CREAT + /// + CreateNewOrOpen = 0x00000008, + /// + /// SSH_FXF_TRUNC + /// + Truncate = 0x00000010, + /// + /// SSH_FXF_EXCL + /// + CreateNew = 0x00000028 + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/HandleMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/HandleMessage.cs new file mode 100644 index 00000000..87483b73 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/HandleMessage.cs @@ -0,0 +1,25 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class HandleMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Handle; } + } + + public string Handle { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Handle = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Handle); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/InitMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/InitMessage.cs new file mode 100644 index 00000000..1c400afa --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/InitMessage.cs @@ -0,0 +1,23 @@ +namespace Renci.SshClient.Messages.Sftp +{ + internal class InitMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Init; } + } + + public uint Version { get; set; } + + protected override void LoadData() + { + + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Version); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/LStatMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/LStatMessage.cs new file mode 100644 index 00000000..aeb3df62 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/LStatMessage.cs @@ -0,0 +1,25 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class LStatMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.LStat; } + } + + public string Path { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Path = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Path); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/MkDirMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/MkDirMessage.cs new file mode 100644 index 00000000..99181aa2 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/MkDirMessage.cs @@ -0,0 +1,33 @@ +namespace Renci.SshClient.Messages.Sftp +{ + internal class MkDirMessage : SftpMessage + { + public MkDirMessage() + { + this.Attributes = new Attributes(); + } + + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.MkDir; } + } + + public string Path { get; set; } + + public Attributes Attributes { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Path = this.ReadString(); + this.Attributes = this.ReadAttributes(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Path); + this.Write(this.Attributes); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/NameMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/NameMessage.cs new file mode 100644 index 00000000..cc0f8f01 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/NameMessage.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using Renci.SshClient.Common; + +namespace Renci.SshClient.Messages.Sftp +{ + internal class NameMessage : SftpMessage + { + + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Name; } + } + + public uint Count { get; set; } + + public IList Files { get; set; } + + public NameMessage() + { + this.Files = new List(); + } + + protected override void LoadData() + { + base.LoadData(); + this.Count = this.ReadUInt32(); + for (int i = 0; i < this.Count; i++) + { + var fileName = this.ReadString(); + var fullName = this.ReadString(); + var attribute = this.ReadAttributes(); + + this.Files.Add(new FtpFileInfo + { + Name = fileName, + FullName = fullName, + Size = attribute.Size, + UserId = attribute.UserId, + GroupId = attribute.GroupId, + LastAccessTime = attribute.AccessTime, + LastModifyTime = attribute.ModifyTime, + Extentions = attribute.Extentions, + }); + } + } + + protected override void SaveData() + { + base.SaveData(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/OpenDirMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/OpenDirMessage.cs new file mode 100644 index 00000000..f7c3d5ee --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/OpenDirMessage.cs @@ -0,0 +1,26 @@ + +using System.Text; +namespace Renci.SshClient.Messages.Sftp +{ + internal class OpenDirMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.OpenDir; } + } + + public string Path { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Path = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Path, Encoding.UTF8); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/OpenMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/OpenMessage.cs new file mode 100644 index 00000000..1afd4bad --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/OpenMessage.cs @@ -0,0 +1,32 @@ +using System; + +namespace Renci.SshClient.Messages.Sftp +{ + internal class OpenMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Open; } + } + + public string Filename { get; set; } + + public Flags Flags { get; set; } + + public Attributes Attributes { get; set; } + + protected override void LoadData() + { + base.LoadData(); + throw new NotSupportedException(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Filename); + this.Write((uint)this.Flags); + this.Write(this.Attributes); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadDirMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadDirMessage.cs new file mode 100644 index 00000000..8c478fb7 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadDirMessage.cs @@ -0,0 +1,25 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class ReadDirMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.ReadDir; } + } + + public string Handle { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Handle = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Handle); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadLinkMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadLinkMessage.cs new file mode 100644 index 00000000..1b4b4566 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadLinkMessage.cs @@ -0,0 +1,26 @@ + +using System.Text; +namespace Renci.SshClient.Messages.Sftp +{ + internal class ReadLinkMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.ReadLink; } + } + + public string Path { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Path = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Path, Encoding.UTF8); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadMessage.cs new file mode 100644 index 00000000..ae8cc733 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/ReadMessage.cs @@ -0,0 +1,34 @@ + +using System; +namespace Renci.SshClient.Messages.Sftp +{ + internal class ReadMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Read; } + } + + public string Handle { get; set; } + + public UInt64 Offset { get; set; } + + public UInt32 Length { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Handle = this.ReadString(); + this.Offset = this.ReadUInt64(); + this.Length = this.ReadUInt32(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Handle); + this.Write(this.Offset); + this.Write(this.Length); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/RealPathMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/RealPathMessage.cs new file mode 100644 index 00000000..cd9b2387 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/RealPathMessage.cs @@ -0,0 +1,25 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class RealPathMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.RealPath; } + } + + public string Path { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Path = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Path); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/RemoveMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/RemoveMessage.cs new file mode 100644 index 00000000..debfcb25 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/RemoveMessage.cs @@ -0,0 +1,26 @@ + +using System.Text; +namespace Renci.SshClient.Messages.Sftp +{ + internal class RemoveMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Remove; } + } + + public string Filename { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Filename = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Filename, Encoding.UTF8); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/RenameMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/RenameMessage.cs new file mode 100644 index 00000000..86d3877d --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/RenameMessage.cs @@ -0,0 +1,28 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class RenameMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Rename; } + } + + public string OldPath { get; set; } + + public string NewPath { get; set; } + protected override void LoadData() + { + base.LoadData(); + this.OldPath = this.ReadString(); + this.NewPath = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.OldPath); + this.Write(this.NewPath); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/RmDirMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/RmDirMessage.cs new file mode 100644 index 00000000..a62a4d8f --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/RmDirMessage.cs @@ -0,0 +1,26 @@ +using System.Text; + +namespace Renci.SshClient.Messages.Sftp +{ + internal class RmDirMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.RmDir; } + } + + public string Path { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Path = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Path, Encoding.UTF8); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/SetStatMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/SetStatMessage.cs new file mode 100644 index 00000000..12c0f979 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/SetStatMessage.cs @@ -0,0 +1,29 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class SetStatMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.SetStat; } + } + + public string Path { get; set; } + + public Attributes Attributes { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Path = this.ReadString(); + this.Attributes = this.ReadAttributes(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Path); + this.Write(this.Attributes); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpDataMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpDataMessage.cs new file mode 100644 index 00000000..5628b4c4 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpDataMessage.cs @@ -0,0 +1,23 @@ +using System.Linq; +using Renci.SshClient.Messages.Connection; + +namespace Renci.SshClient.Messages.Sftp +{ + internal class SftpDataMessage : ChannelMessage + { + public override MessageTypes MessageType + { + get { return MessageTypes.ChannelData; } + } + + public SftpMessage Data { get; set; } + + protected override void SaveData() + { + base.SaveData(); + var data = this.Data.GetBytes(); + this.Write((uint)data.Count() + 4); + this.Write(data.GetSshString()); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpMessage.cs new file mode 100644 index 00000000..43965b14 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpMessage.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Renci.SshClient.Common; + +namespace Renci.SshClient.Messages.Sftp +{ + internal abstract class SftpMessage : SshData + { + private delegate T LoadFunc(IEnumerable data); + + private static IDictionary> _sftpMessageTypes = new Dictionary>(); + + public static SftpMessage Load(IEnumerable data) + { + var messageType = (SftpMessageTypes)data.FirstOrDefault(); + + return Load(data, messageType); + } + + static SftpMessage() + { + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Init, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Version, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Open, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Close, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Read, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Write, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.LStat, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.FStat, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.SetStat, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.FSetStat, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.OpenDir, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.ReadDir, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Remove, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.MkDir, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.RmDir, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.RealPath, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Stat, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Rename, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.ReadLink, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.SymLink, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Status, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Handle, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Data, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Name, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Attrs, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.Extended, new LoadFunc(Load)); + SftpMessage._sftpMessageTypes.Add(SftpMessageTypes.ExtendedReply, new LoadFunc(Load)); + } + + public abstract SftpMessageTypes SftpMessageType { get; } + + public uint? RequestId { get; set; } + + protected override void LoadData() + { + // SSH_FXP_INIT and SSH_FXP_VERSION doesnt have RequestID, all other messaages do + if (!(this.SftpMessageType == SftpMessageTypes.Init || this.SftpMessageType == SftpMessageTypes.Version)) + { + this.RequestId = this.ReadUInt32(); + } + } + + protected override void SaveData() + { + this.Write((byte)this.SftpMessageType); + if (this.RequestId.HasValue) + this.Write(this.RequestId.Value); + } + + protected Attributes ReadAttributes() + { + var attributes = new Attributes(); + attributes.Flag = this.ReadUInt32(); + + var isSize = (attributes.Flag & 0x00000001) == 0x00000001; //SSH_FILEXFER_ATTR_SIZE 0x00000001 + var isUidGid = (attributes.Flag & 0x00000002) == 0x00000002; //SSH_FILEXFER_ATTR_UIDGID 0x00000002 + var isPermissions = (attributes.Flag & 0x00000004) == 0x00000004; //SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 + var isAccessModifyTime = (attributes.Flag & 0x00000008) == 0x00000008; //SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 + + var isExtended = (attributes.Flag & 0x80000000) == 0x80000000; //SSH_FILEXFER_ATTR_EXTENDED 0x80000000 + + if (isSize) + { + attributes.Size = this.ReadUInt64(); + } + + if (isUidGid) + { + attributes.UserId = this.ReadUInt32(); + + attributes.GroupId = this.ReadUInt32(); + } + + if (isPermissions) + { + attributes.Permissions = this.ReadUInt32(); + } + + if (isAccessModifyTime) + { + var time = this.ReadUInt32(); + attributes.AccessTime = DateTime.FromFileTime((time + 11644473600) * 10000000); + time = this.ReadUInt32(); + attributes.ModifyTime = DateTime.FromFileTime((time + 11644473600) * 10000000); + } + + if (isExtended) + { + var extendedCount = this.ReadUInt32(); + attributes.Extentions = this.ReadExtensionPair(); + } + + return attributes; + } + + protected void Write(Attributes attributes) + { + // TODO: Complete attribute serialization, at this point we pass no attributes + if (attributes == null) + { + this.Write((uint)0); + return; + } + else + { + // TODO: Need to be tested + throw new NotImplementedException(); + + var isSize = (attributes.Flag & 0x00000001) == 0x00000001; //SSH_FILEXFER_ATTR_SIZE 0x00000001 + var isUidGid = (attributes.Flag & 0x00000002) == 0x00000002; //SSH_FILEXFER_ATTR_UIDGID 0x00000002 + var isPermissions = (attributes.Flag & 0x00000004) == 0x00000004; //SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 + var isAccessModifyTime = (attributes.Flag & 0x00000008) == 0x00000008; //SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 + + var isExtended = (attributes.Flag & 0x80000000) == 0x80000000; //SSH_FILEXFER_ATTR_EXTENDED 0x80000000 + + if (isSize) + { + this.Write(attributes.Size); + } + + if (isUidGid) + { + this.Write(attributes.UserId); + + this.Write(attributes.GroupId); + } + + if (isPermissions) + { + this.Write(attributes.Permissions); + } + + if (isAccessModifyTime) + { + uint time = (uint)(attributes.AccessTime.ToFileTime() - 11644473600) / 10000000; + this.Write(time); + time = (uint)(attributes.ModifyTime.ToFileTime() - 11644473600) / 10000000; + this.Write(time); + } + + if (isExtended) + { + this.Write(attributes.Extentions); + } + } + } + + private static SftpMessage Load(IEnumerable data, SftpMessageTypes messageType) + { + if (SftpMessage._sftpMessageTypes.ContainsKey(messageType)) + { + return SftpMessage._sftpMessageTypes[messageType](data); + } + else + { + throw new NotSupportedException(string.Format("Message type '{0}' is not registered.", messageType)); + } + } + + private static T Load(IEnumerable data) where T : SftpMessage, new() + { + var messageType = (SftpMessageTypes)data.FirstOrDefault(); + + T message = new T(); + + message.LoadBytes(data); + + message.ResetReader(); + + message.LoadData(); + + return message; + } + + + + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpMessageTypes.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpMessageTypes.cs new file mode 100644 index 00000000..0a4b74a5 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/SftpMessageTypes.cs @@ -0,0 +1,117 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal enum SftpMessageTypes : byte + { + /// + /// SSH_FXP_INIT + /// + Init = 1, + /// + /// SSH_FXP_VERSION + /// + Version = 2, + /// + /// SSH_FXP_OPEN + /// + Open = 3, + /// + /// SSH_FXP_CLOSE + /// + Close = 4, + /// + /// SSH_FXP_READ + /// + Read = 5, + /// + /// SSH_FXP_WRITE + /// + Write = 6, + /// + /// SSH_FXP_LSTAT + /// + LStat = 7, + /// + /// SSH_FXP_FSTAT + /// + FStat = 8, + /// + /// SSH_FXP_SETSTAT + /// + SetStat = 9, + /// + /// SSH_FXP_FSETSTAT + /// + FSetStat = 10, + /// + /// SSH_FXP_OPENDIR + /// + OpenDir = 11, + /// + /// SSH_FXP_READDIR + /// + ReadDir = 12, + /// + /// SSH_FXP_REMOVE + /// + Remove = 13, + /// + /// SSH_FXP_MKDIR + /// + MkDir = 14, + /// + /// SSH_FXP_RMDIR + /// + RmDir = 15, + /// + /// SSH_FXP_REALPATH + /// + RealPath = 16, + /// + /// SSH_FXP_STAT + /// + Stat = 17, + /// + /// SSH_FXP_RENAME + /// + Rename = 18, + /// + /// SSH_FXP_READLINK + /// + ReadLink = 19, + /// + /// SSH_FXP_SYMLINK + /// + SymLink = 20, + /// + /// SSH_FXP_STATUS + /// + Status = 101, + /// + /// SSH_FXP_HANDLE + /// + Handle = 102, + /// + /// SSH_FXP_DATA + /// + Data = 103, + /// + /// SSH_FXP_NAME + /// + Name = 104, + /// + /// SSH_FXP_ATTRS + /// + Attrs = 105, + + /// + /// SSH_FXP_EXTENDED + /// + Extended = 200, + /// + /// SSH_FXP_EXTENDED_REPLY + /// + ExtendedReply = 201 + + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/StatMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/StatMessage.cs new file mode 100644 index 00000000..db30e9c5 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/StatMessage.cs @@ -0,0 +1,25 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal class StatMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Stat; } + } + + public string Path { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Path = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Path); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/StatusCodes.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/StatusCodes.cs new file mode 100644 index 00000000..78fb6d40 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/StatusCodes.cs @@ -0,0 +1,43 @@ + +namespace Renci.SshClient.Messages.Sftp +{ + internal enum StatusCodes : uint + { + /// + /// SSH_FX_OK + /// + Ok = 0, + /// + /// SSH_FX_EOF + /// + Eof = 1, + /// + /// SSH_FX_NO_SUCH_FILE + /// + NoSuchFile = 2, + /// + /// SSH_FX_PERMISSION_DENIED + /// + PermissionDenied = 3, + /// + /// SSH_FX_FAILURE + /// + Failure = 4, + /// + /// SSH_FX_BAD_MESSAGE + /// + BadMessage = 5, + /// + /// SSH_FX_NO_CONNECTION + /// + NoConnection = 6, + /// + /// SSH_FX_CONNECTION_LOST + /// + ConnectionLost = 7, + /// + /// SSH_FX_OP_UNSUPPORTED + /// + OperationUnsupported = 8, + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/StatusMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/StatusMessage.cs new file mode 100644 index 00000000..efef4180 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/StatusMessage.cs @@ -0,0 +1,67 @@ +namespace Renci.SshClient.Messages.Sftp +{ + internal class StatusMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Status; } + } + + public StatusCodes StatusCode { get; set; } + + public string ErrorMessage { get; set; } + + public string Language { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.StatusCode = (StatusCodes)this.ReadUInt32(); + + switch (this.StatusCode) + { + case StatusCodes.Ok: + break; + case StatusCodes.Eof: + break; + case StatusCodes.NoSuchFile: + break; + case StatusCodes.PermissionDenied: + break; + case StatusCodes.Failure: + break; + case StatusCodes.BadMessage: + break; + case StatusCodes.NoConnection: + break; + case StatusCodes.ConnectionLost: + break; + case StatusCodes.OperationUnsupported: + break; + default: + break; + } + + if (!this.IsEndOfData) + { + this.ErrorMessage = this.ReadString(); + this.Language = this.ReadString(); + } + // TODO: Load error specific data + } + + protected override void SaveData() + { + base.SaveData(); + this.Write((uint)this.StatusCode); + if (this.StatusCode == StatusCodes.Ok) + { + // No more data need to be written + return; + } + this.Write(this.ErrorMessage); + this.Write(this.Language); + // TODO: Save error specific data + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/SymLinkMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/SymLinkMessage.cs new file mode 100644 index 00000000..0d00eab3 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/SymLinkMessage.cs @@ -0,0 +1,34 @@ +using System.Text; + +namespace Renci.SshClient.Messages.Sftp +{ + internal class SymLinkMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.SymLink; } + } + + public string NewLinkPath { get; set; } + + public string ExistingPath { get; set; } + + public bool IsSymLink { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.NewLinkPath = this.ReadString(); + this.ExistingPath = this.ReadString(); + this.IsSymLink = this.ReadBoolean(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.NewLinkPath, Encoding.UTF8); + this.Write(this.ExistingPath, Encoding.UTF8); + this.Write(this.IsSymLink); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/VersionMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/VersionMessage.cs new file mode 100644 index 00000000..77a16680 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/VersionMessage.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace Renci.SshClient.Messages.Sftp +{ + internal class VersionMessage : SftpMessage + { + public VersionMessage() + { + this.Extentions = new Dictionary(); + } + + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Version; } + } + + public uint Version { get; set; } + + public IDictionary Extentions { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Version = this.ReadUInt32(); + this.Extentions = this.ReadExtensionPair(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Version); + this.Write(this.Extentions); + } + + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Sftp/WriteMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Sftp/WriteMessage.cs new file mode 100644 index 00000000..aed3e1c1 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Sftp/WriteMessage.cs @@ -0,0 +1,34 @@ +using System; + +namespace Renci.SshClient.Messages.Sftp +{ + internal class WriteMessage : SftpMessage + { + public override SftpMessageTypes SftpMessageType + { + get { return SftpMessageTypes.Write; } + } + + public string Handle { get; set; } + + public UInt64 Offset { get; set; } + + public string Data { get; set; } + + protected override void LoadData() + { + base.LoadData(); + this.Handle = this.ReadString(); + this.Offset = this.ReadUInt64(); + this.Data = this.ReadString(); + } + + protected override void SaveData() + { + base.SaveData(); + this.Write(this.Handle); + this.Write(this.Offset); + this.Write(this.Data); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Transport/DebugMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Transport/DebugMessage.cs new file mode 100644 index 00000000..0597b39e --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Transport/DebugMessage.cs @@ -0,0 +1,22 @@ +using System; + +namespace Renci.SshClient.Messages.Transport +{ + internal class DebugMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.Debug; } + } + + protected override void LoadData() + { + throw new NotImplementedException(); + } + + protected override void SaveData() + { + throw new NotImplementedException(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Transport/DisconnectMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Transport/DisconnectMessage.cs new file mode 100644 index 00000000..3717fa0d --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Transport/DisconnectMessage.cs @@ -0,0 +1,32 @@ +using System.Text; + +namespace Renci.SshClient.Messages.Transport +{ + internal class DisconnectMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.Disconnect; } + } + + public DisconnectReasonCodes ReasonCode { get; set; } + + public string Description { get; set; } + + public string Language { get; set; } + + protected override void LoadData() + { + this.ReasonCode = (DisconnectReasonCodes)this.ReadUInt32(); + this.Description = this.ReadString(); + this.Language = this.ReadString(); + } + + protected override void SaveData() + { + this.Write((uint)this.ReasonCode); + this.Write(this.Description, Encoding.UTF8); + this.Write(this.Language ?? "en"); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Transport/DisconnectReasonCodes.cs b/Renci.SshClient/Renci.SshClient/Messages/Transport/DisconnectReasonCodes.cs new file mode 100644 index 00000000..33461802 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Transport/DisconnectReasonCodes.cs @@ -0,0 +1,66 @@ +namespace Renci.SshClient.Messages.Transport +{ + public enum DisconnectReasonCodes : uint + { + /// + /// SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT + /// + HostNotAllowedToConnect = 1, + /// + /// SSH_DISCONNECT_PROTOCOL_ERROR + /// + ProtocolError = 2, + /// + /// SSH_DISCONNECT_KEY_EXCHANGE_FAILED + /// + KeyExchangeFailed = 3, + /// + /// SSH_DISCONNECT_RESERVED + /// + Reserved = 4, + /// + /// SSH_DISCONNECT_MAC_ERROR + /// + MacError = 5, + /// + /// SSH_DISCONNECT_COMPRESSION_ERROR + /// + CompressionError = 6, + /// + /// SSH_DISCONNECT_SERVICE_NOT_AVAILABLE + /// + ServiceNotAvailable = 7, + /// + /// SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED + /// + ProtocolVersionNotSupported = 8, + /// + /// SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE + /// + HostKeyNotVerifiable = 9, + /// + /// SSH_DISCONNECT_CONNECTION_LOST + /// + ConnectionLost = 10, + /// + /// SSH_DISCONNECT_BY_APPLICATION + /// + ByApplication = 11, + /// + /// SSH_DISCONNECT_TOO_MANY_CONNECTIONS + /// + TooManyConnections = 12, + /// + /// SSH_DISCONNECT_AUTH_CANCELLED_BY_USER + /// + AuthenticationCancelledByUser = 13, + /// + /// SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE + /// + NoMoreAuthenticationMethodsAvailable = 14, + /// + /// SSH_DISCONNECT_ILLEGAL_USER_NAME + /// + IllegalUserName = 15, + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Transport/IgnoreMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Transport/IgnoreMessage.cs new file mode 100644 index 00000000..a2c8aa6d --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Transport/IgnoreMessage.cs @@ -0,0 +1,22 @@ +using System; + +namespace Renci.SshClient.Messages.Transport +{ + internal class IgnoreMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.Ignore; } + } + + protected override void LoadData() + { + throw new NotImplementedException(); + } + + protected override void SaveData() + { + throw new NotImplementedException(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeDhInitMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeDhInitMessage.cs new file mode 100644 index 00000000..f8ba011b --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeDhInitMessage.cs @@ -0,0 +1,28 @@ +using System.Numerics; + +namespace Renci.SshClient.Messages.Transport +{ + internal class KeyExchangeDhInitMessage : Message + { + public BigInteger E { get; set; } + + public override MessageTypes MessageType + { + get + { + return MessageTypes.DiffieHellmanKeyExchangeInit; + } + } + + protected override void LoadData() + { + this.ResetReader(); + this.E = this.ReadBigInteger(); + } + + protected override void SaveData() + { + this.Write(this.E); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeDhReplyMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeDhReplyMessage.cs new file mode 100644 index 00000000..0a7b3a07 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeDhReplyMessage.cs @@ -0,0 +1,46 @@ +using System; +using System.Numerics; + +namespace Renci.SshClient.Messages.Transport +{ + internal class KeyExchangeDhReplyMessage : Message + { + /// + /// Gets server public host key and certificates + /// + /// The host key. + public string HostKey { get; private set; } + + public BigInteger F { get; private set; } + + /// + /// Gets the signature of H. + /// + /// The signature. + public string Signature { get; private set; } + + public override MessageTypes MessageType + { + get + { + return MessageTypes.KeyExchangeDhReply; + } + } + + protected override void LoadData() + { + this.ResetReader(); + this.HostKey = this.ReadString(); + this.F = this.ReadBigInteger(); + this.Signature = this.ReadString(); + + // TODO: Determine which algorithms to use from signature + + } + + protected override void SaveData() + { + throw new NotSupportedException("SaveData is not supported for KeyExchangeDhReplyMessage class"); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeInitMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeInitMessage.cs new file mode 100644 index 00000000..ee867004 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Transport/KeyExchangeInitMessage.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; + +namespace Renci.SshClient.Messages.Transport +{ + internal class KeyExchangeInitMessage : Message + { + private static RNGCryptoServiceProvider _randomizer = new System.Security.Cryptography.RNGCryptoServiceProvider(); + + public override MessageTypes MessageType + { + get { return MessageTypes.KeyExchangeInit; } + } + + public KeyExchangeInitMessage() + { + var cookie = new byte[16]; + _randomizer.GetBytes(cookie); + this.Cookie = cookie; + } + + #region Message Properties + + public IEnumerable Cookie { get; private set; } + + public IEnumerable KeyExchangeAlgorithms { get; set; } + + public IEnumerable ServerHostKeyAlgorithms { get; set; } + + public IEnumerable EncryptionAlgorithmsClientToServer { get; set; } + + public IEnumerable EncryptionAlgorithmsServerToClient { get; set; } + + public IEnumerable MacAlgorithmsClientToSserver { get; set; } + + public IEnumerable MacAlgorithmsServerToClient { get; set; } + + public IEnumerable CompressionAlgorithmsClientToServer { get; set; } + + public IEnumerable CompressionAlgorithmsServerToClient { get; set; } + + public IEnumerable LanguagesClientToServer { get; set; } + + public IEnumerable LanguagesServerToClient { get; set; } + + public bool FirstKexPacketFollows { get; set; } + + public UInt32 Reserved { get; set; } + + #endregion + + protected override void LoadData() + { + this.ResetReader(); + + this.Cookie = this.ReadBytes(16); + this.KeyExchangeAlgorithms = this.ReadNamesList(); + this.ServerHostKeyAlgorithms = this.ReadNamesList(); + this.EncryptionAlgorithmsClientToServer = this.ReadNamesList(); + this.EncryptionAlgorithmsServerToClient = this.ReadNamesList(); + this.MacAlgorithmsClientToSserver = this.ReadNamesList(); + this.MacAlgorithmsServerToClient = this.ReadNamesList(); + this.CompressionAlgorithmsClientToServer = this.ReadNamesList(); + this.CompressionAlgorithmsServerToClient = this.ReadNamesList(); + this.LanguagesClientToServer = this.ReadNamesList(); + this.LanguagesServerToClient = this.ReadNamesList(); + this.FirstKexPacketFollows = this.ReadBoolean(); + this.Reserved = this.ReadUInt32(); + } + + protected override void SaveData() + { + this.Write(this.Cookie); + this.Write(this.KeyExchangeAlgorithms); + this.Write(this.ServerHostKeyAlgorithms); + this.Write(this.EncryptionAlgorithmsClientToServer); + this.Write(this.EncryptionAlgorithmsServerToClient); + this.Write(this.MacAlgorithmsClientToSserver); + this.Write(this.MacAlgorithmsServerToClient); + this.Write(this.CompressionAlgorithmsClientToServer); + this.Write(this.CompressionAlgorithmsServerToClient); + this.Write(this.LanguagesClientToServer); + this.Write(this.LanguagesServerToClient); + this.Write(this.FirstKexPacketFollows); + this.Write(this.Reserved); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Transport/NewKeysMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Transport/NewKeysMessage.cs new file mode 100644 index 00000000..05bec349 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Transport/NewKeysMessage.cs @@ -0,0 +1,18 @@ +namespace Renci.SshClient.Messages.Transport +{ + internal class NewKeysMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.NewKeys; } + } + + protected override void LoadData() + { + } + + protected override void SaveData() + { + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Transport/ServiceAcceptMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Transport/ServiceAcceptMessage.cs new file mode 100644 index 00000000..d74fb903 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Transport/ServiceAcceptMessage.cs @@ -0,0 +1,38 @@ +using System; + +namespace Renci.SshClient.Messages.Transport +{ + /// + /// SSH_MSG_SERVICE_ACCEPT + /// + internal class ServiceAcceptMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.ServiceAcceptRequest; } + } + + public ServiceNames ServiceName { get; set; } + + protected override void LoadData() + { + var serviceName = this.ReadString(); + switch (serviceName) + { + case "ssh-userauth": + this.ServiceName = ServiceNames.UserAuthentication; + break; + case "ssh-connection": + this.ServiceName = ServiceNames.Connection; + break; + default: + break; + } + } + + protected override void SaveData() + { + throw new InvalidOperationException("Save data is not supported."); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Transport/ServiceRequestMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Transport/ServiceRequestMessage.cs new file mode 100644 index 00000000..246e2fc5 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Transport/ServiceRequestMessage.cs @@ -0,0 +1,38 @@ +using System; + +namespace Renci.SshClient.Messages.Transport +{ + /// + /// Contains SSH_MSG_SERVICE_REQUEST message information + /// + internal class ServiceRequestMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.ServiceRequest; } + } + + public ServiceNames ServiceName { get; set; } + + protected override void LoadData() + { + throw new InvalidOperationException("Load data is not supported."); + } + + protected override void SaveData() + { + switch (this.ServiceName) + { + case ServiceNames.UserAuthentication: + this.Write("ssh-userauth"); + break; + case ServiceNames.Connection: + this.Write("ssh-connection"); + break; + default: + throw new NotSupportedException("Not supported service name"); + } + + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Messages/Transport/UnimplementedMessage.cs b/Renci.SshClient/Renci.SshClient/Messages/Transport/UnimplementedMessage.cs new file mode 100644 index 00000000..6c025db4 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Messages/Transport/UnimplementedMessage.cs @@ -0,0 +1,22 @@ +using System; + +namespace Renci.SshClient.Messages.Transport +{ + internal class UnimplementedMessage : Message + { + public override MessageTypes MessageType + { + get { return MessageTypes.Unimplemented; } + } + + protected override void LoadData() + { + throw new NotImplementedException(); + } + + protected override void SaveData() + { + throw new NotImplementedException(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/PrivateKey.cs b/Renci.SshClient/Renci.SshClient/PrivateKey.cs new file mode 100644 index 00000000..ae81d511 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/PrivateKey.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using Renci.SshClient.Common; + +namespace Renci.SshClient +{ + internal abstract class PrivateKey + { + public abstract string AlgorithmName { get; } + + protected IEnumerable Data { get; private set; } + + public abstract IEnumerable PublicKey { get; } + + public PrivateKey(IEnumerable data) + { + this.Data = data; + } + + public abstract IEnumerable GetSignature(IEnumerable sessionId); + + protected class SignatureKeyData : SshData + { + public string AlgorithmName { get; set; } + + public IEnumerable Signature { get; set; } + + protected override void LoadData() + { + } + + protected override void SaveData() + { + this.Write(this.AlgorithmName); + this.Write(this.Signature.GetSshString()); + } + } + + } +} diff --git a/Renci.SshClient/Renci.SshClient/PrivateKeyDsa.cs b/Renci.SshClient/Renci.SshClient/PrivateKeyDsa.cs new file mode 100644 index 00000000..16c74e69 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/PrivateKeyDsa.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using Renci.SshClient.Common; + +namespace Renci.SshClient +{ + internal class PrivateKeyDsa : PrivateKey + { + private byte[] _pValue; + + private byte[] _qValue; + + private byte[] _gValue; + + private byte[] _publicKeyValue; + + private byte[] _privateKeyValue; + + private IEnumerable _publicKey; + /// + /// Gets the public key. + /// + /// The public key. + public override IEnumerable PublicKey + { + get + { + if (this._publicKey == null) + { + this._publicKey = new DsaPublicKeyData + { + P = this._pValue, + Q = this._qValue, + G = this._gValue, + Public = this._publicKeyValue, + }.GetBytes(); + + } + return this._publicKey; + } + } + + public override string AlgorithmName + { + get { return "ssh-dss"; } + } + + + public PrivateKeyDsa(IEnumerable data) + : base(data) + { + if (!this.ParseDSAPrivateKey()) + { + throw new InvalidDataException("DSA Key is not valid"); + } + } + + public override IEnumerable GetSignature(IEnumerable sessionId) + { + var data = sessionId.ToArray(); + using (var sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider()) + using (var cs = new System.Security.Cryptography.CryptoStream(System.IO.Stream.Null, sha1, System.Security.Cryptography.CryptoStreamMode.Write)) + { + DSAParameters DSAKeyInfo = new DSAParameters(); + + DSAKeyInfo.X = this._privateKeyValue.TrimLeadinZero().ToArray(); + DSAKeyInfo.P = this._pValue.TrimLeadinZero().ToArray(); + DSAKeyInfo.Q = this._qValue.TrimLeadinZero().ToArray(); + DSAKeyInfo.G = this._gValue.TrimLeadinZero().ToArray(); + + cs.Write(data, 0, data.Length); + + cs.Close(); + + var DSA = new System.Security.Cryptography.DSACryptoServiceProvider(); + DSA.ImportParameters(DSAKeyInfo); + var DSAFormatter = new RSAPKCS1SignatureFormatter(DSA); + DSAFormatter.SetHashAlgorithm("SHA1"); + + var signature = DSAFormatter.CreateSignature(sha1); + + return new SignatureKeyData + { + AlgorithmName = this.AlgorithmName, + Signature = signature, + }.GetBytes(); + } + + } + + private bool ParseDSAPrivateKey() + { + // --------- Set up stream to decode the asn.1 encoded RSA private key ------ + using (var ms = new MemoryStream(this.Data.ToArray())) + using (var binr = new BinaryReader(ms)) //wrap Memory Stream with BinaryReader for easy reading + { + byte bt = 0; + ushort twobytes = 0; + int elems = 0; + + twobytes = binr.ReadUInt16(); + if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) + binr.ReadByte(); //advance 1 byte + else if (twobytes == 0x8230) + binr.ReadInt16(); //advance 2 bytes + else + return false; + + twobytes = binr.ReadUInt16(); + if (twobytes != 0x0102) //version number + return false; + bt = binr.ReadByte(); + if (bt != 0x00) + return false; + + //------ all private key components are Integer sequences ---- + elems = GetIntegerSize(binr); + this._pValue = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + this._qValue = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + this._gValue = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + this._publicKeyValue = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + this._privateKeyValue = binr.ReadBytes(elems); + } + + return true; + } + + private static int GetIntegerSize(BinaryReader binr) + { + byte bt = 0; + byte lowbyte = 0x00; + byte highbyte = 0x00; + int count = 0; + bt = binr.ReadByte(); + if (bt != 0x02) //expect integer + return 0; + bt = binr.ReadByte(); + + if (bt == 0x81) + count = binr.ReadByte(); // data size in next byte + else + if (bt == 0x82) + { + highbyte = binr.ReadByte(); // data size in next 2 bytes + lowbyte = binr.ReadByte(); + byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; + count = BitConverter.ToInt32(modint, 0); + } + else + { + count = bt; // we already have the data size + } + + return count; + } + + private class DsaPublicKeyData : SshData + { + public IEnumerable P { get; set; } + + public IEnumerable Q { get; set; } + + public IEnumerable G { get; set; } + + public IEnumerable Public { get; set; } + + protected override void LoadData() + { + } + + protected override void SaveData() + { + this.Write("ssh-dss"); + this.Write(this.P.GetSshString()); + this.Write(this.Q.GetSshString()); + this.Write(this.G.GetSshString()); + this.Write(this.Public.GetSshString()); + } + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/PrivateKeyRsa.cs b/Renci.SshClient/Renci.SshClient/PrivateKeyRsa.cs new file mode 100644 index 00000000..c3623043 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/PrivateKeyRsa.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using Renci.SshClient.Common; + +namespace Renci.SshClient +{ + internal class PrivateKeyRsa : PrivateKey + { + private byte[] _modulus; + private byte[] _eValue; + private byte[] _dValue; + private byte[] _pValue; + private byte[] _qValue; + private byte[] _dpValue; + private byte[] _dqValue; + private byte[] _iqValue; + + private IEnumerable _publicKey; + /// + /// Gets the public key. + /// + /// The public key. + public override IEnumerable PublicKey + { + get + { + if (this._publicKey == null) + { + this._publicKey = new RsaPublicKeyData + { + E = this._eValue, + Modulus = this._modulus, + }.GetBytes(); + + } + return this._publicKey; + } + } + + public override string AlgorithmName + { + get { return "ssh-rsa"; } + } + + + public PrivateKeyRsa(IEnumerable data) + : base(data) + { + if (!this.ParseRSAPrivateKey()) + { + throw new InvalidDataException("RSA Key is not valid"); + } + } + + public override IEnumerable GetSignature(IEnumerable sessionId) + { + var data = sessionId.ToArray(); + using (var sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider()) + using (var cs = new System.Security.Cryptography.CryptoStream(System.IO.Stream.Null, sha1, System.Security.Cryptography.CryptoStreamMode.Write)) + { + RSAParameters RSAKeyInfo = new RSAParameters(); + + RSAKeyInfo.Exponent = _eValue.TrimLeadinZero().ToArray(); + RSAKeyInfo.D = _dValue.TrimLeadinZero().ToArray(); + RSAKeyInfo.Modulus = _modulus.TrimLeadinZero().ToArray(); + RSAKeyInfo.P = _pValue.TrimLeadinZero().ToArray(); + RSAKeyInfo.Q = _qValue.TrimLeadinZero().ToArray(); + RSAKeyInfo.DP = _dpValue.TrimLeadinZero().ToArray(); + RSAKeyInfo.DQ = _dqValue.TrimLeadinZero().ToArray(); + RSAKeyInfo.InverseQ = _iqValue.TrimLeadinZero().ToArray(); + + cs.Write(data, 0, data.Length); + + cs.Close(); + + var RSA = new System.Security.Cryptography.RSACryptoServiceProvider(); + RSA.ImportParameters(RSAKeyInfo); + var RSAFormatter = new RSAPKCS1SignatureFormatter(RSA); + RSAFormatter.SetHashAlgorithm("SHA1"); + + var signature = RSAFormatter.CreateSignature(sha1); + + return new SignatureKeyData + { + AlgorithmName = this.AlgorithmName, + Signature = signature, + }.GetBytes(); + } + + } + + private bool ParseRSAPrivateKey() + { + // --------- Set up stream to decode the asn.1 encoded RSA private key ------ + using (var ms = new MemoryStream(this.Data.ToArray())) + using (var binr = new BinaryReader(ms)) //wrap Memory Stream with BinaryReader for easy reading + { + byte bt = 0; + ushort twobytes = 0; + int elems = 0; + + twobytes = binr.ReadUInt16(); + if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) + binr.ReadByte(); //advance 1 byte + else if (twobytes == 0x8230) + binr.ReadInt16(); //advance 2 bytes + else + return false; + + twobytes = binr.ReadUInt16(); + if (twobytes != 0x0102) //version number + return false; + bt = binr.ReadByte(); + if (bt != 0x00) + return false; + + + //------ all private key components are Integer sequences ---- + elems = GetIntegerSize(binr); + this._modulus = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + this._eValue = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + this._dValue = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + this._pValue = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + this._qValue = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + this._dpValue = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + this._dqValue = binr.ReadBytes(elems); + + elems = GetIntegerSize(binr); + this._iqValue = binr.ReadBytes(elems); + + return true; + } + } + + private static int GetIntegerSize(BinaryReader binr) + { + byte bt = 0; + byte lowbyte = 0x00; + byte highbyte = 0x00; + int count = 0; + bt = binr.ReadByte(); + if (bt != 0x02) //expect integer + return 0; + bt = binr.ReadByte(); + + if (bt == 0x81) + count = binr.ReadByte(); // data size in next byte + else + if (bt == 0x82) + { + highbyte = binr.ReadByte(); // data size in next 2 bytes + lowbyte = binr.ReadByte(); + byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; + count = BitConverter.ToInt32(modint, 0); + } + else + { + count = bt; // we already have the data size + } + + return count; + } + + private class RsaPublicKeyData : SshData + { + public IEnumerable Modulus { get; set; } + + public IEnumerable E { get; set; } + + protected override void LoadData() + { + } + + protected override void SaveData() + { + this.Write("ssh-rsa"); + this.Write(this.E.GetSshString()); + this.Write(this.Modulus.GetSshString()); + } + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Properties/AssemblyInfo.cs b/Renci.SshClient/Renci.SshClient/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..df220b7f --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SshClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("SshClient")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ad816c5e-6f13-4589-9f3e-59523f8b77a4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.1")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj new file mode 100644 index 00000000..289b9f55 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj @@ -0,0 +1,194 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {2F5F8C90-0BD1-424F-997C-7BC6280919D1} + Library + Properties + Renci.SshClient + Renci.SshClient + v4.0 + 512 + SAK + SAK + SAK + SAK + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj.vspscc b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj.vspscc new file mode 100644 index 00000000..feffdeca --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/Renci.SshClient/Renci.SshClient/Services/ConnectionService.cs b/Renci.SshClient/Renci.SshClient/Services/ConnectionService.cs new file mode 100644 index 00000000..913d9e2b --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Services/ConnectionService.cs @@ -0,0 +1,29 @@ +using System; +using Renci.SshClient.Messages; + +namespace Renci.SshClient.Services +{ + internal class ConnectionService : Service + { + public override ServiceNames ServiceName + { + get { throw new NotImplementedException(); } + } + + public ConnectionService(SessionInfo sessionInfo) + : base(sessionInfo) + { + + } + + //public override void Request() + //{ + // throw new NotImplementedException(); + //} + + //public override void Accept() + //{ + // throw new NotImplementedException(); + //} + } +} diff --git a/Renci.SshClient/Renci.SshClient/Services/Service.cs b/Renci.SshClient/Renci.SshClient/Services/Service.cs new file mode 100644 index 00000000..041771b9 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Services/Service.cs @@ -0,0 +1,22 @@ +using Renci.SshClient.Messages; + +namespace Renci.SshClient.Services +{ + internal abstract class Service + { + public abstract ServiceNames ServiceName { get; } + + protected SessionInfo SessionInfo { get; private set; } + + public Service(SessionInfo sessionInfo) + { + this.SessionInfo = sessionInfo; + } + + protected void SendMessage(Message message) + { + this.SessionInfo.SendMessage(message); + } + + } +} diff --git a/Renci.SshClient/Renci.SshClient/Services/UserAuthentication.cs b/Renci.SshClient/Renci.SshClient/Services/UserAuthentication.cs new file mode 100644 index 00000000..0dd59246 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Services/UserAuthentication.cs @@ -0,0 +1,23 @@ +using Renci.SshClient.Messages; +namespace Renci.SshClient.Services +{ + internal abstract class UserAuthentication + { + public abstract string Name { get; } + + protected SessionInfo SessionInfo { get; private set; } + + public UserAuthentication(SessionInfo sessionInfo) + { + this.SessionInfo = sessionInfo; + } + + public abstract bool Start(); + + protected void SendMessage(Message message) + { + this.SessionInfo.SendMessage(message); + } + + } +} diff --git a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationHost.cs b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationHost.cs new file mode 100644 index 00000000..9f258c29 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationHost.cs @@ -0,0 +1,24 @@ + +namespace Renci.SshClient.Services +{ + internal class UserAuthenticationHost : UserAuthentication + { + public override string Name + { + get + { + return "hostbased"; + } + } + public UserAuthenticationHost(SessionInfo sessionInfo) + : base(sessionInfo) + { + + } + + public override bool Start() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPassword.cs b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPassword.cs new file mode 100644 index 00000000..e2f9130e --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPassword.cs @@ -0,0 +1,40 @@ +using Renci.SshClient.Messages; +using Renci.SshClient.Messages.Authentication; + +namespace Renci.SshClient.Services +{ + internal class UserAuthenticationPassword : UserAuthentication + { + public override string Name + { + get + { + return "password"; + } + } + + public UserAuthenticationPassword(SessionInfo sessionInfo) + : base(sessionInfo) + { + + } + + public override bool Start() + { + // TODO: Handle all user authentication messages + //Message.RegisterMessageType(MessageTypes.UserAuthenticationPasswordChangeRequired); + + if (!string.IsNullOrEmpty(this.SessionInfo.ConnectionInfo.Password)) + { + this.SendMessage(new PasswordRequestMessage + { + ServiceName = ServiceNames.Connection, + Username = this.SessionInfo.ConnectionInfo.Username, + Password = this.SessionInfo.ConnectionInfo.Password, + }); + return true; + } + return false; + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPublicKey.cs b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPublicKey.cs new file mode 100644 index 00000000..1d63095b --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPublicKey.cs @@ -0,0 +1,41 @@ + +using Renci.SshClient.Messages; +using Renci.SshClient.Messages.Authentication; +namespace Renci.SshClient.Services +{ + internal class UserAuthenticationPublicKey : UserAuthentication + { + public override string Name + { + get + { + return "publickey"; + } + } + + public UserAuthenticationPublicKey(SessionInfo sessionInfo) + : base(sessionInfo) + { + + } + + + public override bool Start() + { + if (this.SessionInfo.ConnectionInfo.KeyFile != null) + { + // TODO: Complete full public key implemention which includes other messages + this.SendMessage(new PublicKeyRequestMessage + { + ServiceName = ServiceNames.Connection, + Username = this.SessionInfo.ConnectionInfo.Username, + PublicKeyAlgorithmName = this.SessionInfo.ConnectionInfo.KeyFile.AlgorithmName, + PublicKeyData = this.SessionInfo.ConnectionInfo.KeyFile.PublicKey, + Signature = this.SessionInfo.ConnectionInfo.KeyFile.GetSignature(this.SessionInfo.SessionId), + }); + return true; + } + return false; + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationService.cs b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationService.cs new file mode 100644 index 00000000..788d8fb4 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationService.cs @@ -0,0 +1,154 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Renci.SshClient.Common; +using Renci.SshClient.Messages; +using Renci.SshClient.Messages.Authentication; +using Renci.SshClient.Messages.Transport; + +namespace Renci.SshClient.Services +{ + internal class UserAuthenticationService : Service + { + private IList _executedMethods = new List(); + + private EventWaitHandle _serviceAccepted = new AutoResetEvent(false); + + private EventWaitHandle _authenticationCompleted = new AutoResetEvent(false); + + public override ServiceNames ServiceName + { + get { return ServiceNames.UserAuthentication; } + } + + public EventWaitHandle AuthenticationCompletedHandle { get; private set; } + + public string ErrorMessage { get; private set; } + + public bool IsAuthenticated { get; private set; } + + public UserAuthenticationService(SessionInfo sessionInfo) + : base(sessionInfo) + { + this.AuthenticationCompletedHandle = new AutoResetEvent(false); + } + + public void AuthenticateUser() + { + // Register Authentication response messages + Message.RegisterMessageType(MessageTypes.UserAuthenticationFailure); + Message.RegisterMessageType(MessageTypes.UserAuthenticationSuccess); + Message.RegisterMessageType(MessageTypes.UserAuthenticationBanner); + + // Attach event handlers to handle messages + this.SessionInfo.MessageReceived += SessionInfo_MessageReceived; + + // Request user authorization service + this.SendMessage(new ServiceRequestMessage + { + ServiceName = ServiceNames.UserAuthentication, + }); + + // Wait for service to be accepted + this.SessionInfo.WaitHandle(this._serviceAccepted); + + // Start by quering supported authentication methods + this.SendMessage(new RequestMessage + { + Username = this.SessionInfo.ConnectionInfo.Username, + ServiceName = ServiceNames.Connection, + }); + + // Wait for authentication to be completed + this.SessionInfo.WaitHandle(this._authenticationCompleted); + + this.SessionInfo.MessageReceived -= SessionInfo_MessageReceived; + + Message.UnRegisterMessageType(MessageTypes.UserAuthenticationFailure); + Message.UnRegisterMessageType(MessageTypes.UserAuthenticationSuccess); + Message.UnRegisterMessageType(MessageTypes.UserAuthenticationBanner); + } + + private void SessionInfo_MessageReceived(object sender, MessageReceivedEventArgs e) + { + this.HandleMessage((dynamic)e.Message); + } + + private void HandleMessage(T message) + { + // Ignore messages that cannot be handled by this module + } + + private void HandleMessage(ServiceAcceptMessage message) + { + if (message.ServiceName == ServiceNames.UserAuthentication) + { + this._serviceAccepted.Set(); + } + } + + private void HandleMessage(SuccessMessage message) + { + this.AuthenticationSucceded(); + } + + private void HandleMessage(FailureMessage message) + { + if (message.PartialSuccess) + { + this.AuthenticationFailed(message.Message); + return; + } + + // Get method that was not executed yet + var methodsToTry = message.AllowedAuthentications.Except(this._executedMethods); + + if (methodsToTry.Count() == 0) + { + this.AuthenticationFailed(string.Format("User '{0}' cannot be authorized.", this.SessionInfo.ConnectionInfo.Username)); + return; + } + + // Execute authentication method + foreach (var methodName in methodsToTry) + { + UserAuthentication userAuthentication = null; + + if (methodName == "publickey") + { + userAuthentication = new UserAuthenticationPublicKey(this.SessionInfo); + } + else if (methodName == "password") + { + userAuthentication = new UserAuthenticationPassword(this.SessionInfo); + } + this._executedMethods.Add(methodName); + if (userAuthentication != null) + { + if (userAuthentication.Start()) + break; + } + } + } + + private void HandleMessage(BannerMessage message) + { + } + + private void AuthenticationFailed(string message) + { + this.IsAuthenticated = false; + this.ErrorMessage = message; + this._authenticationCompleted.Set(); + } + + private void AuthenticationSucceded() + { + this.IsAuthenticated = true; + this._authenticationCompleted.Set(); + } + + + + } +} diff --git a/Renci.SshClient/Renci.SshClient/Session.cs b/Renci.SshClient/Renci.SshClient/Session.cs new file mode 100644 index 00000000..f86e4a71 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Session.cs @@ -0,0 +1,418 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using Renci.SshClient.Algorithms; +using Renci.SshClient.Common; +using Renci.SshClient.Messages; +using Renci.SshClient.Messages.Connection; +using Renci.SshClient.Messages.Transport; +using Renci.SshClient.Services; + +namespace Renci.SshClient +{ + internal abstract class Session : IDisposable + { + protected const int MAXIMUM_PACKET_SIZE = 35000; + + public static Session CreateSession(ConnectionInfo connectionInfo) + { + var ep = new IPEndPoint(Dns.GetHostAddresses(connectionInfo.Host)[0], connectionInfo.Port); + var socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + socket.Connect(ep); + socket.ReceiveTimeout = 5 * 1000; // Set default receive timeout to 5 seconds + + // Get server version from the server, + // ignore text lines which are sent before if any + var serverVersion = string.Empty; + + using (StreamReader sr = new StreamReader(new NetworkStream(socket))) + { + while (true) + { + serverVersion = sr.ReadLine(); + if (serverVersion.StartsWith("SSH")) + { + break; + } + } + } + + // TODO: Create session based on server version + var session = new SessionSSHv2(connectionInfo, socket, serverVersion); + + return session; + } + + private ConnectionInfo _connectionInfo; + + private Socket _socket; + + private KeyExchange _keyExhcange; + + private BackgroundWorker _messageListener; + + private EventWaitHandle _keyExhangedFinishedWaitHandle = new AutoResetEvent(false); + + private IDictionary _services = new Dictionary(); + + private IDictionary _openChannels = new Dictionary(); + + protected HMAC ServerMac { get; private set; } + + protected HMAC ClientMac { get; private set; } + + protected ICryptoTransform Encryption { get; private set; } + + protected ICryptoTransform Decryption { get; private set; } + + protected Compression ServerDecompression { get; private set; } + + protected Compression ClientCompression { get; private set; } + + internal SessionInfo SessionInfo { get; private set; } + + public bool IsConnected + { + get + { + // TODO: Add condition that user is authenticated + // TODO: Add condition that connection was not terminated byt MSG_DISCONNECT + return this._socket.Connected; + } + } + + protected Session(ConnectionInfo connectionInfo, Socket socket, string serverVersion) + { + this._connectionInfo = connectionInfo; + this._socket = socket; + + this.SessionInfo = new SessionInfo(this.SendMessage, this._connectionInfo, socket.ReceiveTimeout); + this.SessionInfo.ServerVersion = serverVersion; + this.SessionInfo.ClientVersion = string.Format("SSH-2.0-Renci.SshClient.{0}", this.GetType().Assembly.GetName().Version); + + } + + public void Connect() + { + this.Write(Encoding.ASCII.GetBytes(string.Format("{0}\n", this.SessionInfo.ClientVersion))); + + // Register Transport response messages + Message.RegisterMessageType(MessageTypes.Disconnect); + Message.RegisterMessageType(MessageTypes.Ignore); + Message.RegisterMessageType(MessageTypes.Unimplemented); + Message.RegisterMessageType(MessageTypes.Debug); + Message.RegisterMessageType(MessageTypes.ServiceAcceptRequest); + Message.RegisterMessageType(MessageTypes.KeyExchangeInit); + Message.RegisterMessageType(MessageTypes.NewKeys); + + // Start incoming request listener + this._messageListener = new BackgroundWorker(); + this._messageListener.DoWork += MessageListener_DoWork; + this._messageListener.RunWorkerAsync(); + + // Wait for key exchange to be completed + this.SessionInfo.WaitHandle(this._keyExhangedFinishedWaitHandle); + + // If sessionId is not set then its not connected + if (this.SessionInfo.SessionId == null) + { + this.Disconnect(); + return; + } + + var authenticationService = new UserAuthenticationService(this.SessionInfo); + + authenticationService.AuthenticateUser(); + + if (!authenticationService.IsAuthenticated) + { + throw new InvalidOperationException(string.Format("User cannot be authenticated. Reason: {0}.", authenticationService.ErrorMessage)); + } + } + + public void Disconnect() + { + // TODO: Change message to something more appropriate + this.Disconnect(DisconnectReasonCodes.ByApplication, "Connection terminated by the client."); + + this.DisconnectCleanup(); + } + + protected abstract void SendMessage(Message message); + + protected abstract Message ReceiveMessage(); + + protected virtual void HandleMessage(T message) where T : Message + { + // Do nothing as message could be proccessed by other module + } + + #region Handle transport messages + + protected virtual void HandleMessage(DisconnectMessage message) + { + this.DisconnectCleanup(); + } + + protected virtual void HandleMessage(IgnoreMessage message) + { + throw new NotImplementedException(); + } + + protected virtual void HandleMessage(UnimplementedMessage message) + { + throw new NotImplementedException(); + } + + protected virtual void HandleMessage(DebugMessage message) + { + throw new NotImplementedException(); + } + + protected virtual void HandleMessage(ServiceAcceptMessage message) + { + } + + protected virtual void HandleMessage(ServiceRequestMessage message) + { + throw new NotImplementedException(); + } + + protected virtual void HandleMessage(KeyExchangeInitMessage message) + { + this._keyExhangedFinishedWaitHandle.Reset(); + + if (message.FirstKexPacketFollows) + { + // TODO: Expect guess packet + throw new NotImplementedException("Guess packets are not supported."); + } + + // Create key exchange algorithm + this._keyExhcange = KeyExchange.Create(message, this.SessionInfo); + + this._keyExhcange.Failed += delegate(object sender, KeyExchangeFailedEventArgs e) + { + this.Disconnect(DisconnectReasonCodes.KeyExchangeFailed, e.Message); + throw new InvalidOperationException(e.Message); + }; + + this._keyExhcange.Start(message); + } + + protected virtual void HandleMessage(NewKeysMessage message) + { + this._keyExhcange.Finish(); + + // Update encryption and decryption algorithm + this.ServerMac = this._keyExhcange.ServerMac; + this.ClientMac = this._keyExhcange.ClientMac; + this.Encryption = this._keyExhcange.Encryption; + this.Decryption = this._keyExhcange.Decryption; + this.ServerDecompression = this._keyExhcange.ServerDecompression; + this.ClientCompression = this._keyExhcange.ClientCompression; + + this._keyExhangedFinishedWaitHandle.Set(); + } + + #endregion + + #region Handle connection messages + + protected virtual void HandleMessage(GlobalRequestMessage message) + { + // TODO: Add implemention for this message + } + + protected virtual void HandleMessage(RequestSuccessMessage message) + { + // TODO: Add implemention for this message + } + + protected virtual void HandleMessage(RequestFailureMessage message) + { + // TODO: Add implemention for this message + } + + #endregion + + protected void Disconnect(DisconnectReasonCodes reasonCode, string message) + { + if (this._socket != null && this._socket.Connected) + { + this.SendMessage(new DisconnectMessage + { + ReasonCode = reasonCode, + Description = message, + }); + + this.DisconnectCleanup(); + } + } + + protected byte[] Read(int length) + { + var buffer = new byte[length]; + + SocketError socketErrorCode; + + this._socket.Receive(buffer, 0, length, SocketFlags.None, out socketErrorCode); + + // Check for socket errors + if (socketErrorCode != SocketError.Success) + { + throw new SocketException((int)socketErrorCode); + } + else if (!this._socket.Connected) + { + // If socket was closed throw an exception + throw new SocketException(995); // WSA_OPERATION_ABORTED + } + else + return buffer; + } + + protected void Write(byte[] data) + { + // TODO: Make sure socket is connected + SocketError socketErrorCode; + + this._socket.Send(data, 0, data.Length, SocketFlags.None, out socketErrorCode); + + // Check for socket errors + if (socketErrorCode != SocketError.Success) + { + throw new SocketException((int)socketErrorCode); + } + else if (!this._socket.Connected) + { + // If socket was closed throw an exception + throw new SocketException(995); // WSA_OPERATION_ABORTED + } + } + + protected void RequestNewKeys() + { + // TODO: Create method to issue new keys when required + //this._keyExhcange.Start(); + } + + private void DisconnectCleanup() + { + // Stop running listener thread + + // Close all open channels if any + foreach (var channelId in this._openChannels.Values) + { + this.SendMessage(new ChannelCloseMessage + { + ChannelNumber = channelId, + }); + } + + // Close socket connection if still open + if (this._socket.Connected) + { + this._socket.Disconnect(false); + } + + // Let session know that it was disconected + this.SessionInfo.Disconnect(); + } + + private void MessageListener_DoWork(object sender, DoWorkEventArgs e) + { + while (this._socket.Connected) + { + try + { + dynamic message = this.ReceiveMessage(); + + if (message == null) + { + throw new NullReferenceException("The 'message' variable cannot be null"); + } + + // Handle session messages first + this.HandleMessage(message); + + // Raise an event that message received + this.SessionInfo.RaiseMessageReceived(this, new MessageReceivedEventArgs(message)); + } + catch (Exception exp) + { + // In case of error issue disconntect command + this.Disconnect(DisconnectReasonCodes.ByApplication, exp.ToString()); + + // TODO: Set exp to some excpetion that can be thrown later by the main thread. + + break; + } + } + } + + private void HandleMessage(ChannelOpenConfirmationMessage message) + { + // Keep track of open channels + this._openChannels.Add(message.ChannelNumber, message.ServerChannelNumber); + Debug.WriteLine("Open channel " + message.ServerChannelNumber); + } + + private void HandleMessage(ChannelCloseMessage message) + { + // Keep track of open channels + Debug.WriteLine("Close channel " + this._openChannels[message.ChannelNumber]); + this._openChannels.Remove(message.ChannelNumber); + } + + #region IDisposable Members + + private bool disposed = false; + + public void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + private 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 resources. + if (disposing) + { + // Dispose managed resources. + if (this._socket != null) + { + this.Disconnect(); + this._socket.Dispose(); + } + } + + // Note disposing has been done. + disposed = true; + } + } + + ~Session() + { + // Do not re-create Dispose clean-up code here. + // Calling Dispose(false) is optimal in terms of + // readability and maintainability. + Dispose(false); + } + + #endregion + } +} diff --git a/Renci.SshClient/Renci.SshClient/SessionInfo.cs b/Renci.SshClient/Renci.SshClient/SessionInfo.cs new file mode 100644 index 00000000..279ec2d3 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/SessionInfo.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Renci.SshClient.Common; +using Renci.SshClient.Messages; + +namespace Renci.SshClient +{ + public delegate void SendMessage(Message message); + + internal class SessionInfo + { + private SendMessage _sendMessage; + + private EventWaitHandle _disconnectWaitHandle = new AutoResetEvent(false); + + private int _waitTimeout; + + public ConnectionInfo ConnectionInfo { get; private set; } + + public IEnumerable SessionId { get; set; } + + public string ServerVersion { get; set; } + + public string ClientVersion { get; set; } + + public SessionInfo(SendMessage sendMessage, ConnectionInfo connectionInfo, int waitTimeout) + { + this._sendMessage = sendMessage; + this._waitTimeout = waitTimeout; + this.ConnectionInfo = connectionInfo; + } + + public event EventHandler MessageReceived; + + public void RaiseMessageReceived(object sender, MessageReceivedEventArgs args) + { + if (this.MessageReceived != null) + { + this.MessageReceived(sender, args); + } + } + + public void SendMessage(Message message) + { + this._sendMessage(message); + } + + public void Disconnect() + { + this._disconnectWaitHandle.Set(); + } + + public void WaitHandle(EventWaitHandle waitHandle) + { + var waitHandles = new EventWaitHandle[] + { + this._disconnectWaitHandle, + waitHandle, + }; + var index = EventWaitHandle.WaitAny(waitHandles); + + //var index = EventWaitHandle.WaitAny(waitHandles, this._waitTimeout); + + //if (index > waitHandles.Length) + //{ + // // TODO: Issue timeout disconnect message if approapriate + // throw new TimeoutException(); + //} + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/SessionSSHv2.cs b/Renci.SshClient/Renci.SshClient/SessionSSHv2.cs new file mode 100644 index 00000000..0c84e58e --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/SessionSSHv2.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Security.Cryptography; +using Renci.SshClient.Messages; + +namespace Renci.SshClient +{ + internal class SessionSSHv2 : Session + { + private static object _readLock = new object(); + private static object _writeLock = new object(); + + private static RNGCryptoServiceProvider _randomizer = new System.Security.Cryptography.RNGCryptoServiceProvider(); + + private UInt32 _outboundPacketSequence = 0; + private UInt32 _inboundPacketSequence = 0; + + internal SessionSSHv2(ConnectionInfo connectionInfo, Socket socket, string serverVersion) + : base(connectionInfo, socket, serverVersion) + { + } + + protected override void SendMessage(Message message) + { + if (!this.IsConnected) + return; + + // TODO: Refactor so we lock only _outboundPacketSequence and _inboundPacketSequence relevant operations + + // Messages can be sent by different thread so we need to synchronize it + lock (_writeLock) + { + var paddingMultiplier = this.Encryption == null ? (byte)8 : (byte)this.Encryption.OutputBlockSize; // Should be recalculate base on cipher min lenght if sipher specified + + // TODO: Maximum uncomporessed payload 32768 + // TOOO: If compression specified then compress only payload + + var messageData = message.GetBytes(); + + var packetLength = messageData.Count() + 4 + 1; // add length bytes and padding byte + byte paddingLength = (byte)((-packetLength) & (paddingMultiplier - 1)); + if (paddingLength < paddingMultiplier) + { + paddingLength += paddingMultiplier; + } + + // Build Packet data + var packetData = new List(); + + // Add packet padding length + packetData.Add(paddingLength); + + // Add packet payload + packetData.AddRange(messageData); + + // Add random padding + var paddingBytes = new byte[paddingLength]; + _randomizer.GetBytes(paddingBytes); + packetData.AddRange(paddingBytes); + + // Insert packet length + packetData.InsertRange(0, BitConverter.GetBytes((uint)(packetData.Count())).Reverse()); + + // Calculate packet hash + var hashData = new List(); + hashData.AddRange(BitConverter.GetBytes((this._outboundPacketSequence)).Reverse()); + hashData.AddRange(packetData); + + // Encrypt packet data + var encryptedData = packetData.ToList(); + if (this.Encryption != null) + { + encryptedData = new List(this.Encrypt(packetData)); + } + + // Add message authentication code (MAC) + if (this.ClientMac != null) + { + var hash = this.ClientMac.ComputeHash(hashData.ToArray()); + + encryptedData.AddRange(hash); + } + + if (encryptedData.Count > Session.MAXIMUM_PACKET_SIZE) + { + throw new InvalidOperationException("Packet is too big. Maximum packet size is 35000 bytes."); + } + + this.Write(encryptedData.ToArray()); + + this._outboundPacketSequence++; + } + } + + protected override Message ReceiveMessage() + { + if (!this.IsConnected) + return null; + + // No lock needed since all messages read by only one thread + + List decryptedData; + + var blockSize = this.Decryption == null ? (byte)8 : (byte)this.Decryption.InputBlockSize; + + // Read packet lenght first + var data = new List(this.Read(blockSize)); + + if (this.Decryption == null) + { + decryptedData = data.ToList(); + } + else + { + decryptedData = new List(this.Decrypt(data)); + } + + var packetLength = BitConverter.ToUInt32(decryptedData.Take(4).Reverse().ToArray(), 0); + + // Test packet minimum and maximum boundaries + if (packetLength < Math.Max((byte)16, blockSize) - 4 || packetLength > Session.MAXIMUM_PACKET_SIZE - 4) + throw new InvalidOperationException(string.Format("Bad packet length {0}", packetLength)); + + // Read rest of the packet data + int bytesToRead = (int)(packetLength - (blockSize - 4)); + + while (bytesToRead > 0) + { + data = new List(this.Read(blockSize)); + + if (this.Decryption == null) + { + decryptedData.AddRange(data); + } + else + { + decryptedData.AddRange(this.Decrypt(data)); + } + bytesToRead -= blockSize; + } + + // Validate message against MAC + if (this.ServerMac != null) + { + var serverHash = this.Read(this.ServerMac.HashSize / 8); + + var clientHashData = new List(); + clientHashData.AddRange(BitConverter.GetBytes(this._inboundPacketSequence).Reverse()); + clientHashData.AddRange(decryptedData); + + // Calculate packet hash + var clientHash = this.ServerMac.ComputeHash(clientHashData.ToArray()); + + if (!serverHash.IsEqualTo(clientHash)) + { + throw new InvalidOperationException("MAC error"); + } + } + + // TODO: Issue new keys after x number of packets + this._inboundPacketSequence++; + + var paddingLength = decryptedData[4]; + + return Message.Load(decryptedData.Skip(5).Take((int)(packetLength - paddingLength - 1))); + } + + private bool ValidateHash(List decryptedData, byte[] serverHash, uint packetSequence) + { + var clientHashData = new List(); + clientHashData.AddRange(BitConverter.GetBytes(packetSequence).Reverse()); + clientHashData.AddRange(decryptedData); + + var clientHash = this.ServerMac.ComputeHash(clientHashData.ToArray()); + if (!serverHash.IsEqualTo(clientHash)) + { + return false; + } + return true; + } + + private IEnumerable Encrypt(List data) + { + var temp = new byte[data.Count]; + this.Encryption.TransformBlock(data.ToArray(), 0, data.Count, temp, 0); + return temp; + } + + private IEnumerable Decrypt(List data) + { + var temp = new byte[data.Count]; + this.Decryption.TransformBlock(data.ToArray(), 0, data.Count, temp, 0); + return temp; + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Settings.cs b/Renci.SshClient/Renci.SshClient/Settings.cs new file mode 100644 index 00000000..4304a6c1 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Settings.cs @@ -0,0 +1,53 @@ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using Renci.SshClient.Algorithms; +namespace Renci.SshClient +{ + internal static class Settings + { + public static IDictionary> KeyExchangeAlgorithms { get; private set; } + + public static IDictionary> Encryptions { get; private set; } + + public static IDictionary, HMAC>> HmacAlgorithms { get; private set; } + + //public static IDictionary, IEnumerable, IEnumerable, bool>> HostKeyAlgorithms { get; private set; } + public static IDictionary, Signature>> HostKeyAlgorithms { get; private set; } + + + static Settings() + { + Settings.KeyExchangeAlgorithms = new Dictionary>() + { + {"diffie-hellman-group1-sha1", (a) => { return new KeyExchangeDiffieHellman(a);}} + //"diffie-hellman-group-exchange-sha1" + + }; + + Settings.Encryptions = new Dictionary>() + { + {"3des-cbc", () => { return new System.Security.Cryptography.TripleDESCryptoServiceProvider();}}, + //{"aes128-cbc", () => { return new System.Security.Cryptography.RijndaelManaged();}}, // TODO: Need to be tested, currently not working + }; + + + Settings.HmacAlgorithms = new Dictionary, HMAC>>() + { + {"hmac-md5", (key) => { return new System.Security.Cryptography.HMACMD5(key.Take(16).ToArray());}}, + {"hmac-sha1", (key) => { return new System.Security.Cryptography.HMACSHA1(key.Take(20).ToArray());}}, + }; + + //Settings.HostKeyAlgorithms = new Dictionary, IEnumerable, IEnumerable, bool>>() + Settings.HostKeyAlgorithms = new Dictionary, Signature>>() + { + //{"ssh-rsa", (hash, signature, hostKeyData) => { var s = new SignatureRsa(hostKeyData); return s.Validate(hash, signature);}}, + //{"ssh-dsa", (hash, signature, hostKeyData) => { var s = new SignatureDss(hostKeyData); return s.Validate(hash, signature);}}, // TODO: Need to be tested + {"ssh-rsa", (hostKeyData) => { return new SignatureRsa(hostKeyData);}}, + {"ssh-dsa", (hostKeyData) => { return new SignatureDss(hostKeyData);;}}, // TODO: Need to be tested + }; + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Sftp.cs b/Renci.SshClient/Renci.SshClient/Sftp.cs new file mode 100644 index 00000000..4e883947 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Sftp.cs @@ -0,0 +1,59 @@ + +using System.Collections.Generic; +using System.IO; +using Renci.SshClient.Channels; +using Renci.SshClient.Common; + +namespace Renci.SshClient +{ + public class Sftp + { + private Session _session; + + private ChannelSftp _channel; + + internal Sftp(Session session) + { + this._session = session; + + // TODO: Keep track of all open channels to disconnect them when connection is closed + this._channel = new ChannelSftp(this._session.SessionInfo); + } + + + public IEnumerable ListDirectory(string path) + { + return this._channel.ListDirectory(path); + } + + public void UploadFile(Stream source, string fileName) + { + this._channel.UploadFile(source, fileName); + } + + public void DownloadFile(string fileName, Stream destination) + { + this._channel.DownloadFile(fileName, destination); + } + + public void RemoveFile(string fileName) + { + this._channel.RemoveFile(fileName); + } + + public void RenameFile(string oldFileName, string newFileName) + { + this._channel.RenameFile(oldFileName, newFileName); + } + + public void CreateDirectory(string directoryName) + { + this._channel.CreateDirectory(directoryName); + } + + public void RemoveDirectory(string directoryName) + { + this._channel.RemoveDirectory(directoryName); + } + } +} diff --git a/Renci.SshClient/Renci.SshClient/Shell.cs b/Renci.SshClient/Renci.SshClient/Shell.cs new file mode 100644 index 00000000..5430fd48 --- /dev/null +++ b/Renci.SshClient/Renci.SshClient/Shell.cs @@ -0,0 +1,24 @@ + +using Renci.SshClient.Channels; +namespace Renci.SshClient +{ + public class Shell + { + private Session _session; + + internal Shell(Session session) + { + this._session = session; + } + + public string Execute(string command) + { + // TODO: Keep track of all open channels to cdisconnect them when connection is closed + var channel = new ChannelSession(this._session.SessionInfo); + + var result = channel.Execute(command); + + return result; + } + } +}