You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2019/02/12 08:17:25 UTC
[mina-sshd] 01/02: [SSHD-892] Inform user about possible session
disconnect prior to disconnecting and allow intervention via
SessionDisconnectHandler
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 <lg...@apache.org>
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:dev@mina.apache.org">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:dev@mina.apache.org">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);