瀏覽代碼

Issue #1930: No connection possible with the same auth method requested multiple times. Improve exception message for authentication failures.

Gert Driesen 11 年之前
父節點
當前提交
397640ef10

+ 27 - 18
Renci.SshClient/Build/nuget/SSH.NET.nuspec

@@ -10,29 +10,38 @@
         <projectUrl>http://sshnet.codeplex.com/</projectUrl>
         <requireLicenseAcceptance>true</requireLicenseAcceptance>
         <description>This project was inspired by Sharp.SSH library which was ported from Java.  This library is a complete rewrite using .NET, without any third party dependencies and to utilize the parallelism as much as possible to allow best performance you can get.  SSH.NET supports .NET 3.5, .NET 4.0, Silverlight 4.0, Silverlight 5.0, Windows Phone 7.1 and Windows Phone 8.</description>
-        <releaseNotes>New Features:
+        <releaseNotes>
+          New Features:
 
-    * Added callbacks to UploadFile, DownloadFile and ListDirectory in SftpClient (issue #1324)
-    * Allow a given private key file to be used concurrently
+          * Added callbacks to UploadFile, DownloadFile and ListDirectory in SftpClient (issue #1324)
+          * Allow a given private key file to be used concurrently
+          * Performance improvements:
+          - optimization of payload size for both read and write operations (SftpClient only)
+          - increase window size from 1MB to 2MB
+          - increase buffer size from 16KB to 64KB for SftpClient
+          - take into account the maximum remote packet size of the channel for write operations
+          - increase maximum size of packets that we can receive from 32 KB to 64 KB
+          * Import exception message for authentication failures.
 
-Breaking changes:
+          Breaking changes:
 
-    * Assembly name is now Renci.SshNet for all supported frameworks
-    * The Renci.SshNet assemblies for .NET and Silverlight are now strong-named (issue #1802)
+          * Assembly name is now Renci.SshNet for all supported frameworks
+          * The Renci.SshNet assemblies for .NET and Silverlight are now strong-named (issue #1802)
 
-Fixes:
+          Fixes:
 
-    * Unobserved exception rethrown by finalizer thread (issue #1298)
-    * Client cipher is used to decrypt server messages (issue #1917)
-    * Connection dropped by server due to invalid DSA signature (issue #1918)
-    * Correct casing of Security/Cryptography/HMAC.cs to fix build on Linux (issue #1505)
-    * HTTP proxy hangs (issue #1890)
-    * Wrong parameters to SetSocketOption leads to SocketException under Mono (issue #1799)
-    * Incorrect check for timeout values (issue #1620)
-    * Wrong PKCS7 padding in DES algorithm (issue #1580)
-    * OverflowException on empty server response (issue #1562)
-    * Event handle leak (issue #1761)
-    * Write access required for private key file</releaseNotes>
+          * No connection possible with the same auth method requested multiple times (issue #1930)
+          * Unobserved exception rethrown by finalizer thread (issue #1298)
+          * Client cipher is used to decrypt server messages (issue #1917)
+          * Connection dropped by server due to invalid DSA signature (issue #1918)
+          * Correct casing of Security/Cryptography/HMAC.cs to fix build on Linux (issue #1505)
+          * HTTP proxy hangs (issue #1890)
+          * Wrong parameters to SetSocketOption leads to SocketException under Mono (issue #1799)
+          * Incorrect check for timeout values (issue #1620)
+          * Wrong PKCS7 padding in DES algorithm (issue #1580)
+          * OverflowException on empty server response (issue #1562)
+          * Event handle leak (issue #1761)
+          * Write access required for private key file</releaseNotes>
         <summary>A Secure Shell (SSH) library for .NET, optimized for parallelism.</summary>
         <copyright>2012-2014, RENCI</copyright>
         <language>en-US</language>

+ 69 - 35
Renci.SshClient/Renci.SshNet/ConnectionInfo.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Text;
 using Renci.SshNet.Security;
@@ -15,6 +16,10 @@ namespace Renci.SshNet
     /// <summary>
     /// Represents remote connection information class.
     /// </summary>
+    /// <remarks>
+    /// This class is NOT thread-safe. Do not use the same <see cref="ConnectionInfo"/> with multiple
+    /// client instances.
+    /// </remarks>
     public class ConnectionInfo
     {
         internal static int DEFAULT_PORT = 22;
@@ -385,64 +390,93 @@ namespace Renci.SshNet
         /// Authenticates the specified session.
         /// </summary>
         /// <param name="session">The session to be authenticated.</param>
-        /// <returns>true if authenticated; otherwise false.</returns>
         /// <exception cref="ArgumentNullException"><paramref name="session"/> is null.</exception>
-        /// <exception cref="SshAuthenticationException">No suitable authentication method found to complete authentication.</exception>
-        public bool Authenticate(Session session)
+        /// <exception cref="SshAuthenticationException">No suitable authentication method found to complete authentication, or permission denied.</exception>
+        public void Authenticate(Session session)
         {
-            var authenticated = AuthenticationResult.Failure;
-
             if (session == null)
                 throw new ArgumentNullException("session");
 
             session.RegisterMessage("SSH_MSG_USERAUTH_FAILURE");
             session.RegisterMessage("SSH_MSG_USERAUTH_SUCCESS");
             session.RegisterMessage("SSH_MSG_USERAUTH_BANNER");
-
             session.UserAuthenticationBannerReceived += Session_UserAuthenticationBannerReceived;
 
-            //  Try to authenticate against none
-            var noneAuthenticationMethod = new NoneAuthenticationMethod(this.Username);
+            try
+            {
+                // the exception to report an authentication failure with
+                SshAuthenticationException authenticationException = null;
 
-            authenticated = noneAuthenticationMethod.Authenticate(session);
+                // try to authenticate against none
+                var noneAuthenticationMethod = new NoneAuthenticationMethod(this.Username);
 
-            var allowedAuthentications = noneAuthenticationMethod.AllowedAuthentications;
+                var authenticated = noneAuthenticationMethod.Authenticate(session);
+                if (authenticated != AuthenticationResult.Success)
+                {
+                    var failedAuthenticationMethods = new List<AuthenticationMethod>();
+                    if (TryAuthenticate(session, noneAuthenticationMethod.AllowedAuthentications.ToList(), failedAuthenticationMethods, ref authenticationException))
+                    {
+                        authenticated = AuthenticationResult.Success;
+                    }
+                }
 
-            var triedAuthentications = new List<string>();
-            while (authenticated != AuthenticationResult.Success)
+                this.IsAuthenticated = authenticated == AuthenticationResult.Success;
+                if (!IsAuthenticated)
+                    throw authenticationException;
+            }
+            finally
             {
-                // Find first authentication method
-                var method = this.AuthenticationMethods.Where((a) => allowedAuthentications.Contains(a.Name) && !triedAuthentications.Contains(a.Name)).FirstOrDefault();
-                if (method == null)
-                    throw new SshAuthenticationException("No suitable authentication method found to complete authentication.");
+                session.UserAuthenticationBannerReceived -= Session_UserAuthenticationBannerReceived;
+                session.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE");
+                session.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS");
+                session.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER");
+            }
+        }
 
-                triedAuthentications.Add(method.Name);
+        private bool TryAuthenticate(Session session, ICollection<string> allowedAuthenticationMethods, IList<AuthenticationMethod> failedAuthenticationMethods, ref SshAuthenticationException authenticationException)
+        {
+            if (!allowedAuthenticationMethods.Any())
+            {
+                authenticationException = new SshAuthenticationException("No authentication methods defined on SSH server.");
+                return false;
+            }
 
-                authenticated = method.Authenticate(session);
+            // we want to try authentication methods in the order in which they were
+            //  passed in the ctor, not the order in which the SSH server returns
+            // the allowed authentication methods
+            var matchingAuthenticationMethods = AuthenticationMethods.Where(a => allowedAuthenticationMethods.Contains(a.Name)).ToList();
+            if (!matchingAuthenticationMethods.Any())
+            {
+                authenticationException = new SshAuthenticationException(string.Format("No suitable authentication method found to complete authentication ({0}).", string.Join(",", allowedAuthenticationMethods)));
+                return false;
+            }
 
-                if (authenticated == AuthenticationResult.PartialSuccess || (method.AllowedAuthentications != null && method.AllowedAuthentications.Count() < allowedAuthentications.Count()))
-                {
-                    // If further authentication is required then continue to try another method
-                    allowedAuthentications = method.AllowedAuthentications;
+            foreach (var authenticationMethod in matchingAuthenticationMethods)
+            {
+                if (failedAuthenticationMethods.Contains(authenticationMethod))
                     continue;
-                }
 
-                // If authentication Fail, and all the authentication have been tried.
-                if (authenticated == AuthenticationResult.Failure && (triedAuthentications.Count() == allowedAuthentications.Count()))
+                var authenticationResult = authenticationMethod.Authenticate(session);
+                switch (authenticationResult)
                 {
-                    break;
+                    case AuthenticationResult.PartialSuccess:
+                        if (TryAuthenticate(session, authenticationMethod.AllowedAuthentications.ToList(), failedAuthenticationMethods, ref authenticationException))
+                            authenticationResult = AuthenticationResult.Success;
+                        break;
+                    case AuthenticationResult.Failure:
+                        failedAuthenticationMethods.Add(authenticationMethod);
+                        authenticationException = new SshAuthenticationException(string.Format("Permission denied ({0}).", authenticationMethod.Name));
+                        break;
+                    case AuthenticationResult.Success:
+                        authenticationException = null;
+                        break;
                 }
-            }
-
-            session.UserAuthenticationBannerReceived -= Session_UserAuthenticationBannerReceived;
 
-            session.UnRegisterMessage("SSH_MSG_USERAUTH_FAILURE");
-            session.UnRegisterMessage("SSH_MSG_USERAUTH_SUCCESS");
-            session.UnRegisterMessage("SSH_MSG_USERAUTH_BANNER");
-
-            this.IsAuthenticated = authenticated == AuthenticationResult.Success;
+                if (authenticationResult == AuthenticationResult.Success)
+                    return true;
+            }
 
-            return authenticated == AuthenticationResult.Success;
+            return false;
         }
 
         private void Session_UserAuthenticationBannerReceived(object sender, MessageEventArgs<BannerMessage> e)

+ 2 - 7
Renci.SshClient/Renci.SshNet/Session.cs

@@ -235,7 +235,6 @@ namespace Renci.SshNet
         public byte[] SessionId { get; private set; }
 
         private Message _clientInitMessage;
-
         /// <summary>
         /// Gets the client init message.
         /// </summary>
@@ -588,12 +587,8 @@ namespace Renci.SshNet
                         throw new SshException("Username is not specified.");
                     }
 
-                    this._isAuthenticated = this.ConnectionInfo.Authenticate(this);
-
-                    if (!this._isAuthenticated)
-                    {
-                        throw new SshAuthenticationException("User cannot be authenticated.");
-                    }
+                    this.ConnectionInfo.Authenticate(this);
+                    this._isAuthenticated = true;
 
                     //  Register Connection messages
                     this.RegisterMessage("SSH_MSG_GLOBAL_REQUEST");