[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; + } + } +}
