[SSHD-234] Support partial authentications on the server side

Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/a49dee37
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/a49dee37
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/a49dee37

Branch: refs/heads/master
Commit: a49dee379f8dab72421595ca28ececfb0ce0b7b2
Parents: 1937ee8
Author: Guillaume Nodet <[email protected]>
Authored: Wed Jul 17 16:31:48 2013 +0200
Committer: Guillaume Nodet <[email protected]>
Committed: Wed Jul 17 16:31:48 2013 +0200

----------------------------------------------------------------------
 .../main/java/org/apache/sshd/SshServer.java    |   1 +
 .../sshd/client/session/ClientSessionImpl.java  |  12 +-
 .../apache/sshd/server/HandshakingUserAuth.java |  20 +-
 .../sshd/server/ServerFactoryManager.java       |  14 ++
 .../java/org/apache/sshd/server/UserAuth.java   |   2 +-
 .../apache/sshd/server/auth/UserAuthNone.java   |   2 +-
 .../sshd/server/auth/UserAuthPassword.java      |   7 +-
 .../sshd/server/auth/UserAuthPublicKey.java     |   6 +-
 .../sshd/server/auth/gss/UserAuthGSS.java       |  30 +--
 .../sshd/server/session/ServerSession.java      | 224 ++++++++++++-------
 .../org/apache/sshd/AuthenticationTest.java     | 161 +++++++++++++
 11 files changed, 353 insertions(+), 126 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a49dee37/sshd-core/src/main/java/org/apache/sshd/SshServer.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/SshServer.java 
b/sshd-core/src/main/java/org/apache/sshd/SshServer.java
index b5c95b6..a8b4919 100644
--- a/sshd-core/src/main/java/org/apache/sshd/SshServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/SshServer.java
@@ -551,6 +551,7 @@ public class SshServer extends AbstractFactoryManager 
implements ServerFactoryMa
                                                     
         SshServer sshd = SshServer.setUpDefaultServer();
         sshd.setPort(port);
+        sshd.getProperties().put(SshServer.WELCOME_BANNER, "Welcome to SSHD");
         if (SecurityUtils.isBouncyCastleRegistered()) {
             sshd.setKeyPairProvider(new 
PEMGeneratorHostKeyProvider("key.pem"));
         } else {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a49dee37/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java 
b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
index 37f7175..2949403 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
@@ -85,7 +85,7 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
         return kex;
     }
 
