This is an automated email from the ASF dual-hosted git repository. lgoldstein pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit f7b04b7b9643ecf5e42fc66f53dcfb9fe5cf31ac Author: Lyor Goldstein <[email protected]> AuthorDate: Sun Feb 10 16:28:39 2019 +0200 [SSHD-892] Inform user about possible session disconnect prior to disconnecting and allow intervention via SessionDisconnectHandler --- CHANGES.md | 6 + docs/event-listeners.md | 9 + .../sshd/client/session/AbstractClientSession.java | 12 +- .../org/apache/sshd/common/FactoryManager.java | 2 + .../common/helpers/AbstractFactoryManager.java | 12 ++ .../org/apache/sshd/common/session/Session.java | 4 +- .../common/session/SessionDisconnectHandler.java | 132 ++++++++++++++ .../session/SessionDisconnectHandlerManager.java | 31 ++++ .../common/session/helpers/AbstractSession.java | 2 +- .../sshd/common/session/helpers/SessionHelper.java | 32 ++++ .../sshd/server/session/AbstractServerSession.java | 24 ++- .../sshd/server/session/ServerUserAuthService.java | 62 +++++-- .../session/helpers/AbstractSessionTest.java | 2 +- .../java/org/apache/sshd/server/ServerTest.java | 199 ++++++++++++++++----- 14 files changed, 457 insertions(+), 72 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 739b760..3ae98eb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,7 +7,13 @@ * The `ChannelSession` provides a mechanism for supporting non-standard extended data (a.k.a. STDERR data) in a similar manner as the "regular" data. Please read the relevant section in the main documentation page. +* The user can use a registered `SessionDisconnectHandler` in order be informed and also intervene in cases +where the code decides to disconnect the session due to various protocol or configuration parameters violations. + ## Behavioral changes and enhancements * [SSHD-882](https://issues.apache.org/jira/browse/SSHD-882) - Provide hooks to allow users to register a consumer for STDERR data sent via the `ChannelSession` - especially for the SFTP subsystem. + +* [SSHD=892](https://issues.apache.org/jira/browse/SSHD-882) - Inform user about possible session disconnect prior +to disconnecting and allow intervention via `SessionDisconnectHandler`. diff --git a/docs/event-listeners.md b/docs/event-listeners.md index 5436763..3b9beef 100644 --- a/docs/event-listeners.md +++ b/docs/event-listeners.md @@ -158,6 +158,15 @@ message received in the session as well. rather than being accumulated. However, one can use the `EventListenerUtils` and create a cumulative listener - see how `SessionListener` or `ChannelListener` proxies were implemented. +### `SessionDisconnectHandler` + +This handler can be registered in order to monitor session disconnect initiated by the internal code due to various +protocol requirements - e.g., unknown service, idle timeout, etc.. In many cases the implementor can intervene and +cancel the disconnect by handling the problem somehow and then signaling to the code that there is no longer any need +to disconnect. The handler can be registered globally at the `SshClient/Server` instance or per-session (via a `SessionListener`). + +**NOTE:** this handler is non-cumulative - i.e., setting it replaces any existing previous handler instance. + ### `SignalListener` Informs about signal requests as described in [RFC 4254 - section 6.9](https://tools.ietf.org/html/rfc4254#section-6.9), break requests diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java index 57485ad..aebdd2c 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java @@ -61,6 +61,7 @@ import org.apache.sshd.common.kex.KexState; import org.apache.sshd.common.keyprovider.KeyIdentityProvider; import org.apache.sshd.common.session.ConnectionService; import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.session.SessionDisconnectHandler; import org.apache.sshd.common.session.helpers.AbstractConnectionService; import org.apache.sshd.common.session.helpers.AbstractSession; import org.apache.sshd.common.util.GenericUtils; @@ -373,7 +374,16 @@ public abstract class AbstractClientSession extends AbstractSession implements C } @Override - public void startService(String name) throws Exception { + public void startService(String name, Buffer buffer) throws Exception { + SessionDisconnectHandler handler = getSessionDisconnectHandler(); + if ((handler != null) + && handler.handleUnsupportedServiceDisconnectReason(this, SshConstants.SSH_MSG_SERVICE_REQUEST, name, buffer)) { + if (log.isDebugEnabled()) { + log.debug("startService({}) ignore unknown service={} by handler", this, name); + } + return; + } + throw new IllegalStateException("Starting services is not supported on the client side: " + name); } diff --git a/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java index e9fa80e..d6bb13e 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java @@ -37,6 +37,7 @@ import org.apache.sshd.common.kex.KexFactoryManager; import org.apache.sshd.common.random.Random; import org.apache.sshd.common.session.ConnectionService; import org.apache.sshd.common.session.ReservedSessionMessagesManager; +import org.apache.sshd.common.session.SessionDisconnectHandlerManager; import org.apache.sshd.common.session.SessionListenerManager; import org.apache.sshd.common.session.UnknownChannelReferenceHandlerManager; import org.apache.sshd.server.forward.AgentForwardingFilter; @@ -54,6 +55,7 @@ public interface FactoryManager extends KexFactoryManager, SessionListenerManager, ReservedSessionMessagesManager, + SessionDisconnectHandlerManager, ChannelListenerManager, ChannelStreamPacketWriterResolverManager, UnknownChannelReferenceHandlerManager, diff --git a/sshd-core/src/main/java/org/apache/sshd/common/helpers/AbstractFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/common/helpers/AbstractFactoryManager.java index 16c563f..6549572 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/helpers/AbstractFactoryManager.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/helpers/AbstractFactoryManager.java @@ -56,6 +56,7 @@ import org.apache.sshd.common.kex.AbstractKexFactoryManager; import org.apache.sshd.common.random.Random; import org.apache.sshd.common.session.ConnectionService; import org.apache.sshd.common.session.ReservedSessionMessagesHandler; +import org.apache.sshd.common.session.SessionDisconnectHandler; import org.apache.sshd.common.session.SessionListener; import org.apache.sshd.common.session.UnknownChannelReferenceHandler; import org.apache.sshd.common.session.helpers.AbstractSessionFactory; @@ -94,6 +95,7 @@ public abstract class AbstractFactoryManager extends AbstractKexFactoryManager i private final Map<AttributeRepository.AttributeKey<?>, Object> attributes = new ConcurrentHashMap<>(); private PropertyResolver parentResolver = SyspropsMapWrapper.SYSPROPS_RESOLVER; private ReservedSessionMessagesHandler reservedSessionMessagesHandler; + private SessionDisconnectHandler sessionDisconnectHandler; private ChannelStreamPacketWriterResolver channelStreamPacketWriterResolver; private UnknownChannelReferenceHandler unknownChannelReferenceHandler; private IoServiceEventListener eventListener; @@ -310,6 +312,16 @@ public abstract class AbstractFactoryManager extends AbstractKexFactoryManager i } @Override + public SessionDisconnectHandler getSessionDisconnectHandler() { + return sessionDisconnectHandler; + } + + @Override + public void setSessionDisconnectHandler(SessionDisconnectHandler sessionDisconnectHandler) { + this.sessionDisconnectHandler = sessionDisconnectHandler; + } + + @Override public ChannelStreamPacketWriterResolver getChannelStreamPacketWriterResolver() { return channelStreamPacketWriterResolver; } diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java b/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java index 90f2885..3c7f26e 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java @@ -57,6 +57,7 @@ public interface Session KexFactoryManager, SessionListenerManager, ReservedSessionMessagesManager, + SessionDisconnectHandlerManager, ChannelListenerManager, ChannelStreamPacketWriterResolverManager, PortForwardingEventListenerManager, @@ -304,9 +305,10 @@ public interface Session /** * @param name Service name + * @param buffer Extra information provided when the service start request was received * @throws Exception If failed to start it */ - void startService(String name) throws Exception; + void startService(String name, Buffer buffer) throws Exception; @Override default <T> T resolveAttribute(AttributeRepository.AttributeKey<T> key) { diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/SessionDisconnectHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/session/SessionDisconnectHandler.java new file mode 100644 index 0000000..d1e7dab --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/SessionDisconnectHandler.java @@ -0,0 +1,132 @@ +/* + * 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.common.session; + +import java.io.IOException; + +import org.apache.sshd.common.Service; +import org.apache.sshd.common.session.Session.TimeoutStatus; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.server.ServerFactoryManager; + +/** + * Invoked when the internal session code decides it should disconnect + * a session due to some consideration. Usually allows intervening in + * the decision and even canceling it. + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface SessionDisconnectHandler { + /** + * Invoked when an internal timeout has expired (e.g., authentication, idle). + * + * @param session The session whose timeout has expired + * @param timeoutStatus The expired timeout + * @return {@code true} if expired timeout should be reset (i.e., no disconnect). + * If {@code false} then session will disconnect due to the expired timeout + * @throws IOException If failed to handle the event + */ + default boolean handleTimeoutDisconnectReason( + Session session, TimeoutStatus timeoutStatus) + throws IOException { + return false; + } + + /** + * Called to inform that the maximum allowed concurrent sessions threshold + * has been exceeded. <B>Note:</B> when handler is invoked the session is + * not yet marked as having been authenticated, nor has the authentication + * success been acknowledged to the peer. + * + * @param session The session that caused the excess + * @param service The {@link Service} instance through which the request was received + * @param username The authenticated username that is associated with the session. + * @param currentSessionCount The current sessions count + * @param maxSessionCount The maximum allowed sessions count + * @return {@code true} if accept the exceeding session regardless of the + * threshold. If {@code false} then exceeding session will be disconnected + * @throws IOException If failed to handle the event, <B>Note:</B> choosing + * to ignore this disconnect reason does not reset the current concurrent sessions + * counter in any way - i.e., the handler will be re-invoked every time the + * threshold is exceeded. + * @see ServerFactoryManager#MAX_CONCURRENT_SESSIONS + */ + default boolean handleSessionsCountDisconnectReason( + Session session, Service service, String username, int currentSessionCount, int maxSessionCount) + throws IOException { + return false; + } + + /** + * Invoked when a request has been made related to an unknown SSH + * service as described in <A HREF="https://tools.ietf.org/html/rfc4253#section-10">RFC 4253 - section 10</A>. + * + * @param session The session through which the command was received + * @param cmd The service related command + * @param serviceName The service name + * @param buffer Any extra data received in the packet containing the request + * @return {@code true} if disregard the request (e.g., the handler handled it) + * @throws IOException If failed to handle the request + */ + default boolean handleUnsupportedServiceDisconnectReason( + Session session, int cmd, String serviceName, Buffer buffer) + throws IOException { + return false; + } + + /** + * Invoked if the number of authentication attempts exceeded the maximum allowed + * + * @param session The session being authenticated + * @param service The {@link Service} instance through which the request was received + * @param serviceName The authentication service name + * @param method The authentication method name + * @param user The authentication username + * @param currentAuthCount The authentication attempt count + * @param maxAuthCount The maximum allowed attempts + * @return {@code true} if OK to ignore the exceeded attempt count and + * allow more attempts. <B>Note:</B> choosing to ignore this disconnect reason does + * not reset the current count - i.e., it will be re-invoked on the next attempt. + * @throws IOException If failed to handle the event + */ + default boolean handleAuthCountDisconnectReason( + Session session, Service service, String serviceName, String method, String user, int currentAuthCount, int maxAuthCount) + throws IOException { + return false; + } + + /** + * Invoked if the authentication parameters changed in mid-authentication process. + * + * @param session The session being authenticated + * @param service The {@link Service} instance through which the request was received + * @param authUser The original username being authenticated + * @param username The requested username + * @param authService The original authentication service name + * @param serviceName The requested service name + * @return {@code true} if OK to ignore the change + * @throws IOException If failed to handle the event + */ + default boolean handleAuthParamsDisconnectReason( + Session session, Service service, String authUser, String username, String authService, String serviceName) + throws IOException { + return false; + } +} diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/SessionDisconnectHandlerManager.java b/sshd-core/src/main/java/org/apache/sshd/common/session/SessionDisconnectHandlerManager.java new file mode 100644 index 0000000..d75fee4 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/SessionDisconnectHandlerManager.java @@ -0,0 +1,31 @@ +/* + * 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.common.session; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface SessionDisconnectHandlerManager { + SessionDisconnectHandler getSessionDisconnectHandler(); + + void setSessionDisconnectHandler(SessionDisconnectHandler handler); +} diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java index e582039..b949d6a 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java @@ -483,7 +483,7 @@ public abstract class AbstractSession extends SessionHelper { validateKexState(SshConstants.SSH_MSG_SERVICE_REQUEST, KexState.DONE); try { - startService(serviceName); + startService(serviceName, buffer); } catch (Throwable e) { if (debugEnabled) { log.debug("handleServiceRequest({}) Service {} rejected: {} = {}", diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java index b2f16dd..a996d6d 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java @@ -61,6 +61,7 @@ import org.apache.sshd.common.session.ConnectionService; import org.apache.sshd.common.session.ReservedSessionMessagesHandler; import org.apache.sshd.common.session.Session; import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.session.SessionDisconnectHandler; import org.apache.sshd.common.session.SessionListener; import org.apache.sshd.common.session.UnknownChannelReferenceHandler; import org.apache.sshd.common.util.GenericUtils; @@ -99,6 +100,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements private final AtomicReference<TimeoutStatus> timeoutStatus = new AtomicReference<>(TimeoutStatus.NoTimeout); private ReservedSessionMessagesHandler reservedSessionMessagesHandler; + private SessionDisconnectHandler sessionDisconnectHandler; private UnknownChannelReferenceHandler unknownChannelReferenceHandler; private ChannelStreamPacketWriterResolver channelStreamPacketWriterResolver; @@ -238,6 +240,25 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements return; } + SessionDisconnectHandler handler = getSessionDisconnectHandler(); + if ((handler != null) && handler.handleTimeoutDisconnectReason(this, status)) { + if (log.isDebugEnabled()) { + log.debug("checkForTimeouts({}) cancel {} due to handler intervention", this, status); + } + + switch(status) { + case AuthTimeout: + resetAuthTimeout(); + break; + case IdleTimeout: + resetIdleTimeout(); + break; + + default: // ignored + } + return; + } + if (log.isDebugEnabled()) { log.debug("checkForTimeouts({}) disconnect - reason={}", this, status); } @@ -330,6 +351,17 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements reservedSessionMessagesHandler = handler; } + @Override + public SessionDisconnectHandler getSessionDisconnectHandler() { + return resolveEffectiveProvider(SessionDisconnectHandler.class, + sessionDisconnectHandler, getFactoryManager().getSessionDisconnectHandler()); + } + + @Override + public void setSessionDisconnectHandler(SessionDisconnectHandler sessionDisconnectHandler) { + this.sessionDisconnectHandler = sessionDisconnectHandler; + } + protected void handleIgnore(Buffer buffer) throws Exception { // malformed ignore message - ignore (even though we don't have to, but we can be tolerant in this case) if (!buffer.isValidMessageStructure(byte[].class)) { diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java index 2a5209c..523f16a 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java @@ -46,6 +46,7 @@ import org.apache.sshd.common.kex.KexState; import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.session.ConnectionService; import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.session.SessionDisconnectHandler; import org.apache.sshd.common.session.helpers.AbstractSession; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; @@ -227,7 +228,7 @@ public abstract class AbstractServerSession extends AbstractSession implements S } @Override - public void startService(String name) throws Exception { + public void startService(String name, Buffer buffer) throws Exception { FactoryManager factoryManager = getFactoryManager(); currentService = ServiceFactory.create( factoryManager.getServiceFactories(), @@ -240,6 +241,16 @@ public abstract class AbstractServerSession extends AbstractSession implements S * appropriate SSH_MSG_DISCONNECT message and MUST disconnect. */ if (currentService == null) { + SessionDisconnectHandler handler = getSessionDisconnectHandler(); + if ((handler != null) + && handler.handleUnsupportedServiceDisconnectReason( + this, SshConstants.SSH_MSG_SERVICE_REQUEST, name, buffer)) { + if (log.isDebugEnabled()) { + log.debug("startService({}) ignore unknown service={} by handler", this, name); + } + return; + } + throw new SshException(SshConstants.SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE, "Unknown service: " + name); } } @@ -247,6 +258,17 @@ public abstract class AbstractServerSession extends AbstractSession implements S @Override protected void handleServiceAccept(String serviceName, Buffer buffer) throws Exception { super.handleServiceAccept(serviceName, buffer); + + SessionDisconnectHandler handler = getSessionDisconnectHandler(); + if ((handler != null) + && handler.handleUnsupportedServiceDisconnectReason( + this, SshConstants.SSH_MSG_SERVICE_ACCEPT, serviceName, buffer)) { + if (log.isDebugEnabled()) { + log.debug("handleServiceAccept({}) ignore unknown service={} by handler", this, serviceName); + } + return; + } + // TODO: can services be initiated by the server-side ? disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Unsupported packet: SSH_MSG_SERVICE_ACCEPT for " + serviceName); } diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java index d6f6d51..1e45bbd 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerUserAuthService.java @@ -47,6 +47,7 @@ import org.apache.sshd.common.SshException; import org.apache.sshd.common.config.keys.KeyRandomArt; import org.apache.sshd.common.io.IoWriteFuture; import org.apache.sshd.common.session.Session; +import org.apache.sshd.common.session.SessionDisconnectHandler; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.NumberUtils; import org.apache.sshd.common.util.ValidateUtils; @@ -173,20 +174,35 @@ public class ServerUserAuthService extends AbstractCloseable implements Service, session, username, service, method); } - if (this.authUserName == null || this.authService == null) { + if ((this.authUserName == null) || (this.authService == null)) { this.authUserName = username; this.authService = service; } else if (this.authUserName.equals(username) && this.authService.equals(service)) { nbAuthRequests++; if (nbAuthRequests > maxAuthRequests) { - session.disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, - "Too many authentication failures: " + nbAuthRequests); - return; + SessionDisconnectHandler handler = session.getSessionDisconnectHandler(); + if ((handler == null) + || (!handler.handleAuthCountDisconnectReason( + session, this, service, method, username, nbAuthRequests, maxAuthRequests))) { + session.disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, + "Too many authentication failures: " + nbAuthRequests); + return; + } } } else { - session.disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, - "Change of username or service is not allowed (" + this.authUserName + ", " + this.authService + ") -> (" - + username + ", " + service + ")"); + SessionDisconnectHandler handler = session.getSessionDisconnectHandler(); + if ((handler != null) + && handler.handleAuthParamsDisconnectReason( + session, this, this.authUserName, username, this.authService, service)) { + if (debugEnabled) { + log.debug("process({}) ignore mismatched authentication parameters: user={}/{}, service={}/{}", + session, this.authUserName, username, this.authService, service); + } + } else { + session.disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, + "Change of username or service is not allowed (" + this.authUserName + ", " + this.authService + ") -> (" + + username + ", " + service + ")"); + } return; } @@ -287,7 +303,7 @@ public class ServerUserAuthService extends AbstractCloseable implements Service, boolean debugEnabled = log.isDebugEnabled(); if (debugEnabled) { log.debug("handleAuthenticationSuccess({}@{}) {}", - username, session, SshConstants.getCommandMessageName(cmd)); + username, session, SshConstants.getCommandMessageName(cmd)); } boolean success = false; @@ -303,9 +319,19 @@ public class ServerUserAuthService extends AbstractCloseable implements Service, if (maxSessionCount != null) { int currentSessionCount = session.getActiveSessionCountForUser(username); if (currentSessionCount >= maxSessionCount) { - session.disconnect(SshConstants.SSH2_DISCONNECT_TOO_MANY_CONNECTIONS, - "Too many concurrent connections (" + currentSessionCount + ") - max. allowed: " + maxSessionCount); - return; + SessionDisconnectHandler handler = session.getSessionDisconnectHandler(); + if ((handler == null) + || (!handler.handleSessionsCountDisconnectReason( + session, this, username, currentSessionCount, maxSessionCount))) { + session.disconnect(SshConstants.SSH2_DISCONNECT_TOO_MANY_CONNECTIONS, + "Too many concurrent connections (" + currentSessionCount + ") - max. allowed: " + maxSessionCount); + return; + } else { + if (debugEnabled) { + log.debug("handleAuthenticationSuccess({}@{}) ignore {}/{} sessions count due to handler intervention", + username, session, currentSessionCount, maxSessionCount); + } + } } } @@ -313,11 +339,11 @@ public class ServerUserAuthService extends AbstractCloseable implements Service, sendWelcomeBanner(session); } - buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_SUCCESS, Byte.SIZE); - session.writePacket(buffer); + Buffer response = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_SUCCESS, Byte.SIZE); + session.writePacket(response); session.setUsername(username); session.setAuthenticated(); - session.startService(authService); + session.startService(authService, buffer); session.resetIdleTimeout(); log.info("Session {}@{} authenticated", username, session.getIoSession().getRemoteAddress()); } else { @@ -330,10 +356,10 @@ public class ServerUserAuthService extends AbstractCloseable implements Service, log.debug("handleAuthenticationSuccess({}@{}) remaining methods={}", username, session, remaining); } - buffer = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_FAILURE, remaining.length() + Byte.SIZE); - buffer.putString(remaining); - buffer.putBoolean(true); // partial success ... - session.writePacket(buffer); + Buffer response = session.createBuffer(SshConstants.SSH_MSG_USERAUTH_FAILURE, remaining.length() + Byte.SIZE); + response.putString(remaining); + response.putBoolean(true); // partial success ... + session.writePacket(response); } try { diff --git a/sshd-core/src/test/java/org/apache/sshd/common/session/helpers/AbstractSessionTest.java b/sshd-core/src/test/java/org/apache/sshd/common/session/helpers/AbstractSessionTest.java index 770dca8..f06fa1e 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/session/helpers/AbstractSessionTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/session/helpers/AbstractSessionTest.java @@ -460,7 +460,7 @@ public class AbstractSessionTest extends BaseTestSupport { } @Override - public void startService(String name) throws Exception { + public void startService(String name, Buffer buffer) throws Exception { // ignored } diff --git a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java index c874714..fefd7b1 100644 --- a/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/server/ServerTest.java @@ -60,7 +60,9 @@ import org.apache.sshd.common.channel.WindowClosedException; import org.apache.sshd.common.io.IoSession; import org.apache.sshd.common.kex.KexProposalOption; import org.apache.sshd.common.session.Session; +import org.apache.sshd.common.session.Session.TimeoutStatus; import org.apache.sshd.common.session.SessionContext; +import org.apache.sshd.common.session.SessionDisconnectHandler; import org.apache.sshd.common.session.SessionListener; import org.apache.sshd.common.session.helpers.AbstractConnectionService; import org.apache.sshd.common.session.helpers.AbstractSession; @@ -199,24 +201,64 @@ public class ServerTest extends BaseTestSupport { final long testAuthTimeout = TimeUnit.SECONDS.toMillis(5L); PropertyResolverUtils.updateProperty(sshd, FactoryManager.AUTH_TIMEOUT, testAuthTimeout); + AtomicReference<TimeoutStatus> timeoutHolder = new AtomicReference<>(); + sshd.setSessionDisconnectHandler(new SessionDisconnectHandler() { + @Override + public boolean handleTimeoutDisconnectReason(Session session, TimeoutStatus timeoutStatus) + throws IOException { + outputDebugMessage("Session %s timeout reported: %s", session, timeoutStatus); + TimeoutStatus prev = timeoutHolder.getAndSet(timeoutStatus); + if (prev != null) { + throw new StreamCorruptedException("Multiple timeout disconnects: " + timeoutStatus + " / " + prev); + } + return false; + } + + @Override + public String toString() { + return SessionDisconnectHandler.class.getSimpleName() + "[" + getCurrentTestName() + "]"; + } + }); sshd.start(); client.start(); - try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()).verify(7L, TimeUnit.SECONDS).getSession()) { - Collection<ClientSession.ClientSessionEvent> res = s.waitFor(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED), 2L * testAuthTimeout); - assertTrue("Session should be closed: " + res, - res.containsAll(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH))); + Collection<ClientSession.ClientSessionEvent> res; + try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()) + .verify(7L, TimeUnit.SECONDS) + .getSession()) { + res = s.waitFor(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED), 2L * testAuthTimeout); } finally { client.stop(); } + + assertTrue("Session should be closed: " + res, + res.containsAll(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.WAIT_AUTH))); + assertSame("Mismatched timeout status reported", TimeoutStatus.AuthTimeout, timeoutHolder.getAndSet(null)); } @Test public void testIdleTimeout() throws Exception { - final CountDownLatch latch = new CountDownLatch(1); - TestEchoShell.latch = new CountDownLatch(1); final long testIdleTimeout = 2500L; PropertyResolverUtils.updateProperty(sshd, FactoryManager.IDLE_TIMEOUT, testIdleTimeout); + AtomicReference<TimeoutStatus> timeoutHolder = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + TestEchoShell.latch = new CountDownLatch(1); + sshd.setSessionDisconnectHandler(new SessionDisconnectHandler() { + @Override + public boolean handleTimeoutDisconnectReason(Session session, TimeoutStatus timeoutStatus) + throws IOException { + outputDebugMessage("Session %s timeout reported: %s", session, timeoutStatus); + TimeoutStatus prev = timeoutHolder.getAndSet(timeoutStatus); + if (prev != null) { + throw new StreamCorruptedException("Multiple timeout disconnects: " + timeoutStatus + " / " + prev); + } + return false; + } + @Override + public String toString() { + return SessionDisconnectHandler.class.getSimpleName() + "[" + getCurrentTestName() + "]"; + } + }); sshd.addSessionListener(new SessionListener() { @Override public void sessionCreated(Session session) { @@ -235,7 +277,8 @@ public class ServerTest extends BaseTestSupport { } @Override - public void sessionDisconnect(Session session, int reason, String msg, String language, boolean initiator) { + public void sessionDisconnect( + Session session, int reason, String msg, String language, boolean initiator) { outputDebugMessage("Session %s disconnected (sender=%s): reason=%d, message=%s", session, initiator, reason, msg); } @@ -245,6 +288,11 @@ public class ServerTest extends BaseTestSupport { outputDebugMessage("Session closed: %s", session); latch.countDown(); } + + @Override + public String toString() { + return SessionListener.class.getSimpleName() + "[" + getCurrentTestName() + "]"; + } }); TestChannelListener channelListener = new TestChannelListener(getCurrentTestName()); @@ -252,6 +300,7 @@ public class ServerTest extends BaseTestSupport { sshd.start(); client.start(); + Collection<ClientSession.ClientSessionEvent> res; try (ClientSession s = createTestClientSession(sshd); ChannelShell shell = s.createShellChannel(); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -263,16 +312,16 @@ public class ServerTest extends BaseTestSupport { assertTrue("No changes in activated channels", channelListener.waitForActiveChannelsChange(5L, TimeUnit.SECONDS)); assertTrue("No changes in open channels", channelListener.waitForOpenChannelsChange(5L, TimeUnit.SECONDS)); - Collection<ClientSession.ClientSessionEvent> res = - s.waitFor(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED), 2L * testIdleTimeout); - assertTrue("Session should be closed and authenticated: " + res, - res.containsAll(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.AUTHED))); + res = s.waitFor(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED), 2L * testIdleTimeout); } finally { client.stop(); } + assertTrue("Session should be closed and authenticated: " + res, + res.containsAll(EnumSet.of(ClientSession.ClientSessionEvent.CLOSED, ClientSession.ClientSessionEvent.AUTHED))); assertTrue("Session latch not signalled in time", latch.await(1L, TimeUnit.SECONDS)); assertTrue("Shell latch not signalled in time", TestEchoShell.latch.await(1L, TimeUnit.SECONDS)); + assertSame("Mismatched timeout status", TimeoutStatus.IdleTimeout, timeoutHolder.getAndSet(null)); } /* @@ -284,16 +333,14 @@ public class ServerTest extends BaseTestSupport { */ @Test public void testServerIdleTimeoutWithForce() throws Exception { - final CountDownLatch latch = new CountDownLatch(1); - - sshd.setCommandFactory(StreamCommand::new); - final long idleTimeoutValue = TimeUnit.SECONDS.toMillis(5L); PropertyResolverUtils.updateProperty(sshd, FactoryManager.IDLE_TIMEOUT, idleTimeoutValue); final long disconnectTimeoutValue = TimeUnit.SECONDS.toMillis(2L); PropertyResolverUtils.updateProperty(sshd, FactoryManager.DISCONNECT_TIMEOUT, disconnectTimeoutValue); + CountDownLatch latch = new CountDownLatch(1); + sshd.setCommandFactory(StreamCommand::new); sshd.addSessionListener(new SessionListener() { @Override public void sessionCreated(Session session) { @@ -315,6 +362,11 @@ public class ServerTest extends BaseTestSupport { outputDebugMessage("Session closed: %s", session); latch.countDown(); } + + @Override + public String toString() { + return SessionListener.class.getSimpleName() + "[" + getCurrentTestName() + "]"; + } }); TestChannelListener channelListener = new TestChannelListener(getCurrentTestName()); @@ -386,7 +438,7 @@ public class ServerTest extends BaseTestSupport { } }); - final Semaphore sigSem = new Semaphore(0, true); + Semaphore sigSem = new Semaphore(0, true); client.addSessionListener(new SessionListener() { @Override public void sessionCreated(Session session) { @@ -415,10 +467,17 @@ public class ServerTest extends BaseTestSupport { public void sessionClosed(Session session) { outputDebugMessage("Session closed: %s", session); } + + @Override + public String toString() { + return SessionListener.class.getSimpleName() + "[" + getCurrentTestName() + "]"; + } }); client.start(); - try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()).verify(7L, TimeUnit.SECONDS).getSession()) { + try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()) + .verify(7L, TimeUnit.SECONDS) + .getSession()) { assertTrue("Failed to receive signal on time", sigSem.tryAcquire(11L, TimeUnit.SECONDS)); } finally { client.stop(); @@ -458,7 +517,7 @@ public class ServerTest extends BaseTestSupport { } }); - final Semaphore sigSem = new Semaphore(0, true); + Semaphore sigSem = new Semaphore(0, true); client.addSessionListener(new SessionListener() { @Override public void sessionCreated(Session session) { @@ -479,11 +538,18 @@ public class ServerTest extends BaseTestSupport { public void sessionClosed(Session session) { sigSem.release(); } + + @Override + public String toString() { + return SessionListener.class.getSimpleName() + "[" + getCurrentTestName() + "]"; + } }); client.start(); try { - try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()).verify(7L, TimeUnit.SECONDS).getSession()) { + try (ClientSession s = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()) + .verify(7L, TimeUnit.SECONDS) + .getSession()) { assertTrue("Session closing not signalled on time", sigSem.tryAcquire(5L, TimeUnit.SECONDS)); for (boolean incoming : new boolean[]{true, false}) { assertNull("Unexpected compression information for incoming=" + incoming, s.getCompressionInformation(incoming)); @@ -497,7 +563,7 @@ public class ServerTest extends BaseTestSupport { @Test public void testKexCompletedEvent() throws Exception { - final AtomicInteger serverEventCount = new AtomicInteger(0); + AtomicInteger serverEventCount = new AtomicInteger(0); sshd.addSessionListener(new SessionListener() { @Override public void sessionEvent(Session session, Event event) { @@ -505,10 +571,15 @@ public class ServerTest extends BaseTestSupport { serverEventCount.incrementAndGet(); } } + + @Override + public String toString() { + return SessionListener.class.getSimpleName() + "[" + getCurrentTestName() + "]"; + } }); sshd.start(); - final AtomicInteger clientEventCount = new AtomicInteger(0); + AtomicInteger clientEventCount = new AtomicInteger(0); client.addSessionListener(new SessionListener() { @Override public void sessionEvent(Session session, Event event) { @@ -516,6 +587,11 @@ public class ServerTest extends BaseTestSupport { clientEventCount.incrementAndGet(); } } + + @Override + public String toString() { + return SessionListener.class.getSimpleName() + "[" + getCurrentTestName() + "]"; + } }); client.start(); @@ -530,7 +606,7 @@ public class ServerTest extends BaseTestSupport { @Test // see SSHD-645 public void testChannelStateChangeNotifications() throws Exception { - final Semaphore exitSignal = new Semaphore(0); + Semaphore exitSignal = new Semaphore(0); sshd.setCommandFactory(command -> new Command() { private ExitCallback cb; @@ -568,7 +644,7 @@ public class ServerTest extends BaseTestSupport { sshd.start(); client.start(); - final Collection<String> stateChangeHints = new CopyOnWriteArrayList<>(); + Collection<String> stateChangeHints = new CopyOnWriteArrayList<>(); try (ClientSession s = createTestClientSession(sshd); ChannelExec shell = s.createExecChannel(getCurrentTestName())) { shell.addChannelListener(new ChannelListener() { @@ -583,7 +659,7 @@ public class ServerTest extends BaseTestSupport { assertTrue("Timeout while wait for exit signal", exitSignal.tryAcquire(15L, TimeUnit.SECONDS)); Collection<ClientChannelEvent> result = - shell.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), TimeUnit.SECONDS.toMillis(13L)); + shell.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), TimeUnit.SECONDS.toMillis(13L)); assertFalse("Channel close timeout", result.contains(ClientChannelEvent.TIMEOUT)); Integer status = shell.getExitStatus(); @@ -599,7 +675,7 @@ public class ServerTest extends BaseTestSupport { @Test public void testEnvironmentVariablesPropagationToServer() throws Exception { - final AtomicReference<Environment> envHolder = new AtomicReference<>(null); + AtomicReference<Environment> envHolder = new AtomicReference<>(null); sshd.setCommandFactory(command -> new Command() { private ExitCallback cb; @@ -663,7 +739,7 @@ public class ServerTest extends BaseTestSupport { assertTrue("No changes in open channels", channelListener.waitForOpenChannelsChange(5L, TimeUnit.SECONDS)); Collection<ClientChannelEvent> result = - shell.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), TimeUnit.SECONDS.toMillis(17L)); + shell.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), TimeUnit.SECONDS.toMillis(17L)); assertFalse("Channel close timeout", result.contains(ClientChannelEvent.TIMEOUT)); Integer status = shell.getExitStatus(); @@ -691,17 +767,20 @@ public class ServerTest extends BaseTestSupport { public void testImmediateAuthFailureOpcode() throws Exception { sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE); sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE); - final AtomicInteger challengeCount = new AtomicInteger(0); + AtomicInteger challengeCount = new AtomicInteger(0); sshd.setKeyboardInteractiveAuthenticator(new KeyboardInteractiveAuthenticator() { @Override - public InteractiveChallenge generateChallenge(ServerSession session, String username, String lang, String subMethods) { + public InteractiveChallenge generateChallenge( + ServerSession session, String username, String lang, String subMethods) { challengeCount.incrementAndGet(); outputDebugMessage("generateChallenge(%s@%s) count=%s", username, session, challengeCount); return null; } @Override - public boolean authenticate(ServerSession session, String username, List<String> responses) throws Exception { + public boolean authenticate( + ServerSession session, String username, List<String> responses) + throws Exception { return false; } }); @@ -709,11 +788,13 @@ public class ServerTest extends BaseTestSupport { // order is important String authMethods = GenericUtils.join( - Arrays.asList(UserAuthMethodFactory.KB_INTERACTIVE, UserAuthMethodFactory.PUBLIC_KEY, UserAuthMethodFactory.PUBLIC_KEY), ','); + Arrays.asList(UserAuthMethodFactory.KB_INTERACTIVE, UserAuthMethodFactory.PUBLIC_KEY, UserAuthMethodFactory.PUBLIC_KEY), ','); PropertyResolverUtils.updateProperty(client, ClientAuthenticationManager.PREFERRED_AUTHS, authMethods); client.start(); - try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()).verify(7L, TimeUnit.SECONDS).getSession()) { + try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()) + .verify(7L, TimeUnit.SECONDS) + .getSession()) { AuthFuture auth = session.auth(); assertTrue("Failed to complete authentication on time", auth.await(17L, TimeUnit.SECONDS)); assertFalse("Unexpected authentication success", auth.isSuccess()); @@ -728,20 +809,24 @@ public class ServerTest extends BaseTestSupport { sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE); sshd.setPublickeyAuthenticator(RejectAllPublickeyAuthenticator.INSTANCE); - final InteractiveChallenge challenge = new InteractiveChallenge(); + InteractiveChallenge challenge = new InteractiveChallenge(); challenge.setInteractionInstruction(getCurrentTestName()); challenge.setInteractionName(getClass().getSimpleName()); challenge.setLanguageTag("il-heb"); challenge.addPrompt(new PromptEntry("Password", false)); - final AtomicInteger serverCount = new AtomicInteger(0); + + AtomicInteger serverCount = new AtomicInteger(0); sshd.setKeyboardInteractiveAuthenticator(new KeyboardInteractiveAuthenticator() { @Override - public InteractiveChallenge generateChallenge(ServerSession session, String username, String lang, String subMethods) { + public InteractiveChallenge generateChallenge( + ServerSession session, String username, String lang, String subMethods) { return challenge; } @Override - public boolean authenticate(ServerSession session, String username, List<String> responses) throws Exception { + public boolean authenticate( + ServerSession session, String username, List<String> responses) + throws Exception { outputDebugMessage("authenticate(%s@%s) count=%s", username, session, serverCount); serverCount.incrementAndGet(); return false; @@ -751,10 +836,10 @@ public class ServerTest extends BaseTestSupport { // order is important String authMethods = GenericUtils.join( - Arrays.asList(UserAuthMethodFactory.KB_INTERACTIVE, UserAuthMethodFactory.PUBLIC_KEY, UserAuthMethodFactory.PUBLIC_KEY), ','); + Arrays.asList(UserAuthMethodFactory.KB_INTERACTIVE, UserAuthMethodFactory.PUBLIC_KEY, UserAuthMethodFactory.PUBLIC_KEY), ','); PropertyResolverUtils.updateProperty(client, ClientAuthenticationManager.PREFERRED_AUTHS, authMethods); - final AtomicInteger clientCount = new AtomicInteger(0); - final String[] replies = {getCurrentTestName()}; + AtomicInteger clientCount = new AtomicInteger(0); + String[] replies = {getCurrentTestName()}; client.setUserInteraction(new UserInteraction() { @Override public boolean isInteractionAllowed(ClientSession session) { @@ -762,7 +847,8 @@ public class ServerTest extends BaseTestSupport { } @Override - public String[] interactive(ClientSession session, String name, String instruction, String lang, String[] prompt, boolean[] echo) { + public String[] interactive( + ClientSession session, String name, String instruction, String lang, String[] prompt, boolean[] echo) { clientCount.incrementAndGet(); return replies; } @@ -774,12 +860,16 @@ public class ServerTest extends BaseTestSupport { }); client.start(); - try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()).verify(7L, TimeUnit.SECONDS).getSession()) { + try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()) + .verify(7L, TimeUnit.SECONDS) + .getSession()) { AuthFuture auth = session.auth(); assertTrue("Failed to complete authentication on time", auth.await(17L, TimeUnit.SECONDS)); assertFalse("Unexpected authentication success", auth.isSuccess()); - assertEquals("Mismatched interactive server challenge calls", ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS, serverCount.get()); - assertEquals("Mismatched interactive client challenge calls", ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS, clientCount.get()); + assertEquals("Mismatched interactive server challenge calls", + ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS, serverCount.get()); + assertEquals("Mismatched interactive client challenge calls", + ClientAuthenticationManager.DEFAULT_PASSWORD_PROMPTS, clientCount.get()); } finally { client.stop(); } @@ -789,8 +879,7 @@ public class ServerTest extends BaseTestSupport { public void testIdentificationStringsOverrides() throws Exception { String clientIdent = getCurrentTestName() + "-client"; PropertyResolverUtils.updateProperty(client, ClientFactoryManager.CLIENT_IDENTIFICATION, clientIdent); - final String expClientIdent = SessionContext.DEFAULT_SSH_VERSION_PREFIX + clientIdent; - + String expClientIdent = SessionContext.DEFAULT_SSH_VERSION_PREFIX + clientIdent; String serverIdent = getCurrentTestName() + "-server"; PropertyResolverUtils.updateProperty(sshd, ServerFactoryManager.SERVER_IDENTIFICATION, serverIdent); String expServerIdent = SessionContext.DEFAULT_SSH_VERSION_PREFIX + serverIdent; @@ -817,6 +906,11 @@ public class ServerTest extends BaseTestSupport { public void sessionClosed(Session session) { // ignored } + + @Override + public String toString() { + return SessionListener.class.getSimpleName() + "[" + getCurrentTestName() + "]"; + } }; sshd.addSessionListener(listener); @@ -825,7 +919,9 @@ public class ServerTest extends BaseTestSupport { client.addSessionListener(listener); client.start(); - try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()).verify(7L, TimeUnit.SECONDS).getSession()) { + try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()) + .verify(7L, TimeUnit.SECONDS) + .getSession()) { session.addPasswordIdentity(getCurrentTestName()); session.auth().verify(9L, TimeUnit.SECONDS); assertEquals("Mismatched client identification", expClientIdent, session.getClientVersion()); @@ -842,7 +938,7 @@ public class ServerTest extends BaseTestSupport { getClass().getSimpleName(), getCurrentTestName()); PropertyResolverUtils.updateProperty(sshd, ServerFactoryManager.SERVER_EXTRA_IDENTIFICATION_LINES, - GenericUtils.join(expected, ServerFactoryManager.SERVER_EXTRA_IDENT_LINES_SEPARATOR)); + GenericUtils.join(expected, ServerFactoryManager.SERVER_EXTRA_IDENT_LINES_SEPARATOR)); sshd.start(); AtomicReference<List<String>> actualHolder = new AtomicReference<>(); @@ -860,7 +956,8 @@ public class ServerTest extends BaseTestSupport { } @Override - public String[] interactive(ClientSession session, String name, String instruction, String lang, String[] prompt, boolean[] echo) { + public String[] interactive( + ClientSession session, String name, String instruction, String lang, String[] prompt, boolean[] echo) { return null; } @@ -871,7 +968,9 @@ public class ServerTest extends BaseTestSupport { }); client.start(); - try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()).verify(7L, TimeUnit.SECONDS).getSession()) { + try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()) + .verify(7L, TimeUnit.SECONDS) + .getSession()) { session.addPasswordIdentity(getCurrentTestName()); session.auth().verify(9L, TimeUnit.SECONDS); assertTrue("No signal received in time", signal.tryAcquire(11L, TimeUnit.SECONDS)); @@ -885,7 +984,9 @@ public class ServerTest extends BaseTestSupport { } private ClientSession createTestClientSession(SshServer server) throws Exception { - ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, server.getPort()).verify(7L, TimeUnit.SECONDS).getSession(); + ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, server.getPort()) + .verify(7L, TimeUnit.SECONDS) + .getSession(); try { session.addPasswordIdentity(getCurrentTestName()); session.auth().verify(5L, TimeUnit.SECONDS);
