diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchange.cs b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchange.cs index 97ba8734..f193f693 100644 --- a/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchange.cs +++ b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchange.cs @@ -17,7 +17,7 @@ namespace Renci.SshClient.Algorithms /// /// The message. /// - internal static KeyExchange Create(KeyExchangeInitMessage message, SessionInfo sessionInfo) + internal static KeyExchange Create(KeyExchangeInitMessage message, Session session) { // TODO: Determine key exchange algorithm @@ -32,7 +32,7 @@ namespace Renci.SshClient.Algorithms throw new InvalidDataException("Failed to negotiate key exchange algorithm."); } - return Settings.KeyExchangeAlgorithms[keyExchangeAlgorithm](sessionInfo); + return Settings.KeyExchangeAlgorithms[keyExchangeAlgorithm](session); } /// @@ -87,7 +87,7 @@ namespace Renci.SshClient.Algorithms public bool IsSuccessed { get; protected set; } - protected SessionInfo SessionInfo { get; private set; } + protected Session Session { get; private set; } protected string ClientPayload { get; set; } @@ -107,9 +107,9 @@ namespace Renci.SshClient.Algorithms public event EventHandler Failed; - public KeyExchange(SessionInfo sessionInfo) + public KeyExchange(Session session) { - this.SessionInfo = sessionInfo; + this.Session = session; this.ServerDecompression = Compression.None; this.ClientCompression = Compression.None; } @@ -191,9 +191,9 @@ namespace Renci.SshClient.Algorithms public virtual void Finish() { // TODO: Validate that all required properties are set - if (this.SessionInfo.SessionId == null) + if (this.Session.SessionId == null) { - this.SessionInfo.SessionId = this.ExchangeHash; + this.Session.SessionId = this.ExchangeHash; } // Set encryption @@ -201,10 +201,10 @@ namespace Renci.SshClient.Algorithms 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)); + var clientValue = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'A', this.Session.SessionId)); // Calculate client to server encryption - var clientKey = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'C', this.SessionInfo.SessionId)); + var clientKey = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'C', this.Session.SessionId)); clientKey = this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, clientKey, clientAlgorithm.KeySize / 8); @@ -219,10 +219,10 @@ namespace Renci.SshClient.Algorithms 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)); + var serverValue = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'B', this.Session.SessionId)); // Calculate server to client encryption - var serverKey = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'D', this.SessionInfo.SessionId)); + var serverKey = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'D', this.Session.SessionId)); serverKey = this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, serverKey, serverAlgorithm.KeySize / 8); @@ -233,11 +233,11 @@ namespace Renci.SshClient.Algorithms } // Calculate client to server integrity - var MACc2s = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'E', this.SessionInfo.SessionId)); + var MACc2s = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'E', this.Session.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 MACs2c = this.Hash(this.GenerateSessionKey(this.SharedKey, this.ExchangeHash, 'F', this.Session.SessionId)); var serverMac = this._serverHmacAlgorithm(MACs2c); // TODO: Create compression and decompression objects if any @@ -314,15 +314,15 @@ namespace Renci.SshClient.Algorithms protected void SendMessage(Message message) { - this.SessionInfo.SendMessage(message); + this.Session.SendMessage(message); } private IEnumerable CalculateHash() { var hashData = new _ExchangeHashData { - ClientVersion = this.SessionInfo.ClientVersion, - ServerVersion = this.SessionInfo.ServerVersion, + ClientVersion = this.Session.ClientVersion, + ServerVersion = this.Session.ServerVersion, ClientPayload = this.ClientPayload, ServerPayload = this.ServerPayload, HostKey = this.HostKey, diff --git a/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeDiffieHellman.cs b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeDiffieHellman.cs index d1f00a35..34e82ce6 100644 --- a/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeDiffieHellman.cs +++ b/Renci.SshClient/Renci.SshClient/Algorithms/KeyExchangeDiffieHellman.cs @@ -43,8 +43,8 @@ namespace Renci.SshClient.Algorithms /// Initializes a new instance of the class. /// /// The session information. - internal KeyExchangeDiffieHellman(SessionInfo sessionInfo) - : base(sessionInfo) + internal KeyExchangeDiffieHellman(Session session) + : base(session) { } @@ -72,7 +72,7 @@ namespace Renci.SshClient.Algorithms E = this.ClientExchangeValue, }); - this.SessionInfo.MessageReceived += SessionInfo_MessageReceived; + this.Session.MessageReceived += SessionInfo_MessageReceived; } @@ -80,7 +80,7 @@ namespace Renci.SshClient.Algorithms { base.Finish(); - this.SessionInfo.MessageReceived -= SessionInfo_MessageReceived; + this.Session.MessageReceived -= SessionInfo_MessageReceived; } private void SessionInfo_MessageReceived(object sender, MessageReceivedEventArgs e) diff --git a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs index 8037f966..83d20a0f 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/Channel.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/Channel.cs @@ -38,9 +38,9 @@ namespace Renci.SshClient.Channels public bool IsOpen { get; protected set; } - protected SessionInfo SessionInfo { get; private set; } + protected Session Session { get; private set; } - public Channel(SessionInfo sessionInfo, uint windowSize, uint packetSize) + public Channel(Session session, uint windowSize, uint packetSize) { this._initialWindowSize = windowSize; this._maximumPacketSize = Math.Max(packetSize, 0x8000); // Ensure minimum maximum packet size of 0x8000 bytes @@ -61,21 +61,21 @@ namespace Renci.SshClient.Channels this.ClientChannelNumber = _channelCounter++; } - this.SessionInfo = sessionInfo; + this.Session = session; 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 Channel(Session session) + : this(session, 0x100000, 0x8000) { } public virtual void Open() { - this.SessionInfo.MessageReceived += SessionInfo_MessageReceived; + this.Session.MessageReceived += SessionInfo_MessageReceived; // Open session channel if (!this.IsOpen) @@ -88,7 +88,7 @@ namespace Renci.SshClient.Channels MaximumPacketSize = this.PacketSize, }); - this.SessionInfo.WaitHandle(this._channelOpenWaitHandle); + this.Session.WaitHandle(this._channelOpenWaitHandle); } } @@ -102,7 +102,7 @@ namespace Renci.SshClient.Channels }); // Wait for channel to be closed - this.SessionInfo.WaitHandle(this._channelClosedWaitHandle); + this.Session.WaitHandle(this._channelClosedWaitHandle); } this.CloseCleanup(); @@ -130,7 +130,7 @@ namespace Renci.SshClient.Channels protected void SendMessage(Message message) { - this.SessionInfo.SendMessage(message); + this.Session.SendMessage(message); } private void SessionInfo_MessageReceived(object sender, MessageReceivedEventArgs e) @@ -251,7 +251,7 @@ namespace Renci.SshClient.Channels this.IsOpen = false; - this.SessionInfo.MessageReceived -= SessionInfo_MessageReceived; + this.Session.MessageReceived -= SessionInfo_MessageReceived; } } } diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs index 90bb69dc..eb398234 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelSession.cs @@ -14,8 +14,8 @@ namespace Renci.SshClient.Channels get { return ChannelTypes.Session; } } - public ChannelSession(SessionInfo sessionInfo) - : base(sessionInfo, 0x100000, 0x1000) + public ChannelSession(Session session) + : base(session, 0x100000, 0x1000) { } @@ -33,7 +33,7 @@ namespace Renci.SshClient.Channels }); - this.SessionInfo.WaitHandle(this._channelEofWaitHandle); + this.Session.WaitHandle(this._channelEofWaitHandle); this.Close(); @@ -49,6 +49,13 @@ namespace Renci.SshClient.Channels this._channelEofWaitHandle.Set(); } + protected override void OnChannelClose() + { + base.OnChannelClose(); + + this._channelEofWaitHandle.Set(); + } + protected override void OnChannelData(string data) { base.OnChannelData(data); diff --git a/Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs b/Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs index f22e7f75..fab31ec6 100644 --- a/Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs +++ b/Renci.SshClient/Renci.SshClient/Channels/ChannelSftp.cs @@ -33,13 +33,13 @@ namespace Renci.SshClient.Channels get { return ChannelTypes.Session; } } - public ChannelSftp(SessionInfo sessionInfo, uint windowSize, uint packetSize) - : base(sessionInfo, windowSize, packetSize) + public ChannelSftp(Session session, uint windowSize, uint packetSize) + : base(session, windowSize, packetSize) { } - public ChannelSftp(SessionInfo sessionInfo) - : base(sessionInfo, 0x100000, 0x4000) + public ChannelSftp(Session session) + : base(session, 0x100000, 0x4000) { } @@ -56,7 +56,7 @@ namespace Renci.SshClient.Channels SubsystemName = "sftp", }); - this.SessionInfo.WaitHandle(this._channelRequestSuccessWaitHandle); + this.Session.WaitHandle(this._channelRequestSuccessWaitHandle); this.SendMessage(new InitMessage { @@ -263,7 +263,7 @@ namespace Renci.SshClient.Channels private SftpMessage ReceiveMessage() { - this.SessionInfo.WaitHandle(this._responseMessageReceivedWaitHandle); + this.Session.WaitHandle(this._responseMessageReceivedWaitHandle); var statusMessage = this._responseMessage as StatusMessage; diff --git a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj index 289b9f55..7987fdbf 100644 --- a/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj +++ b/Renci.SshClient/Renci.SshClient/Renci.SshClient.csproj @@ -172,7 +172,6 @@ - diff --git a/Renci.SshClient/Renci.SshClient/Services/ConnectionService.cs b/Renci.SshClient/Renci.SshClient/Services/ConnectionService.cs index 913d9e2b..8719d78e 100644 --- a/Renci.SshClient/Renci.SshClient/Services/ConnectionService.cs +++ b/Renci.SshClient/Renci.SshClient/Services/ConnectionService.cs @@ -10,20 +10,10 @@ namespace Renci.SshClient.Services get { throw new NotImplementedException(); } } - public ConnectionService(SessionInfo sessionInfo) - : base(sessionInfo) + public ConnectionService(Session session) + : base(session) { } - - //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 index 041771b9..23ebe0fb 100644 --- a/Renci.SshClient/Renci.SshClient/Services/Service.cs +++ b/Renci.SshClient/Renci.SshClient/Services/Service.cs @@ -6,16 +6,16 @@ namespace Renci.SshClient.Services { public abstract ServiceNames ServiceName { get; } - protected SessionInfo SessionInfo { get; private set; } + protected Session Session { get; private set; } - public Service(SessionInfo sessionInfo) + public Service(Session session) { - this.SessionInfo = sessionInfo; + this.Session = session; } protected void SendMessage(Message message) { - this.SessionInfo.SendMessage(message); + this.Session.SendMessage(message); } } diff --git a/Renci.SshClient/Renci.SshClient/Services/UserAuthentication.cs b/Renci.SshClient/Renci.SshClient/Services/UserAuthentication.cs index 0dd59246..cd9fec35 100644 --- a/Renci.SshClient/Renci.SshClient/Services/UserAuthentication.cs +++ b/Renci.SshClient/Renci.SshClient/Services/UserAuthentication.cs @@ -5,18 +5,18 @@ namespace Renci.SshClient.Services { public abstract string Name { get; } - protected SessionInfo SessionInfo { get; private set; } + protected Session Session { get; private set; } - public UserAuthentication(SessionInfo sessionInfo) + public UserAuthentication(Session session) { - this.SessionInfo = sessionInfo; + this.Session = session; } public abstract bool Start(); protected void SendMessage(Message message) { - this.SessionInfo.SendMessage(message); + this.Session.SendMessage(message); } } diff --git a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationHost.cs b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationHost.cs index 9f258c29..ab39a8e6 100644 --- a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationHost.cs +++ b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationHost.cs @@ -10,8 +10,8 @@ namespace Renci.SshClient.Services return "hostbased"; } } - public UserAuthenticationHost(SessionInfo sessionInfo) - : base(sessionInfo) + public UserAuthenticationHost(Session session) + : base(session) { } diff --git a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPassword.cs b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPassword.cs index e2f9130e..546806fb 100644 --- a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPassword.cs +++ b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPassword.cs @@ -13,8 +13,8 @@ namespace Renci.SshClient.Services } } - public UserAuthenticationPassword(SessionInfo sessionInfo) - : base(sessionInfo) + public UserAuthenticationPassword(Session session) + : base(session) { } @@ -24,13 +24,13 @@ namespace Renci.SshClient.Services // TODO: Handle all user authentication messages //Message.RegisterMessageType(MessageTypes.UserAuthenticationPasswordChangeRequired); - if (!string.IsNullOrEmpty(this.SessionInfo.ConnectionInfo.Password)) + if (!string.IsNullOrEmpty(this.Session.ConnectionInfo.Password)) { this.SendMessage(new PasswordRequestMessage { ServiceName = ServiceNames.Connection, - Username = this.SessionInfo.ConnectionInfo.Username, - Password = this.SessionInfo.ConnectionInfo.Password, + Username = this.Session.ConnectionInfo.Username, + Password = this.Session.ConnectionInfo.Password, }); return true; } diff --git a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPublicKey.cs b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPublicKey.cs index 1d63095b..f83b478e 100644 --- a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPublicKey.cs +++ b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationPublicKey.cs @@ -13,8 +13,8 @@ namespace Renci.SshClient.Services } } - public UserAuthenticationPublicKey(SessionInfo sessionInfo) - : base(sessionInfo) + public UserAuthenticationPublicKey(Session session) + : base(session) { } @@ -22,16 +22,16 @@ namespace Renci.SshClient.Services public override bool Start() { - if (this.SessionInfo.ConnectionInfo.KeyFile != null) + if (this.Session.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), + Username = this.Session.ConnectionInfo.Username, + PublicKeyAlgorithmName = this.Session.ConnectionInfo.KeyFile.AlgorithmName, + PublicKeyData = this.Session.ConnectionInfo.KeyFile.PublicKey, + Signature = this.Session.ConnectionInfo.KeyFile.GetSignature(this.Session.SessionId), }); return true; } diff --git a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationService.cs b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationService.cs index 788d8fb4..1cdcf68c 100644 --- a/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationService.cs +++ b/Renci.SshClient/Renci.SshClient/Services/UserAuthenticationService.cs @@ -27,8 +27,8 @@ namespace Renci.SshClient.Services public bool IsAuthenticated { get; private set; } - public UserAuthenticationService(SessionInfo sessionInfo) - : base(sessionInfo) + public UserAuthenticationService(Session session) + : base(session) { this.AuthenticationCompletedHandle = new AutoResetEvent(false); } @@ -41,7 +41,7 @@ namespace Renci.SshClient.Services Message.RegisterMessageType(MessageTypes.UserAuthenticationBanner); // Attach event handlers to handle messages - this.SessionInfo.MessageReceived += SessionInfo_MessageReceived; + this.Session.MessageReceived += SessionInfo_MessageReceived; // Request user authorization service this.SendMessage(new ServiceRequestMessage @@ -50,19 +50,19 @@ namespace Renci.SshClient.Services }); // Wait for service to be accepted - this.SessionInfo.WaitHandle(this._serviceAccepted); + this.Session.WaitHandle(this._serviceAccepted); // Start by quering supported authentication methods this.SendMessage(new RequestMessage { - Username = this.SessionInfo.ConnectionInfo.Username, + Username = this.Session.ConnectionInfo.Username, ServiceName = ServiceNames.Connection, }); // Wait for authentication to be completed - this.SessionInfo.WaitHandle(this._authenticationCompleted); + this.Session.WaitHandle(this._authenticationCompleted); - this.SessionInfo.MessageReceived -= SessionInfo_MessageReceived; + this.Session.MessageReceived -= SessionInfo_MessageReceived; Message.UnRegisterMessageType(MessageTypes.UserAuthenticationFailure); Message.UnRegisterMessageType(MessageTypes.UserAuthenticationSuccess); @@ -105,7 +105,7 @@ namespace Renci.SshClient.Services if (methodsToTry.Count() == 0) { - this.AuthenticationFailed(string.Format("User '{0}' cannot be authorized.", this.SessionInfo.ConnectionInfo.Username)); + this.AuthenticationFailed(string.Format("User '{0}' cannot be authorized.", this.Session.ConnectionInfo.Username)); return; } @@ -116,11 +116,11 @@ namespace Renci.SshClient.Services if (methodName == "publickey") { - userAuthentication = new UserAuthenticationPublicKey(this.SessionInfo); + userAuthentication = new UserAuthenticationPublicKey(this.Session); } else if (methodName == "password") { - userAuthentication = new UserAuthenticationPassword(this.SessionInfo); + userAuthentication = new UserAuthenticationPassword(this.Session); } this._executedMethods.Add(methodName); if (userAuthentication != null) diff --git a/Renci.SshClient/Renci.SshClient/Session.cs b/Renci.SshClient/Renci.SshClient/Session.cs index f86e4a71..586f893b 100644 --- a/Renci.SshClient/Renci.SshClient/Session.cs +++ b/Renci.SshClient/Renci.SshClient/Session.cs @@ -9,6 +9,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading; using Renci.SshClient.Algorithms; +using Renci.SshClient.Channels; using Renci.SshClient.Common; using Renci.SshClient.Messages; using Renci.SshClient.Messages.Connection; @@ -26,7 +27,8 @@ namespace Renci.SshClient 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 + //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1000 * 15); + //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000 * 5); // Get server version from the server, // ignore text lines which are sent before if any @@ -50,10 +52,10 @@ namespace Renci.SshClient return session; } - private ConnectionInfo _connectionInfo; - private Socket _socket; + private int _waitTimeout = 5 * 1000; // Set default receive timeout to 5 seconds + private KeyExchange _keyExhcange; private BackgroundWorker _messageListener; @@ -64,6 +66,14 @@ namespace Renci.SshClient private IDictionary _openChannels = new Dictionary(); + private EventWaitHandle _disconnectWaitHandle = new AutoResetEvent(false); + + private EventWaitHandle _exceptionWaitHandle = new AutoResetEvent(false); + + private Exception _exceptionToThrow; + + public event EventHandler MessageReceived; + protected HMAC ServerMac { get; private set; } protected HMAC ClientMac { get; private set; } @@ -76,7 +86,14 @@ namespace Renci.SshClient protected Compression ClientCompression { get; private set; } - internal SessionInfo SessionInfo { get; private set; } + // TODO: Consider refactor to make setter private + public IEnumerable SessionId { get; set; } + + public string ServerVersion { get; private set; } + + public string ClientVersion { get; private set; } + + public ConnectionInfo ConnectionInfo { get; private set; } public bool IsConnected { @@ -90,18 +107,32 @@ namespace Renci.SshClient protected Session(ConnectionInfo connectionInfo, Socket socket, string serverVersion) { - this._connectionInfo = connectionInfo; + this.ConnectionInfo = connectionInfo; this._socket = socket; + //this._socket.ReceiveTimeout = this._waitTimeout; - 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); + this.ServerVersion = serverVersion; + this.ClientVersion = string.Format("SSH-2.0-Renci.SshClient.{0}", this.GetType().Assembly.GetName().Version); + } + private static IDictionary> _channels = new Dictionary>() + { + {typeof(ChannelSession), (session) => { return new ChannelSession(session);}}, + {typeof(ChannelSftp), (session) => { return new ChannelSftp(session);}} + }; + + public T CreateChannel() where T : Channel + { + if (!this._socket.Connected) + { + throw new InvalidOperationException("Not connected"); + } + return _channels[typeof(T)](this) as T; } public void Connect() { - this.Write(Encoding.ASCII.GetBytes(string.Format("{0}\n", this.SessionInfo.ClientVersion))); + this.Write(Encoding.ASCII.GetBytes(string.Format("{0}\n", this.ClientVersion))); // Register Transport response messages Message.RegisterMessageType(MessageTypes.Disconnect); @@ -118,16 +149,16 @@ namespace Renci.SshClient this._messageListener.RunWorkerAsync(); // Wait for key exchange to be completed - this.SessionInfo.WaitHandle(this._keyExhangedFinishedWaitHandle); + this.WaitHandle(this._keyExhangedFinishedWaitHandle); // If sessionId is not set then its not connected - if (this.SessionInfo.SessionId == null) + if (this.SessionId == null) { this.Disconnect(); return; } - var authenticationService = new UserAuthenticationService(this.SessionInfo); + var authenticationService = new UserAuthenticationService(this); authenticationService.AuthenticateUser(); @@ -145,7 +176,33 @@ namespace Renci.SshClient this.DisconnectCleanup(); } - protected abstract void SendMessage(Message message); + internal abstract void SendMessage(Message message); + + internal void WaitHandle(EventWaitHandle waitHandle) + { + var waitHandles = new EventWaitHandle[] + { + this._disconnectWaitHandle, + this._exceptionWaitHandle, + waitHandle, + }; + + //EventWaitHandle.WaitAny(waitHandles); + + var index = EventWaitHandle.WaitAny(waitHandles, this._waitTimeout); + + if (this._exceptionToThrow != null) + { + var exception = this._exceptionToThrow; + this._exceptionToThrow = null; + throw exception; + } + else if (index > waitHandles.Length) + { + // TODO: Issue timeout disconnect message if approapriate + throw new TimeoutException(); + } + } protected abstract Message ReceiveMessage(); @@ -196,7 +253,7 @@ namespace Renci.SshClient } // Create key exchange algorithm - this._keyExhcange = KeyExchange.Create(message, this.SessionInfo); + this._keyExhcange = KeyExchange.Create(message, this); this._keyExhcange.Failed += delegate(object sender, KeyExchangeFailedEventArgs e) { @@ -261,20 +318,35 @@ namespace Renci.SshClient { var buffer = new byte[length]; - SocketError socketErrorCode; + SocketError socketErrorCode = SocketError.Success; - this._socket.Receive(buffer, 0, length, SocketFlags.None, out socketErrorCode); + int bytesRead = 0; - // Check for socket errors - if (socketErrorCode != SocketError.Success) + while (bytesRead == 0 && this._socket.Connected && socketErrorCode == SocketError.Success) { - throw new SocketException((int)socketErrorCode); + var asynchResult = this._socket.BeginReceive(buffer, 0, length, SocketFlags.None, null, null); + + while (!asynchResult.IsCompleted && this._socket.Connected) + { + asynchResult.AsyncWaitHandle.WaitOne(this._waitTimeout); + } + bytesRead = this._socket.EndReceive(asynchResult, out socketErrorCode); } - else if (!this._socket.Connected) + + // Check for errors + if (!this._socket.Connected) { // If socket was closed throw an exception throw new SocketException(995); // WSA_OPERATION_ABORTED } + if (bytesRead != length && socketErrorCode == SocketError.Success) + { + throw new InvalidDataException(string.Format("Data read {0}, expected {1}", bytesRead, length)); + } + else if (socketErrorCode != SocketError.Success) + { + throw new SocketException((int)socketErrorCode); + } else return buffer; } @@ -322,9 +394,6 @@ namespace Renci.SshClient { this._socket.Disconnect(false); } - - // Let session know that it was disconected - this.SessionInfo.Disconnect(); } private void MessageListener_DoWork(object sender, DoWorkEventArgs e) @@ -344,13 +413,19 @@ namespace Renci.SshClient this.HandleMessage(message); // Raise an event that message received - this.SessionInfo.RaiseMessageReceived(this, new MessageReceivedEventArgs(message)); + this.RaiseMessageReceived(this, new MessageReceivedEventArgs(message)); } catch (Exception exp) { + // TODO: This exception can be swolloed if it occures while running in the background, look for possible solutions + // In case of error issue disconntect command this.Disconnect(DisconnectReasonCodes.ByApplication, exp.ToString()); + // Set exception that need to be thrown by the main thread + this._exceptionToThrow = exp; + + this._exceptionWaitHandle.Set(); // TODO: Set exp to some excpetion that can be thrown later by the main thread. break; @@ -358,6 +433,14 @@ namespace Renci.SshClient } } + private void RaiseMessageReceived(object sender, MessageReceivedEventArgs args) + { + if (this.MessageReceived != null) + { + this.MessageReceived(sender, args); + } + } + private void HandleMessage(ChannelOpenConfirmationMessage message) { // Keep track of open channels diff --git a/Renci.SshClient/Renci.SshClient/SessionInfo.cs b/Renci.SshClient/Renci.SshClient/SessionInfo.cs deleted file mode 100644 index 279ec2d3..00000000 --- a/Renci.SshClient/Renci.SshClient/SessionInfo.cs +++ /dev/null @@ -1,72 +0,0 @@ -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 index 0c84e58e..ab4d8c4c 100644 --- a/Renci.SshClient/Renci.SshClient/SessionSSHv2.cs +++ b/Renci.SshClient/Renci.SshClient/SessionSSHv2.cs @@ -22,7 +22,7 @@ namespace Renci.SshClient { } - protected override void SendMessage(Message message) + internal override void SendMessage(Message message) { if (!this.IsConnected) return; diff --git a/Renci.SshClient/Renci.SshClient/Settings.cs b/Renci.SshClient/Renci.SshClient/Settings.cs index 4304a6c1..9b3828b9 100644 --- a/Renci.SshClient/Renci.SshClient/Settings.cs +++ b/Renci.SshClient/Renci.SshClient/Settings.cs @@ -8,7 +8,7 @@ namespace Renci.SshClient { internal static class Settings { - public static IDictionary> KeyExchangeAlgorithms { get; private set; } + public static IDictionary> KeyExchangeAlgorithms { get; private set; } public static IDictionary> Encryptions { get; private set; } @@ -20,7 +20,7 @@ namespace Renci.SshClient static Settings() { - Settings.KeyExchangeAlgorithms = new Dictionary>() + Settings.KeyExchangeAlgorithms = new Dictionary>() { {"diffie-hellman-group1-sha1", (a) => { return new KeyExchangeDiffieHellman(a);}} //"diffie-hellman-group-exchange-sha1" diff --git a/Renci.SshClient/Renci.SshClient/Sftp.cs b/Renci.SshClient/Renci.SshClient/Sftp.cs index 4e883947..b1827e7a 100644 --- a/Renci.SshClient/Renci.SshClient/Sftp.cs +++ b/Renci.SshClient/Renci.SshClient/Sftp.cs @@ -16,8 +16,8 @@ namespace Renci.SshClient { this._session = session; - // TODO: Keep track of all open channels to disconnect them when connection is closed - this._channel = new ChannelSftp(this._session.SessionInfo); + //this._channel = new ChannelSftp(this._session); + this._channel = this._session.CreateChannel(); } @@ -31,11 +31,22 @@ namespace Renci.SshClient this._channel.UploadFile(source, fileName); } + public void UploadFile(string source, string fileName) + { + this._channel.UploadFile(File.OpenRead(source), fileName); + } + public void DownloadFile(string fileName, Stream destination) { this._channel.DownloadFile(fileName, destination); } + public void DownloadFile(string fileName, string destination) + { + var file = File.Create(destination); + this._channel.DownloadFile(fileName, file); + } + public void RemoveFile(string fileName) { this._channel.RemoveFile(fileName); diff --git a/Renci.SshClient/Renci.SshClient/Shell.cs b/Renci.SshClient/Renci.SshClient/Shell.cs index 5430fd48..87eafa0b 100644 --- a/Renci.SshClient/Renci.SshClient/Shell.cs +++ b/Renci.SshClient/Renci.SshClient/Shell.cs @@ -13,8 +13,9 @@ namespace Renci.SshClient 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 channel = new ChannelSession(this._session); + + var channel = this._session.CreateChannel(); var result = channel.Execute(command);