-    public AuthFuture authAgent(String username) throws IOException {
+    public AuthFuture authAgent(String user) throws IOException {
         synchronized (lock) {
             if (closeFuture.isClosed()) {
                 throw new IllegalStateException("Session is closed");
@@ -104,7 +104,7 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
                 throw new IllegalStateException("Session is closed");
             }
             authFuture = new DefaultAuthFuture(lock);
-            userAuth = new UserAuthAgent(this, username);
+            userAuth = new UserAuthAgent(this, user);
             setState(State.UserAuth);
 
             switch (userAuth.next(null)) {
@@ -126,7 +126,7 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
         }
     }
 
-    public AuthFuture authPassword(String username, String password) throws 
IOException {
+    public AuthFuture authPassword(String user, String password) throws 
IOException {
         synchronized (lock) {
             if (closeFuture.isClosed()) {
                 throw new IllegalStateException("Session is closed");
@@ -142,7 +142,7 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
                 throw new IllegalStateException("Session is closed");
             }
             authFuture = new DefaultAuthFuture(lock);
-            userAuth = new UserAuthPassword(this, username, password);
+            userAuth = new UserAuthPassword(this, user, password);
             setState(State.UserAuth);
 
             switch (userAuth.next(null)) {
@@ -164,7 +164,7 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
         }
     }
 
-    public AuthFuture authPublicKey(String username, KeyPair key) throws 
IOException {
+    public AuthFuture authPublicKey(String user, KeyPair key) throws 
IOException {
         synchronized (lock) {
             if (closeFuture.isClosed()) {
                 throw new IllegalStateException("Session is closed");
@@ -180,7 +180,7 @@ public class ClientSessionImpl extends AbstractSession 
implements ClientSession
                 throw new IllegalStateException("Session is closed");
             }
             authFuture = new DefaultAuthFuture(lock);
-            userAuth = new UserAuthPublicKey(this, username, key);
+            userAuth = new UserAuthPublicKey(this, user, key);
             setState(State.UserAuth);
 
             switch (userAuth.next(null)) {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a49dee37/sshd-core/src/main/java/org/apache/sshd/server/HandshakingUserAuth.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/HandshakingUserAuth.java 
b/sshd-core/src/main/java/org/apache/sshd/server/HandshakingUserAuth.java
index 625f7ba..d0bf5d9 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/HandshakingUserAuth.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/HandshakingUserAuth.java
@@ -31,34 +31,16 @@ import org.apache.sshd.server.session.ServerSession;
 public interface HandshakingUserAuth extends UserAuth {
 
     /**
-     * Set the service name from the original request.  This may be required 
for MIC verification later.
-     *
-     * @param service The service name
-     */
-
-    void setServiceName(String service);
-
-    /**
-     * Check whether a particular message is handled here.
-     *
-     * @param msg The message
-     * @return <code>true</code> if the message is handled
-     */
-
-    boolean handles(SshConstants.Message msg);
-
-    /**
      * Handle another step in the authentication process.
      *
      * @param session the current ssh session
-     * @param msg     The message type
      * @param buffer  the request buffer containing parameters specific to 
this request
      * @return <code>true</code> if the authentication succeeded, 
<code>false</code> if the authentication
      *         is not finished yet
      * @throws Exception if the authentication fails
      */
 
-    Boolean next(ServerSession session, SshConstants.Message msg, Buffer 
buffer) throws Exception;
+    Boolean next(ServerSession session, Buffer buffer) throws Exception;
 
     /**
      * Get a user name which has been derived from the handshaking process, or 
the intial name if

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a49dee37/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java 
b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
index 3f1e74b..f179a15 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
@@ -68,6 +68,20 @@ public interface ServerFactoryManager extends FactoryManager 
{
     public static final String WELCOME_BANNER = "welcome-banner";
 
     /**
+     * This key is used when configuring multi-step authentications.
+     * The value needs to be a blank separated list of comma separated list
+     * of authentication method names.
+     * For example, an argument of
+     * <code>publickey,password publickey,keyboard-interactive</code>
+     * would require the user to complete public key authentication,
+     * followed by either password or keyboard interactive authentication.
+     * Only methods that are next in one or more lists are offered at each
+     * stage, so for this example, it would not be possible to attempt
+     * password or keyboard-interactive authentication before public key.
+     */
+    public static final String AUTH_METHODS = "auth-methods";
+
+    /**
      * Retrieve the list of named factories for <code>UserAuth<code> objects.
      *
      * @return a list of named <code>UserAuth</code> factories, never 
<code>null</code>

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a49dee37/sshd-core/src/main/java/org/apache/sshd/server/UserAuth.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/UserAuth.java 
b/sshd-core/src/main/java/org/apache/sshd/server/UserAuth.java
index 8afb799..0bb9940 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/UserAuth.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/UserAuth.java
@@ -42,6 +42,6 @@ public interface UserAuth {
      *          is not finished yet
      * @throws Exception if the authentication fails
      */
-    Boolean auth(ServerSession session, String username, Buffer buffer) throws 
Exception;
+    Boolean auth(ServerSession session, String username, String service, 
Buffer buffer) throws Exception;
 
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a49dee37/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthNone.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthNone.java 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthNone.java
index 0297cef..3d96c9e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthNone.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthNone.java
@@ -39,7 +39,7 @@ public class UserAuthNone implements UserAuth {
         }
     }
 
-    public Boolean auth(ServerSession session, String username, Buffer buffer) 
{
+    public Boolean auth(ServerSession session, String username, String 
service, Buffer buffer) {
         return true;
     }
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a49dee37/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPassword.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPassword.java 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPassword.java
index 39c2f49..b2c60e2 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPassword.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPassword.java
@@ -19,6 +19,8 @@
 package org.apache.sshd.server.auth;
 
 import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.util.Buffer;
 import org.apache.sshd.server.PasswordAuthenticator;
 import org.apache.sshd.server.UserAuth;
@@ -40,7 +42,10 @@ public class UserAuthPassword implements UserAuth {
         }
     }
 
-    public Boolean auth(ServerSession session, String username, Buffer buffer) 
throws Exception {
+    public Boolean auth(ServerSession session, String username, String 
service, Buffer buffer) throws Exception {
+        if (!"ssh-connection".equals(service)) {
+            throw new 
SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Unsupported service 
'" + service + "'");
+        }
         boolean newPassword = buffer.getBoolean();
         if (newPassword) {
             throw new IllegalStateException("Password changes are not 
supported");

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a49dee37/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPublicKey.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPublicKey.java 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPublicKey.java
index cb7a907..f9349a7 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPublicKey.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/UserAuthPublicKey.java
@@ -25,6 +25,7 @@ import org.apache.sshd.common.KeyPairProvider;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.Signature;
 import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.util.Buffer;
 import org.apache.sshd.server.PublickeyAuthenticator;
 import org.apache.sshd.server.UserAuth;
@@ -46,7 +47,10 @@ public class UserAuthPublicKey implements UserAuth {
         }
     }
 
-    public Boolean auth(ServerSession session, String username, Buffer buffer) 
throws Exception {
+    public Boolean auth(ServerSession session, String username, String 
service, Buffer buffer) throws Exception {
+        if (!"ssh-connection".equals(service)) {
+            throw new 
SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Unsupported service 
'" + service + "'");
+        }
         boolean hasSig = buffer.getBoolean();
         String alg = buffer.getString();
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a49dee37/sshd-core/src/main/java/org/apache/sshd/server/auth/gss/UserAuthGSS.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/auth/gss/UserAuthGSS.java 
b/sshd-core/src/main/java/org/apache/sshd/server/auth/gss/UserAuthGSS.java
index afcca3a..02f2458 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/auth/gss/UserAuthGSS.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/auth/gss/UserAuthGSS.java
@@ -20,6 +20,7 @@ package org.apache.sshd.server.auth.gss;
 
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.util.Buffer;
 import org.apache.sshd.server.HandshakingUserAuth;
 import org.apache.sshd.server.UserAuth;
@@ -39,7 +40,6 @@ import org.slf4j.LoggerFactory;
  * <p/>
  * Several methods are available for overriding in specific circumstances.
  */
-
 public class UserAuthGSS implements HandshakingUserAuth {
 
     // Oids for the Kerberos 5 mechanism and principal
@@ -88,10 +88,11 @@ public class UserAuthGSS implements HandshakingUserAuth {
      * @throws Exception If something went wrong
      */
 
-    public Boolean auth(ServerSession sess, String user, Buffer buff) throws 
Exception {
+    public Boolean auth(ServerSession sess, String user, String service, 
Buffer buff) throws Exception {
         GSSAuthenticator auth = getAuthenticator(sess);
 
         this.user = user;
+        this.service = service;
 
         // Get mechanism count from buffer and look for Kerberos 5.
 
@@ -136,22 +137,11 @@ public class UserAuthGSS implements HandshakingUserAuth {
     }
 
     /**
-     * Set the service name from the original request.  This may be required 
for MIC verification later.
-     *
-     * @param service The service name
-     */
-
-    public void setServiceName(String service) {
-        this.service = service;
-    }
-
-    /**
      * Check whether a particular message is handled here.
      *
      * @param msg The message
      * @return <code>true</code> if the message is handled
      */
-
     public boolean handles(SshConstants.Message msg) {
         return msg == SshConstants.Message.SSH_MSG_USERAUTH_INFO_RESPONSE || 
msg == SshConstants.Message.SSH_MSG_USERAUTH_GSSAPI_MIC && ctxt.isEstablished();
     }
@@ -165,8 +155,13 @@ public class UserAuthGSS implements HandshakingUserAuth {
      *         is not finished yet
      * @throws Exception if the authentication fails
      */
+    public Boolean next(ServerSession session, Buffer buffer) throws Exception 
{
+
+        SshConstants.Message msg = buffer.getCommand();
+        if (!handles(msg)) {
+            throw new 
SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Packet not supported 
by user authentication method");
+        }
 
-    public Boolean next(ServerSession session, SshConstants.Message msg, 
Buffer buffer) throws Exception {
         GSSAuthenticator auth = getAuthenticator(session);
 
         log.debug("In krb5.next: msg = " + msg);
@@ -229,7 +224,7 @@ public class UserAuthGSS implements HandshakingUserAuth {
                 session.writePacket(b);
                 return null;
             } else {
-                return Boolean.valueOf(established);
+                return established;
             }
         }
     }
@@ -240,7 +235,6 @@ public class UserAuthGSS implements HandshakingUserAuth {
      *
      * @return The user name
      */
-
     public String getUserName() throws GSSException {
         return identity;
     }
@@ -248,7 +242,6 @@ public class UserAuthGSS implements HandshakingUserAuth {
     /**
      * Free any system resources used by the module.
      */
-
     public void destroy() {
         if (creds != null) {
             try {
@@ -275,7 +268,6 @@ public class UserAuthGSS implements HandshakingUserAuth {
      * @return The GSS authenticator
      * @throws Exception If no GSS authenticator is defined
      */
-
     private GSSAuthenticator getAuthenticator(ServerSession session) throws 
Exception {
         GSSAuthenticator ga = 
session.getServerFactoryManager().getGSSAuthenticator();
 
@@ -292,7 +284,6 @@ public class UserAuthGSS implements HandshakingUserAuth {
      * @param rep The string form
      * @return The Oid
      */
-
     private static Oid createOID(String rep) {
         try {
             return new Oid(rep);
@@ -305,7 +296,6 @@ public class UserAuthGSS implements HandshakingUserAuth {
     /**
      * Factory class.
      */
-
     public static class Factory implements NamedFactory<UserAuth> {
 
         /**

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a49dee37/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java 
b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
index 4582519..75f14d4 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java
@@ -23,6 +23,8 @@ import java.net.InetSocketAddress;
 import java.net.SocketAddress;
 import java.security.KeyPair;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Future;
@@ -30,6 +32,7 @@ import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.mina.core.session.IoSession;
+import org.apache.sshd.SshServer;
 import org.apache.sshd.agent.common.AgentForwardSupport;
 import org.apache.sshd.client.future.OpenFuture;
 import org.apache.sshd.common.*;
@@ -67,9 +70,12 @@ public class ServerSession extends AbstractSession {
     private final AgentForwardSupport agentForward;
     private final X11ForwardSupport x11Forward;
 
-    private HandshakingUserAuth currentAuth;
-
     private List<NamedFactory<UserAuth>> userAuthFactories;
+    private List<List<String>> authMethods;
+    private String authUserName;
+    private String authMethod;
+    private String authService;
+    private HandshakingUserAuth currentAuth;
 
     public ServerSession(FactoryManager server, IoSession ioSession) throws 
Exception {
         super(server, ioSession);
@@ -183,7 +189,7 @@ public class ServerSession extends AbstractSession {
                         }
                         break;
                     case UserAuth:
-                        if (cmd != 
SshConstants.Message.SSH_MSG_USERAUTH_REQUEST && (currentAuth == null || 
!currentAuth.handles(cmd))) {
+                        if (cmd != 
SshConstants.Message.SSH_MSG_USERAUTH_REQUEST && currentAuth == null) {
                             
disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Protocol error: 
expected packet " + SshConstants.Message.SSH_MSG_USERAUTH_REQUEST + ", got " + 
cmd);
                             return;
                         }
@@ -358,113 +364,177 @@ public class ServerSession extends AbstractSession {
             buffer.putString("ssh-userauth");
             writePacket(buffer);
             userAuthFactories = new 
ArrayList<NamedFactory<UserAuth>>(getServerFactoryManager().getUserAuthFactories());
+            // Get authentication methods
+            authMethods = new ArrayList<List<String>>();
+            String mths = 
getServerFactoryManager().getProperties().get(SshServer.AUTH_METHODS);
+            if (mths == null) {
+                for (NamedFactory<UserAuth> uaf : 
getServerFactoryManager().getUserAuthFactories()) {
+                    authMethods.add(new 
ArrayList<String>(Collections.singletonList(uaf.getName())));
+                }
+            } else {
+                for (String mthl : mths.split("\\s")) {
+                    authMethods.add(new 
ArrayList<String>(Arrays.asList(mthl.split(","))));
+                }
+            }
+            // Verify all required methods are supported
+            for (List<String> l : authMethods) {
+                for (String m : l) {
+                    if (NamedFactory.Utils.get(userAuthFactories, m) == null) {
+                        throw new SshException("Configured method is not 
supported: " + m);
+                    }
+                }
+            }
             log.debug("Authorized authentication methods: {}", 
NamedFactory.Utils.getNames(userAuthFactories));
             setState(State.UserAuth);
+
         } else {
-            if (nbAuthRequests++ > maxAuthRequests) {
-                throw new 
SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Too may 
authentication failures");
-            }
 
-            Boolean authed = null;
-            String username = null;
+            UserAuth auth = this.currentAuth;
+            Boolean authed = Boolean.FALSE;
 
             if (cmd == SshConstants.Message.SSH_MSG_USERAUTH_REQUEST) {
-                username = buffer.getString();
+                if (this.currentAuth != null) {
+                    this.currentAuth.destroy();
+                    this.currentAuth = null;
+                }
 
-                String svcName = buffer.getString();
+                String username = buffer.getString();
+                String service = buffer.getString();
                 String method = buffer.getString();
+                if (this.authUserName == null || this.authService == null) {
+                    this.authUserName = username;
+                    this.authService = service;
+                } else if (!this.authUserName.equals(username) || 
!this.authService.equals(service)) {
+                    disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR,
+                            "Change of username or service is not allowed (" + 
this.authUserName + ", " + this.authService + ") -> ("
+                                + username + ", " + service + ")");
+                    return;
+                }
+                this.authMethod = method;
+                if (nbAuthRequests++ > maxAuthRequests) {
+                    disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, 
"Too may authentication failures");
+                    return;
+                }
 
-                log.debug("Authenticating user '{}' with method '{}'", 
username, method);
+                log.debug("Authenticating user '{}' with service '{}' and 
method '{}'", new Object[] { username, service, method });
                 NamedFactory<UserAuth> factory = 
NamedFactory.Utils.get(userAuthFactories, method);
                 if (factory != null) {
-                    UserAuth auth = factory.create();
+                    auth = factory.create();
                     try {
-                        authed = auth.auth(this, username, buffer);
-                        if (authed == null) {
-                            // authentication is still ongoing
-                            log.debug("Authentication not finished");
-
-                            if (auth instanceof HandshakingUserAuth) {
-                                currentAuth = (HandshakingUserAuth) auth;
-
-                                // GSSAPI needs the user name and service to 
verify the MIC
-
-                                currentAuth.setServiceName(svcName);
-                            }
-                            return;
-                        } else {
-                            log.debug(authed ? "Authentication succeeded" : 
"Authentication failed");
-                        }
+                        authed = auth.auth(this, username, service, buffer);
                     } catch (Exception e) {
                         // Continue
-                        authed = false;
                         log.debug("Authentication failed: {}", e.getMessage());
                     }
-
-                } else {
-                    log.debug("Unsupported authentication method '{}'", 
method);
                 }
-            } else {
+            } else  {
+                if (this.currentAuth == null) {
+                    // This should not happen
+                    throw new IllegalStateException();
+                }
+                buffer.rpos(buffer.rpos() - 1);
                 try {
-                    authed = currentAuth.next(this, cmd, buffer);
-
-                    if (authed == null) {
-                        // authentication is still ongoing
-                        log.debug("Authentication still not finished");
-                        return;
-                    } else if (authed.booleanValue()) {
-                        username = currentAuth.getUserName();
-                    }
+                    authed = currentAuth.next(this, buffer);
                 } catch (Exception e) {
-                    // failed
-                    authed = false;
-                    log.debug("Authentication next failed: {}", 
e.getMessage());
+                    // Continue
+                    log.debug("Authentication failed: {}", e.getMessage());
                 }
             }
 
-            // No more handshakes now - clean up if necessary
+            if (authed == null) {
+                // authentication is still ongoing
+                log.debug("Authentication not finished");
+                if (auth instanceof HandshakingUserAuth) {
+                    currentAuth = (HandshakingUserAuth) auth;
+                }
+            } else if (authed) {
+                log.debug("Authentication succeeded");
+                if (currentAuth != null) {
+                    username = currentAuth.getUserName();
+                } else {
+                    username = this.authUserName;
+                }
 
-            if (currentAuth != null) {
-                currentAuth.destroy();
-                currentAuth = null;
-            }
+                boolean success = false;
+                for (List<String> l : authMethods) {
+                    if (!l.isEmpty() && l.get(0).equals(authMethod)) {
+                        l.remove(0);
+                        success |= l.isEmpty();
+                    }
+                }
+                if (success) {
+                    if (getFactoryManager().getProperties() != null) {
+                        String maxSessionCountAsString = 
getFactoryManager().getProperties().get(ServerFactoryManager.MAX_CONCURRENT_SESSIONS);
+                        if (maxSessionCountAsString != null) {
+                            int maxSessionCount = 
Integer.parseInt(maxSessionCountAsString);
+                            int currentSessionCount = 
getActiveSessionCountForUser(username);
+                            if (currentSessionCount >= maxSessionCount) {
+                                
disconnect(SshConstants.SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE, "Too many 
concurrent connections");
+                                return;
+                            }
+                        }
+                    }
 
-            if (authed != null && authed) {
+                    String welcomeBanner = 
factoryManager.getProperties().get(ServerFactoryManager.WELCOME_BANNER);
+                    if (welcomeBanner != null) {
+                        buffer = 
createBuffer(SshConstants.Message.SSH_MSG_USERAUTH_BANNER, 0);
+                        buffer.putString(welcomeBanner);
+                        buffer.putString("en");
+                        writePacket(buffer);
+                    }
 
-                if (getFactoryManager().getProperties() != null) {
-                    String maxSessionCountAsString = 
getFactoryManager().getProperties().get(ServerFactoryManager.MAX_CONCURRENT_SESSIONS);
-                    if (maxSessionCountAsString != null) {
-                        int maxSessionCount = 
Integer.parseInt(maxSessionCountAsString);
-                        int currentSessionCount = 
getActiveSessionCountForUser(username);
-                        if (currentSessionCount >= maxSessionCount) {
-                            
disconnect(SshConstants.SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE, "Too many 
concurrent connections");
-                            return;
+                    buffer = 
createBuffer(SshConstants.Message.SSH_MSG_USERAUTH_SUCCESS, 0);
+                    writePacket(buffer);
+                    setState(State.Running);
+                    this.authed = true;
+                    unscheduleAuthTimer();
+                    scheduleIdleTimer();
+                    log.info("Session {}@{} authenticated", getUsername(), 
getIoSession().getRemoteAddress());
+
+                } else {
+                    buffer = 
createBuffer(SshConstants.Message.SSH_MSG_USERAUTH_FAILURE, 0);
+                    StringBuilder sb = new StringBuilder();
+                    for (List<String> l : authMethods) {
+                        if (!l.isEmpty()) {
+                            if (sb.length() > 0) {
+                                sb.append(",");
+                            }
+                            sb.append(l.get(0));
                         }
                     }
-                }
-
-                String welcomeBanner = 
factoryManager.getProperties().get(ServerFactoryManager.WELCOME_BANNER);
-                if (welcomeBanner != null) {
-                    buffer = 
createBuffer(SshConstants.Message.SSH_MSG_USERAUTH_BANNER, 0);
-                    buffer.putString(welcomeBanner);
-                    buffer.putString("en");
+                    buffer.putString(sb.toString());
+                    buffer.putByte((byte) 1);
                     writePacket(buffer);
                 }
 
-                buffer = 
createBuffer(SshConstants.Message.SSH_MSG_USERAUTH_SUCCESS, 0);
-                writePacket(buffer);
-                setState(State.Running);
-                this.authed = true;
-                this.username = username;
-                unscheduleAuthTimer();
-                scheduleIdleTimer();
-                log.info("Session {}@{} authenticated", getUsername(), 
getIoSession().getRemoteAddress());
+                if (currentAuth != null) {
+                    currentAuth.destroy();
+                    currentAuth = null;
+                }
             } else {
+                log.debug("Authentication failed");
+
                 buffer = 
createBuffer(SshConstants.Message.SSH_MSG_USERAUTH_FAILURE, 0);
-                NamedFactory.Utils.remove(userAuthFactories, "none"); // 
'none' MUST NOT be listed
-                
buffer.putString(NamedFactory.Utils.getNames(userAuthFactories));
-                buffer.putByte((byte) 0);
+                StringBuilder sb = new StringBuilder();
+                for (List<String> l : authMethods) {
+                    if (!l.isEmpty()) {
+                        String m = l.get(0);
+                        if (!"none".equals(m)) {
+                            if (sb.length() > 0) {
+                                sb.append(",");
+                            }
+                            sb.append(l.get(0));
+                        }
+                    }
+                }
+                buffer.putString(sb.toString());
+                buffer.putByte((byte) 1);
                 writePacket(buffer);
+
+                if (currentAuth != null) {
+                    currentAuth.destroy();
+                    currentAuth = null;
+                }
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a49dee37/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java 
b/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
new file mode 100644
index 0000000..67b5626
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/AuthenticationTest.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd;
+
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.Logger;
+import com.jcraft.jsch.UserInfo;
+import org.apache.mina.core.filterchain.IoFilterChain;
+import org.apache.mina.core.service.IoProcessor;
+import org.apache.mina.core.service.IoService;
+import org.apache.mina.core.service.TransportMetadata;
+import org.apache.mina.core.session.AbstractIoSession;
+import org.apache.mina.core.session.IoSession;
+import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
+import org.apache.mina.transport.vmpipe.VmPipeAcceptor;
+import org.apache.mina.transport.vmpipe.VmPipeAddress;
+import org.apache.mina.transport.vmpipe.VmPipeConnector;
+import org.apache.sshd.client.UserInteraction;
+import org.apache.sshd.client.future.AuthFuture;
+import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.KeyPairProvider;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.Session;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.session.AbstractSession;
+import org.apache.sshd.common.util.Buffer;
+import org.apache.sshd.server.UserAuth;
+import org.apache.sshd.server.auth.UserAuthPassword;
+import org.apache.sshd.server.auth.UserAuthPublicKey;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.session.SessionFactory;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.BogusPublickeyAuthenticator;
+import org.apache.sshd.util.Utils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.ServerSocket;
+import java.net.SocketAddress;
+import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class AuthenticationTest {
+
+    private static final String WELCOME = "Welcome to SSHD";
+
+    private SshServer sshd;
+    private int port;
+
+    @Before
+    public void setUp() throws Exception {
+        ServerSocket s = new ServerSocket(0);
+        port = s.getLocalPort();
+        s.close();
+
+        sshd = SshServer.setUpDefaultServer();
+        sshd.setPort(port);
+        sshd.setKeyPairProvider(Utils.createTestHostKeyProvider());
+        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
+        sshd.setPublickeyAuthenticator(new BogusPublickeyAuthenticator());
+        sshd.setUserAuthFactories(Arrays.asList(
+                new UserAuthDummy.Factory(), new UserAuthPassword.Factory(), 
new UserAuthPublicKey.Factory()
+        ));
+        sshd.getProperties().put(SshServer.WELCOME_BANNER, WELCOME);
+        sshd.getProperties().put(SshServer.AUTH_METHODS, "publickey,password 
publickey,dummy");
+        sshd.setSessionFactory(new SessionFactory() {
+            @Override
+            protected AbstractSession doCreateSession(IoSession ioSession) 
throws Exception {
+                return new TestSession(server, ioSession);
+            }
+        });
+        sshd.start();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (sshd != null) {
+            sshd.stop(true);
+            Thread.sleep(50);
+        }
+    }
+
+    @Test
+    public void testChangeUser() throws Exception {
+        SshClient client = SshClient.setUpDefaultClient();
+        client.start();
+        ClientSession s = client.connect("localhost", 
port).await().getSession();
+        s.waitFor(ClientSession.CLOSED | ClientSession.WAIT_AUTH, 0);
+
+        assertFalse(s.authPassword("user1", 
"the-password").await().isSuccess());
+        assertFalse(s.authPassword("user2", 
"the-password").await().isSuccess());
+
+        assertEquals(ClientSession.CLOSED, s.waitFor(ClientSession.CLOSED, 
1000));
+    }
+
+    @Test
+    public void testAuth() throws Exception {
+        SshClient client = SshClient.setUpDefaultClient();
+        client.start();
+        ClientSession s = client.connect("localhost", 
port).await().getSession();
+        s.waitFor(ClientSession.CLOSED | ClientSession.WAIT_AUTH, 0);
+
+        assertFalse(s.authPassword("smx", "smx").await().isSuccess());
+
+        KeyPair pair = 
Utils.createTestHostKeyProvider().loadKey(KeyPairProvider.SSH_RSA);
+        assertFalse(s.authPublicKey("smx", pair).await().isSuccess());
+
+        assertTrue(s.authPassword("smx", "smx").await().isSuccess());
+
+        s.close(true);
+        client.stop();
+    }
+
+    public static class TestSession extends ServerSession {
+        public TestSession(FactoryManager server, IoSession ioSession) throws 
Exception {
+            super(server, ioSession);
+        }
+        public void setState(State state) {
+            super.setState(state);
+        }
+        public void handleMessage(Buffer buffer) throws Exception {
+            super.handleMessage(buffer);
+        }
+    }
+
+    public static class UserAuthDummy implements UserAuth {
+        public static class Factory implements NamedFactory<UserAuth> {
+            public String getName() {
+                return "dummy";
+            }
+            public UserAuth create() {
+                return new UserAuthDummy();
+            }
+        }
+        public Boolean auth(ServerSession session, String username, String 
service, Buffer buffer) throws Exception {
+            return Boolean.TRUE;
+        }
+    }
+}

Reply via email to