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 2021/01/09 05:41:46 UTC

[mina-sshd] branch master updated (f4aa59f -> cbd6198)

This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git.


    from f4aa59f  [SSHD-1114] Added HostBasedAuthenticationReporter#signalAuthenticationExhausted
     new daac252  [SSHD-1091] Renamed sshd-contrib top-level package in order to align naming convention.
     new 0b5544a  [SSHD-1097] Added more SessionListener callbacks related to the initial version and key exchange
     new 8e24d0d  [SSHD-1097] Added capability to send peer identification via ReservedSessionMessagesHandler
     new cbd6198  [SSHD-1097] Implemented endless tarpit example in sshd-contrib

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CHANGES.md                                         |   4 +
 README.md                                          |   3 +
 docs/event-listeners.md                            |   1 +
 docs/howto.md                                      |  25 ++
 .../org/apache/sshd/common/util/GenericUtils.java  |  10 +-
 .../org/apache/sshd/common/util/buffer/Buffer.java |  27 +-
 .../sshd/common/util/buffer/ByteArrayBuffer.java   |  18 +-
 .../InteractivePasswordIdentityProvider.java       |   3 +-
 .../throttle/ThrottlingChannelStreamWriter.java    |   4 +-
 .../common/compression/DeflatingInputStream.java   |   4 +-
 .../sshd/contrib/common/io/EndlessWriteFuture.java |  90 +++++++
 .../contrib/common/io/ImmediateWriteFuture.java    |  20 +-
 ...elIdTrackingUnknownChannelReferenceHandler.java |   3 +-
 .../common/signature/LegacyDSASigner.java          |   3 +-
 .../io/ExposedBufferByteArrayOutputStream.java     |   2 +-
 .../common/util/io/LineOutputStream.java           |   2 +-
 .../AndroidOpenSSLSecurityProviderRegistrar.java   |   2 +-
 .../scp/SimpleAccessControlScpEventListener.java   |   2 +-
 .../proxyprotocol/ProxyProtocolAcceptor.java       |   2 +-
 .../sftp/DetailedSftpErrorStatusDataHandler.java   |   2 +-
 .../sftp/SimpleAccessControlSftpEventListener.java |   2 +-
 .../InteractivePasswordIdentityProviderTest.java   |   3 +-
 .../ThrottlingChannelStreamWriterTest.java         |   3 +-
 .../common/signature/LegacyDSASignerTest.java      |   3 +-
 .../common/util/io/LineOutputStreamTest.java       |   3 +-
 .../SimpleAccessControlScpEventListenerTest.java   |   2 +-
 .../EndlessTarpitSenderSupportDevelopment.java     | 280 +++++++++++++++++++++
 .../SimpleAccessControlSftpEventListenerTest.java  |   2 +-
 .../{ => contrib}/common/signature/ssh-dss-1024    |   0
 .../{ => contrib}/common/signature/ssh-dss-2048    |   0
 .../sshd/client/session/AbstractClientSession.java |  18 +-
 .../sshd/client/session/ClientSessionImpl.java     |   2 +-
 .../common/kex/extension/KexExtensionHandler.java  |  92 +++----
 .../session/ReservedSessionMessagesHandler.java    |  47 +++-
 .../sshd/common/session/SessionListener.java       |  42 ++++
 .../sshd/common/session/SessionWorkBuffer.java     |   3 +-
 .../common/session/helpers/AbstractSession.java    |  79 +++---
 .../ReservedSessionMessagesHandlerAdapter.java     |  20 ++
 .../sshd/common/session/helpers/SessionHelper.java | 155 ++++++++++--
 .../sshd/server/session/AbstractServerSession.java |  14 +-
 .../sshd/server/session/ServerSessionImpl.java     |  11 +-
 .../kex/extension/KexExtensionHandlerTest.java     |   2 +-
 .../session/helpers/AbstractSessionTest.java       |  20 +-
 .../sshd/util/test/CoreTestSupportUtils.java       |  10 +-
 .../sshd/scp/client/SimpleScpClientImpl.java       |   3 +-
 45 files changed, 853 insertions(+), 190 deletions(-)
 create mode 100644 docs/howto.md
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/client/auth/password/InteractivePasswordIdentityProvider.java (97%)
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/common/channel/throttle/ThrottlingChannelStreamWriter.java (97%)
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/common/compression/DeflatingInputStream.java (97%)
 create mode 100644 sshd-contrib/src/main/java/org/apache/sshd/contrib/common/io/EndlessWriteFuture.java
 copy sshd-core/src/test/java/org/apache/sshd/deprecated/UserAuth.java => sshd-contrib/src/main/java/org/apache/sshd/contrib/common/io/ImmediateWriteFuture.java (74%)
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/common/session/helpers/ChannelIdTrackingUnknownChannelReferenceHandler.java (96%)
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/common/signature/LegacyDSASigner.java (98%)
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/common/util/io/ExposedBufferByteArrayOutputStream.java (96%)
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/common/util/io/LineOutputStream.java (98%)
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/common/util/security/androidopenssl/AndroidOpenSSLSecurityProviderRegistrar.java (96%)
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/server/scp/SimpleAccessControlScpEventListener.java (98%)
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/server/session/proxyprotocol/ProxyProtocolAcceptor.java (98%)
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/server/subsystem/sftp/DetailedSftpErrorStatusDataHandler.java (97%)
 rename sshd-contrib/src/main/java/org/apache/sshd/{ => contrib}/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java (99%)
 rename sshd-contrib/src/test/java/org/apache/sshd/{ => contrib}/client/auth/password/InteractivePasswordIdentityProviderTest.java (97%)
 rename sshd-contrib/src/test/java/org/apache/sshd/{ => contrib}/common/channel/throttle/ThrottlingChannelStreamWriterTest.java (97%)
 rename sshd-contrib/src/test/java/org/apache/sshd/{ => contrib}/common/signature/LegacyDSASignerTest.java (98%)
 rename sshd-contrib/src/test/java/org/apache/sshd/{ => contrib}/common/util/io/LineOutputStreamTest.java (97%)
 rename sshd-contrib/src/test/java/org/apache/sshd/{ => contrib}/server/scp/SimpleAccessControlScpEventListenerTest.java (99%)
 create mode 100644 sshd-contrib/src/test/java/org/apache/sshd/contrib/server/session/EndlessTarpitSenderSupportDevelopment.java
 rename sshd-contrib/src/test/java/org/apache/sshd/{ => contrib}/server/subsystem/sftp/SimpleAccessControlSftpEventListenerTest.java (99%)
 rename sshd-contrib/src/test/resources/org/apache/sshd/{ => contrib}/common/signature/ssh-dss-1024 (100%)
 rename sshd-contrib/src/test/resources/org/apache/sshd/{ => contrib}/common/signature/ssh-dss-2048 (100%)


[mina-sshd] 03/04: [SSHD-1097] Added capability to send peer identification via ReservedSessionMessagesHandler

Posted by lg...@apache.org.
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 8e24d0d82a21fc99c6ca9de21c4fc1c794ff9b4e
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Jan 8 07:37:00 2021 +0200

    [SSHD-1097] Added capability to send peer identification via ReservedSessionMessagesHandler
---
 CHANGES.md                                         |  1 +
 .../sshd/client/session/AbstractClientSession.java |  3 +--
 .../session/ReservedSessionMessagesHandler.java    | 28 +++++++++++++++++++-
 .../ReservedSessionMessagesHandlerAdapter.java     | 20 +++++++++++++++
 .../sshd/common/session/helpers/SessionHelper.java | 30 +++++++++++++++++-----
 .../sshd/server/session/AbstractServerSession.java |  8 +-----
 .../sshd/server/session/ServerSessionImpl.java     |  2 +-
 7 files changed, 75 insertions(+), 17 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 7d2b02c..5dada51 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -27,6 +27,7 @@
 * [SSHD-1085](https://issues.apache.org/jira/browse/SSHD-1085) Added more notifications related to channel state change for detecting channel closing or closed earlier.
 * [SSHD-1091](https://issues.apache.org/jira/browse/SSHD-1091) Renamed `sshd-contrib` top-level package in order to align naming convention.
 * [SSHD-1097](https://issues.apache.org/jira/browse/SSHD-1097) Added more `SessionListener` callbacks related to the initial version and key exchange
+* [SSHD-1097](https://issues.apache.org/jira/browse/SSHD-1097) Added more capability to send peer identification via `ReservedSessionMessagesHandler`
 * [SSHD-1109](https://issues.apache.org/jira/browse/SSHD-1109) Replace log4j with logback as the slf4j logger implementation for tests
 * [SSHD-1114](https://issues.apache.org/jira/browse/SSHD-1114) Added callbacks for client-side password authentication progress
 * [SSHD-1114](https://issues.apache.org/jira/browse/SSHD-1114) Added callbacks for client-side public key authentication progress
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 ce8e529..3bab23a 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
@@ -356,8 +356,7 @@ public abstract class AbstractClientSession extends AbstractSession implements C
         clientVersion = resolveIdentificationString(CoreModuleProperties.CLIENT_IDENTIFICATION.getName());
         // Note: we intentionally use an unmodifiable list in order to enforce the fact that client cannot send header lines
         signalSendIdentification(clientVersion, Collections.emptyList());
-
-        return sendIdentification(clientVersion);
+        return sendIdentification(clientVersion, Collections.emptyList());
     }
 
     @Override
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/ReservedSessionMessagesHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/session/ReservedSessionMessagesHandler.java
index e73e5d9..ecc49f1 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/ReservedSessionMessagesHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/ReservedSessionMessagesHandler.java
@@ -19,17 +19,43 @@
 
 package org.apache.sshd.common.session;
 
+import java.util.List;
+
+import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.util.SshdEventListener;
 import org.apache.sshd.common.util.buffer.Buffer;
 
 /**
  * Provides a way to listen and handle the {@code SSH_MSG_IGNORE} and {@code SSH_MSG_DEBUG} messages that are received
- * by a session, as well as proprietary and/or extension messages.
+ * by a session, as well as proprietary and/or extension messages and behavior.
  *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public interface ReservedSessionMessagesHandler extends SshdEventListener {
     /**
+     * Send the initial version exchange identification in and independent manner
+     *
+     * @param  session    The {@code Session} through which the version is exchange is being managed
+     * @param  version    The version line that was resolved - <B>Note:</B> since this string is part of the KEX and is
+     *                    <U>cached</U> in the calling session, any changes to it require updating the session's cached
+     *                    value.
+     * @param  extraLines Extra lines to be sent - valid only for server sessions. <B>Note:/B> the handler may modify
+     *                    these lines and return {@code null} thus signaling the session to proceed with sending the
+     *                    identification
+     * @return            A {@link IoWriteFuture} that can be used to wait for the data to be sent successfully. If
+     *                    {@code null} then the session will send the identification, otherwise it is assumed that the
+     *                    handler has sent it.
+     * @throws Exception
+     * @see               <A HREF="https://tools.ietf.org/html/rfc4253#section-4.2">RFC 4253 - section 4.2 - Protocol
+     *                    Version Exchange</A>
+     */
+    default IoWriteFuture sendIdentification(
+            Session session, String version, List<String> extraLines)
+            throws Exception {
+        return null;
+    }
+
+    /**
      * Invoked when an {@code SSH_MSG_IGNORE} packet is received
      *
      * @param  session   The {@code Session} through which the message was received
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/ReservedSessionMessagesHandlerAdapter.java b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/ReservedSessionMessagesHandlerAdapter.java
index a394612..6b97b01 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/ReservedSessionMessagesHandlerAdapter.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/ReservedSessionMessagesHandlerAdapter.java
@@ -19,9 +19,13 @@
 
 package org.apache.sshd.common.session.helpers;
 
+import java.util.List;
+
 import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.session.ReservedSessionMessagesHandler;
 import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.BufferUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
@@ -41,6 +45,22 @@ public class ReservedSessionMessagesHandlerAdapter
     }
 
     @Override
+    public IoWriteFuture sendIdentification(Session session, String version, List<String> extraLines) throws Exception {
+        if (log.isDebugEnabled()) {
+            log.debug("sendIdentification({}) version={} linesCount={}",
+                    session, version, GenericUtils.size(extraLines));
+        }
+
+        if (log.isTraceEnabled() && GenericUtils.isNotEmpty(extraLines)) {
+            for (String line : extraLines) {
+                log.trace("sendIdentification({}) {}", session, line);
+            }
+        }
+
+        return null;
+    }
+
+    @Override
     public void handleIgnoreMessage(Session session, Buffer buffer) throws Exception {
         handleIgnoreMessage(session, buffer.getBytes(), buffer);
     }
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 7c0e74d..a5d7542 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
@@ -832,13 +832,31 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
     /**
      * Send our identification.
      *
-     * @param  ident       our identification to send
-     * @return             {@link IoWriteFuture} that can be used to wait for notification that identification has been
-     *                     send
-     * @throws IOException If failed to send the packet
+     * @param  version    our identification to send
+     * @param  extraLines Extra lines to send - used only by server sessions
+     * @return            {@link IoWriteFuture} that can be used to wait for notification that identification has been
+     *                    send
+     * @throws Exception  If failed to send the packet
      */
-    protected IoWriteFuture sendIdentification(String ident) throws IOException {
-        if (log.isDebugEnabled()) {
+    protected IoWriteFuture sendIdentification(String version, List<String> extraLines) throws Exception {
+        ReservedSessionMessagesHandler handler = getReservedSessionMessagesHandler();
+        IoWriteFuture future = (handler == null) ? null : handler.sendIdentification(this, version, extraLines);
+        boolean debugEnabled = log.isDebugEnabled();
+        if (future != null) {
+            if (debugEnabled) {
+                log.debug("sendIdentification({})[{}] sent {} lines via reserved handler",
+                        this, version, GenericUtils.size(extraLines));
+            }
+
+            return future;
+        }
+
+        String ident = version;
+        if (GenericUtils.size(extraLines) > 0) {
+            ident = GenericUtils.join(extraLines, "\r\n") + "\r\n" + version;
+        }
+
+        if (debugEnabled) {
             log.debug("sendIdentification({}): {}",
                     this, ident.replace('\r', '|').replace('\n', '|'));
         }
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 d663c10..b388a6c 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
@@ -225,13 +225,7 @@ public abstract class AbstractServerSession extends AbstractSession implements S
     protected IoWriteFuture sendServerIdentification(List<String> headerLines) throws Exception {
         serverVersion = resolveIdentificationString(CoreModuleProperties.SERVER_IDENTIFICATION.getName());
         signalSendIdentification(serverVersion, headerLines);
-
-        String ident = serverVersion;
-        if (GenericUtils.size(headerLines) > 0) {
-            ident = GenericUtils.join(headerLines, "\r\n") + "\r\n" + serverVersion;
-        }
-
-        return sendIdentification(ident);
+        return sendIdentification(serverVersion, headerLines);
     }
 
     @Override
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java
index 9fee875..0b4a588 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java
@@ -39,7 +39,7 @@ public class ServerSessionImpl extends AbstractServerSession {
 
         String headerConfig = CoreModuleProperties.SERVER_EXTRA_IDENTIFICATION_LINES.getOrNull(this);
         String[] headers = GenericUtils.split(headerConfig, CoreModuleProperties.SERVER_EXTRA_IDENT_LINES_SEPARATOR);
-        // We intentionally create a modifiable array so as to allow users to modify it via SessionListener
+        // We intentionally create a modifiable array so as to allow users to modify it via SessionListener or ReservedSessionMessagesHandler
         List<String> extraLines = GenericUtils.isEmpty(headers)
                 ? new ArrayList<>()
                 : new ArrayList<>(Arrays.asList(headers));


[mina-sshd] 02/04: [SSHD-1097] Added more SessionListener callbacks related to the initial version and key exchange

Posted by lg...@apache.org.
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 0b5544a8af5bf58c23e229f39acce6639a5feb7e
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Jan 8 07:02:10 2021 +0200

    [SSHD-1097] Added more SessionListener callbacks related to the initial version and key exchange
---
 CHANGES.md                                         |   2 +
 .../sshd/client/session/AbstractClientSession.java |   4 +
 .../common/kex/extension/KexExtensionHandler.java  |   6 +-
 .../sshd/common/session/SessionListener.java       |  42 +++++++++
 .../common/session/helpers/AbstractSession.java    |   2 +
 .../sshd/common/session/helpers/SessionHelper.java | 103 ++++++++++++++++++---
 .../sshd/server/session/AbstractServerSession.java |   8 +-
 .../sshd/server/session/ServerSessionImpl.java     |  10 +-
 .../session/helpers/AbstractSessionTest.java       |  20 ++--
 9 files changed, 169 insertions(+), 28 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 69e03b3..7d2b02c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -25,6 +25,8 @@
 ## Behavioral changes and enhancements
 
 * [SSHD-1085](https://issues.apache.org/jira/browse/SSHD-1085) Added more notifications related to channel state change for detecting channel closing or closed earlier.
+* [SSHD-1091](https://issues.apache.org/jira/browse/SSHD-1091) Renamed `sshd-contrib` top-level package in order to align naming convention.
+* [SSHD-1097](https://issues.apache.org/jira/browse/SSHD-1097) Added more `SessionListener` callbacks related to the initial version and key exchange
 * [SSHD-1109](https://issues.apache.org/jira/browse/SSHD-1109) Replace log4j with logback as the slf4j logger implementation for tests
 * [SSHD-1114](https://issues.apache.org/jira/browse/SSHD-1114) Added callbacks for client-side password authentication progress
 * [SSHD-1114](https://issues.apache.org/jira/browse/SSHD-1114) Added callbacks for client-side public key authentication progress
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 6b1faff..ce8e529 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
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.net.SocketAddress;
 import java.security.KeyPair;
 import java.security.PublicKey;
+import java.util.Collections;
 import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
@@ -353,6 +354,9 @@ public abstract class AbstractClientSession extends AbstractSession implements C
 
     protected IoWriteFuture sendClientIdentification() throws Exception {
         clientVersion = resolveIdentificationString(CoreModuleProperties.CLIENT_IDENTIFICATION.getName());
+        // Note: we intentionally use an unmodifiable list in order to enforce the fact that client cannot send header lines
+        signalSendIdentification(clientVersion, Collections.emptyList());
+
         return sendIdentification(clientVersion);
     }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
index b5428c6..ae039f8 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
@@ -84,7 +84,7 @@ public interface KexExtensionHandler {
      * @param  initiator   {@code true} if the proposal is about to be sent, {@code false} if this is a proposal
      *                     received from the peer.
      * @param  proposal    The proposal contents - <B>Caveat emptor:</B> the proposal is <U>modifiable</U> i.e., the
-     *                     handler can modify before being sent or before being processed (if incoming)
+     *                     handler can modify it before being sent or before being processed (if incoming)
      * @throws IOException If failed to handle the request
      */
     default void handleKexInitProposal(
@@ -117,7 +117,7 @@ public interface KexExtensionHandler {
 
     /**
      * The phase at which {@code sendKexExtensions} is invoked
-     * 
+     *
      * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
      */
     enum KexPhase {
@@ -130,7 +130,7 @@ public interface KexExtensionHandler {
     /**
      * Invoked in order to allow the handler to send an {@code SSH_MSG_EXT_INFO} message. <B>Note:</B> this method is
      * called only if {@code isKexExtensionsAvailable} returns {@code true} for the session.
-     * 
+     *
      * @param  session     The {@link Session}
      * @param  phase       The phase at which the handler is invoked
      * @throws IOException If failed to handle the invocation
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/SessionListener.java b/sshd-core/src/main/java/org/apache/sshd/common/session/SessionListener.java
index 36f7275..7b89478 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/SessionListener.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/SessionListener.java
@@ -58,6 +58,38 @@ public interface SessionListener extends SshdEventListener {
     }
 
     /**
+     * About to send identification to peer
+     *
+     * @param session    The {@link Session} instance
+     * @param version    The resolved identification version
+     * @param extraLines Extra data preceding the identification to be sent. <B>Note:</B> the list is modifiable only if
+     *                   this is a server session. The user may modify it based on the peer.
+     * @see              <A HREF="https://tools.ietf.org/html/rfc4253#section-4.2">RFC 4253 - section 4.2 - Protocol
+     *                   Version Exchange</A>
+     */
+    default void sessionPeerIdentificationSend(
+            Session session, String version, List<String> extraLines) {
+        // ignored
+    }
+
+    /**
+     * Successfully read a line as part of the initial peer identification
+     *
+     * @param session    The {@link Session} instance
+     * @param line       The data that was read so far - <B>Note:</B> might not be a full line if more packets are
+     *                   required for full identification data. Furthermore, it may be <U>repeated</U> data due to
+     *                   packets segmentation and re-assembly mechanism
+     * @param extraLines Previous lines that were before this one - <B>Note:</B> it may be <U>repeated</U> data due to
+     *                   packets segmentation and re-assembly mechanism
+     * @see              <A HREF="https://tools.ietf.org/html/rfc4253#section-4.2">RFC 4253 - section 4.2 - Protocol
+     *                   Version Exchange</A>
+     */
+    default void sessionPeerIdentificationLine(
+            Session session, String line, List<String> extraLines) {
+        // ignored
+    }
+
+    /**
      * The peer's identification version was received
      *
      * @param session    The {@link Session} instance
@@ -72,6 +104,16 @@ public interface SessionListener extends SshdEventListener {
     }
 
     /**
+     *
+     * @param session  The referenced {@link Session}
+     * @param proposal The proposals that will be sent to the peer - <B>Caveat emptor:</B> the proposal is
+     *                 <U>modifiable</U> i.e., the handler can modify it before being sent
+     */
+    default void sessionNegotiationOptionsCreated(Session session, Map<KexProposalOption, String> proposal) {
+        // ignored
+    }
+
+    /**
      * Signals the start of the negotiation options handling
      *
      * @param session        The referenced {@link Session}
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 124895f..13bb2f9 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
@@ -2282,6 +2282,8 @@ public abstract class AbstractSession extends SessionHelper {
             }
         }
 
+        signalNegotiationOptionsCreated(proposal);
+
         byte[] seed;
         synchronized (kexState) {
             seed = sendKexInit(proposal);
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 c62719a..7c0e74d 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
@@ -610,6 +610,57 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
         listener.sessionCreated(this);
     }
 
+    protected void signalSendIdentification(String version, List<String> extraLines) throws Exception {
+        try {
+            invokeSessionSignaller(l -> {
+                signalSendIdentification(l, version, extraLines);
+                return null;
+            });
+        } catch (Throwable err) {
+            Throwable e = GenericUtils.peelException(err);
+            if (e instanceof Exception) {
+                throw (Exception) e;
+            } else {
+                throw new RuntimeSshException(e);
+            }
+        }
+    }
+
+    protected void signalSendIdentification(SessionListener listener, String version, List<String> extraLines) {
+        if (listener == null) {
+            return;
+        }
+
+        listener.sessionPeerIdentificationSend(this, version, extraLines);
+    }
+
+    protected void signalReadPeerIdentificationLine(String line, List<String> extraLines) throws Exception {
+        try {
+            invokeSessionSignaller(l -> {
+                signalReadPeerIdentificationLine(l, line, extraLines);
+                return null;
+            });
+        } catch (Throwable err) {
+            Throwable e = GenericUtils.peelException(err);
+            debug("signalReadPeerIdentificationLine({}) Failed ({}) to announce peer={}: {}",
+                    this, e.getClass().getSimpleName(), line, e.getMessage(), e);
+            if (e instanceof Exception) {
+                throw (Exception) e;
+            } else {
+                throw new RuntimeSshException(e);
+            }
+        }
+    }
+
+    protected void signalReadPeerIdentificationLine(
+            SessionListener listener, String version, List<String> extraLines) {
+        if (listener == null) {
+            return;
+        }
+
+        listener.sessionPeerIdentificationLine(this, version, extraLines);
+    }
+
     protected void signalPeerIdentificationReceived(String version, List<String> extraLines) throws Exception {
         try {
             invokeSessionSignaller(l -> {
@@ -626,10 +677,10 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 throw new RuntimeSshException(e);
             }
         }
-
     }
 
-    protected void signalPeerIdentificationReceived(SessionListener listener, String version, List<String> extraLines) {
+    protected void signalPeerIdentificationReceived(
+            SessionListener listener, String version, List<String> extraLines) {
         if (listener == null) {
             return;
         }
@@ -684,6 +735,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
             if (l == null) {
                 continue;
             }
+
             try {
                 invoker.invoke(l);
             } catch (Throwable t) {
@@ -801,14 +853,13 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
      * state and a {@code null} value will be returned. Else the identification string will be returned and the data
      * read will be consumed from the buffer.
      *
-     * @param  buffer      the buffer containing the identification string
-     * @param  server      {@code true} if it is called by the server session, {@code false} if by the client session
-     * @return             A {@link List} of all received remote identification lines until the version line was read or
-     *                     {@code null} if more data is needed. The identification line is the <U>last</U> one in the
-     *                     list
-     * @throws IOException if malformed identification found
+     * @param  buffer    the buffer containing the identification string
+     * @param  server    {@code true} if it is called by the server session, {@code false} if by the client session
+     * @return           A {@link List} of all received remote identification lines until the version line was read or
+     *                   {@code null} if more data is needed. The identification line is the <U>last</U> one in the list
+     * @throws Exception if malformed identification found
      */
-    protected List<String> doReadIdentification(Buffer buffer, boolean server) throws IOException {
+    protected List<String> doReadIdentification(Buffer buffer, boolean server) throws Exception {
         int maxIdentSize = CoreModuleProperties.MAX_IDENTIFICATION_SIZE.getRequired(this);
         List<String> ident = null;
         int rpos = buffer.rpos();
@@ -869,6 +920,8 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
             if (ident == null) {
                 ident = new ArrayList<>();
             }
+
+            signalReadPeerIdentificationLine(str, ident);
             ident.add(str);
 
             // if this is a server then only one line is expected from the client
@@ -943,6 +996,32 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
         return proposal;
     }
 
+    protected void signalNegotiationOptionsCreated(Map<KexProposalOption, String> proposal) {
+        try {
+            invokeSessionSignaller(l -> {
+                signalNegotiationOptionsCreated(l, proposal);
+                return null;
+            });
+        } catch (Throwable t) {
+            Throwable err = GenericUtils.peelException(t);
+            if (err instanceof RuntimeException) {
+                throw (RuntimeException) err;
+            } else if (err instanceof Error) {
+                throw (Error) err;
+            } else {
+                throw new RuntimeException(err);
+            }
+        }
+    }
+
+    protected void signalNegotiationOptionsCreated(SessionListener listener, Map<KexProposalOption, String> proposal) {
+        if (listener == null) {
+            return;
+        }
+
+        listener.sessionNegotiationOptionsCreated(this, proposal);
+    }
+
     protected void signalNegotiationStart(
             Map<KexProposalOption, String> c2sOptions, Map<KexProposalOption, String> s2cOptions) {
         try {
@@ -950,7 +1029,8 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 signalNegotiationStart(l, c2sOptions, s2cOptions);
                 return null;
             });
-        } catch (Throwable err) {
+        } catch (Throwable t) {
+            Throwable err = GenericUtils.peelException(t);
             if (err instanceof RuntimeException) {
                 throw (RuntimeException) err;
             } else if (err instanceof Error) {
@@ -978,7 +1058,8 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
                 signalNegotiationEnd(l, c2sOptions, s2cOptions, negotiatedGuess, reason);
                 return null;
             });
-        } catch (Throwable err) {
+        } catch (Throwable t) {
+            Throwable err = GenericUtils.peelException(t);
             if (err instanceof RuntimeException) {
                 throw (RuntimeException) err;
             } else if (err instanceof Error) {
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 4c2c9b3..d663c10 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
@@ -219,16 +219,18 @@ public abstract class AbstractServerSession extends AbstractSession implements S
      *                     {@code null}/empty
      * @return             An {@link IoWriteFuture} that can be used to be notified of identification data being written
      *                     successfully or failing
-     * @throws IOException If failed to send identification
+     * @throws Exception   If failed to send identification
      * @see                <A HREF="https://tools.ietf.org/html/rfc4253#section-4.2">RFC 4253 - section 4.2</A>
      */
-    protected IoWriteFuture sendServerIdentification(String... headerLines) throws IOException {
+    protected IoWriteFuture sendServerIdentification(List<String> headerLines) throws Exception {
         serverVersion = resolveIdentificationString(CoreModuleProperties.SERVER_IDENTIFICATION.getName());
+        signalSendIdentification(serverVersion, headerLines);
 
         String ident = serverVersion;
-        if (GenericUtils.length(headerLines) > 0) {
+        if (GenericUtils.size(headerLines) > 0) {
             ident = GenericUtils.join(headerLines, "\r\n") + "\r\n" + serverVersion;
         }
+
         return sendIdentification(ident);
     }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java
index 06d7c97..9fee875 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java
@@ -18,6 +18,10 @@
  */
 package org.apache.sshd.server.session;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.core.CoreModuleProperties;
@@ -35,6 +39,10 @@ public class ServerSessionImpl extends AbstractServerSession {
 
         String headerConfig = CoreModuleProperties.SERVER_EXTRA_IDENTIFICATION_LINES.getOrNull(this);
         String[] headers = GenericUtils.split(headerConfig, CoreModuleProperties.SERVER_EXTRA_IDENT_LINES_SEPARATOR);
-        sendServerIdentification(headers);
+        // We intentionally create a modifiable array so as to allow users to modify it via SessionListener
+        List<String> extraLines = GenericUtils.isEmpty(headers)
+                ? new ArrayList<>()
+                : new ArrayList<>(Arrays.asList(headers));
+        sendServerIdentification(extraLines);
     }
 }
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 d8fd35f..a143cab 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
@@ -86,28 +86,28 @@ public class AbstractSessionTest extends BaseTestSupport {
     }
 
     @Test
-    public void testReadIdentSimple() throws IOException {
+    public void testReadIdentSimple() throws Exception {
         Buffer buf = new ByteArrayBuffer("SSH-2.0-software\r\n".getBytes(StandardCharsets.UTF_8));
         String ident = readIdentification(session, buf);
         assertEquals("SSH-2.0-software", ident);
     }
 
     @Test
-    public void testReadIdentWithoutCR() throws IOException {
+    public void testReadIdentWithoutCR() throws Exception {
         Buffer buf = new ByteArrayBuffer("SSH-2.0-software\n".getBytes(StandardCharsets.UTF_8));
         String ident = readIdentification(session, buf);
         assertEquals("SSH-2.0-software", ident);
     }
 
     @Test
-    public void testReadIdentWithHeaders() throws IOException {
+    public void testReadIdentWithHeaders() throws Exception {
         Buffer buf = new ByteArrayBuffer("a header line\r\nSSH-2.0-software\r\n".getBytes(StandardCharsets.UTF_8));
         String ident = readIdentification(session, buf);
         assertEquals("SSH-2.0-software", ident);
     }
 
     @Test
-    public void testReadIdentWithSplitPackets() throws IOException {
+    public void testReadIdentWithSplitPackets() throws Exception {
         Buffer buf = new ByteArrayBuffer("header line\r\nSSH".getBytes(StandardCharsets.UTF_8));
         String ident = readIdentification(session, buf);
         assertNull("Unexpected identification for header only", ident);
@@ -118,14 +118,14 @@ public class AbstractSessionTest extends BaseTestSupport {
     }
 
     @Test(expected = StreamCorruptedException.class)
-    public void testReadIdentBadLineEnding() throws IOException {
+    public void testReadIdentBadLineEnding() throws Exception {
         Buffer buf = new ByteArrayBuffer("SSH-2.0-software\ra".getBytes(StandardCharsets.UTF_8));
         String ident = readIdentification(session, buf);
         fail("Unexpected success: " + ident);
     }
 
     @Test(expected = StreamCorruptedException.class)
-    public void testReadIdentLongLine() throws IOException {
+    public void testReadIdentLongLine() throws Exception {
         StringBuilder sb = new StringBuilder(SessionContext.MAX_VERSION_LINE_LENGTH + Integer.SIZE);
         sb.append("SSH-2.0-software");
         do {
@@ -138,7 +138,7 @@ public class AbstractSessionTest extends BaseTestSupport {
     }
 
     @Test(expected = StreamCorruptedException.class)
-    public void testReadIdentWithNullChar() throws IOException {
+    public void testReadIdentWithNullChar() throws Exception {
         String id = "SSH-2.0" + '\0' + "-software\r\n";
         Buffer buf = new ByteArrayBuffer(id.getBytes(StandardCharsets.UTF_8));
         String ident = readIdentification(session, buf);
@@ -146,7 +146,7 @@ public class AbstractSessionTest extends BaseTestSupport {
     }
 
     @Test(expected = StreamCorruptedException.class)
-    public void testReadIdentLongHeader() throws IOException {
+    public void testReadIdentLongHeader() throws Exception {
         int maxIdentSize = CoreModuleProperties.MAX_IDENTIFICATION_SIZE.getRequiredDefault();
         StringBuilder sb = new StringBuilder(maxIdentSize + Integer.SIZE);
         do {
@@ -300,7 +300,7 @@ public class AbstractSessionTest extends BaseTestSupport {
         }
     }
 
-    private static String readIdentification(MySession session, Buffer buf) throws IOException {
+    private static String readIdentification(MySession session, Buffer buf) throws Exception {
         List<String> lines = session.doReadIdentification(buf);
         return GenericUtils.isEmpty(lines) ? null : lines.get(lines.size() - 1);
     }
@@ -439,7 +439,7 @@ public class AbstractSessionTest extends BaseTestSupport {
             return false;
         }
 
-        public List<String> doReadIdentification(Buffer buffer) throws IOException {
+        public List<String> doReadIdentification(Buffer buffer) throws Exception {
             return super.doReadIdentification(buffer, false);
         }
 


[mina-sshd] 01/04: [SSHD-1091] Renamed sshd-contrib top-level package in order to align naming convention.

Posted by lg...@apache.org.
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 daac252f981b679434b87802f07d27079863d760
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Jan 8 06:35:02 2021 +0200

    [SSHD-1091] Renamed sshd-contrib top-level package in order to align naming convention.
---
 .../client/auth/password/InteractivePasswordIdentityProvider.java     | 3 ++-
 .../common/channel/throttle/ThrottlingChannelStreamWriter.java        | 4 +++-
 .../sshd/{ => contrib}/common/compression/DeflatingInputStream.java   | 4 ++--
 .../helpers/ChannelIdTrackingUnknownChannelReferenceHandler.java      | 3 ++-
 .../apache/sshd/{ => contrib}/common/signature/LegacyDSASigner.java   | 3 ++-
 .../common/util/io/ExposedBufferByteArrayOutputStream.java            | 2 +-
 .../apache/sshd/{ => contrib}/common/util/io/LineOutputStream.java    | 2 +-
 .../androidopenssl/AndroidOpenSSLSecurityProviderRegistrar.java       | 2 +-
 .../{ => contrib}/server/scp/SimpleAccessControlScpEventListener.java | 2 +-
 .../server/session/proxyprotocol/ProxyProtocolAcceptor.java           | 2 +-
 .../server/subsystem/sftp/DetailedSftpErrorStatusDataHandler.java     | 2 +-
 .../server/subsystem/sftp/SimpleAccessControlSftpEventListener.java   | 2 +-
 .../client/auth/password/InteractivePasswordIdentityProviderTest.java | 3 ++-
 .../common/channel/throttle/ThrottlingChannelStreamWriterTest.java    | 3 ++-
 .../sshd/{ => contrib}/common/signature/LegacyDSASignerTest.java      | 3 ++-
 .../sshd/{ => contrib}/common/util/io/LineOutputStreamTest.java       | 3 ++-
 .../server/scp/SimpleAccessControlScpEventListenerTest.java           | 2 +-
 .../subsystem/sftp/SimpleAccessControlSftpEventListenerTest.java      | 2 +-
 .../org/apache/sshd/{ => contrib}/common/signature/ssh-dss-1024       | 0
 .../org/apache/sshd/{ => contrib}/common/signature/ssh-dss-2048       | 0
 20 files changed, 28 insertions(+), 19 deletions(-)

diff --git a/sshd-contrib/src/main/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProvider.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProvider.java
similarity index 97%
rename from sshd-contrib/src/main/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProvider.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProvider.java
index 855587d..4f0846f 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProvider.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProvider.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.client.auth.password;
+package org.apache.sshd.contrib.client.auth.password;
 
 import java.util.Iterator;
 import java.util.NoSuchElementException;
@@ -24,6 +24,7 @@ import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.session.ClientSessionHolder;
 import org.apache.sshd.common.session.SessionHolder;
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/channel/throttle/ThrottlingChannelStreamWriter.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/channel/throttle/ThrottlingChannelStreamWriter.java
similarity index 97%
rename from sshd-contrib/src/main/java/org/apache/sshd/common/channel/throttle/ThrottlingChannelStreamWriter.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/common/channel/throttle/ThrottlingChannelStreamWriter.java
index 5092b5e..addf088 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/common/channel/throttle/ThrottlingChannelStreamWriter.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/channel/throttle/ThrottlingChannelStreamWriter.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.channel.throttle;
+package org.apache.sshd.contrib.common.channel.throttle;
 
 import java.io.EOFException;
 import java.io.IOException;
@@ -32,6 +32,8 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.sshd.common.Property;
 import org.apache.sshd.common.PropertyResolver;
 import org.apache.sshd.common.channel.Channel;
+import org.apache.sshd.common.channel.throttle.ChannelStreamWriter;
+import org.apache.sshd.common.channel.throttle.DefaultChannelStreamWriter;
 import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.util.ValidateUtils;
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/compression/DeflatingInputStream.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/compression/DeflatingInputStream.java
similarity index 97%
rename from sshd-contrib/src/main/java/org/apache/sshd/common/compression/DeflatingInputStream.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/common/compression/DeflatingInputStream.java
index 470b132..9f80d23 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/common/compression/DeflatingInputStream.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/compression/DeflatingInputStream.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.compression;
+package org.apache.sshd.contrib.common.compression;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -26,8 +26,8 @@ import java.io.StreamCorruptedException;
 import java.util.Objects;
 
 import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.io.ExposedBufferByteArrayOutputStream;
 import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.contrib.common.util.io.ExposedBufferByteArrayOutputStream;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/session/helpers/ChannelIdTrackingUnknownChannelReferenceHandler.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/session/helpers/ChannelIdTrackingUnknownChannelReferenceHandler.java
similarity index 96%
rename from sshd-contrib/src/main/java/org/apache/sshd/common/session/helpers/ChannelIdTrackingUnknownChannelReferenceHandler.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/common/session/helpers/ChannelIdTrackingUnknownChannelReferenceHandler.java
index f133cde..e791d1c 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/common/session/helpers/ChannelIdTrackingUnknownChannelReferenceHandler.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/session/helpers/ChannelIdTrackingUnknownChannelReferenceHandler.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.session.helpers;
+package org.apache.sshd.contrib.common.session.helpers;
 
 import java.io.IOException;
 
@@ -28,6 +28,7 @@ import org.apache.sshd.common.channel.ChannelListener;
 import org.apache.sshd.common.channel.exception.SshChannelNotFoundException;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.session.helpers.DefaultUnknownChannelReferenceHandler;
 import org.apache.sshd.common.util.buffer.Buffer;
 
 /**
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/signature/LegacyDSASigner.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/signature/LegacyDSASigner.java
similarity index 98%
rename from sshd-contrib/src/main/java/org/apache/sshd/common/signature/LegacyDSASigner.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/common/signature/LegacyDSASigner.java
index 1645ec0..8999e19 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/common/signature/LegacyDSASigner.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/signature/LegacyDSASigner.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.signature;
+package org.apache.sshd.contrib.common.signature;
 
 import java.io.IOException;
 import java.math.BigInteger;
@@ -37,6 +37,7 @@ import java.util.Arrays;
 
 import org.apache.sshd.common.Factory;
 import org.apache.sshd.common.random.Random;
+import org.apache.sshd.common.signature.SignatureDSA;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.io.der.DERParser;
 import org.apache.sshd.common.util.io.der.DERWriter;
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/util/io/ExposedBufferByteArrayOutputStream.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/io/ExposedBufferByteArrayOutputStream.java
similarity index 96%
rename from sshd-contrib/src/main/java/org/apache/sshd/common/util/io/ExposedBufferByteArrayOutputStream.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/io/ExposedBufferByteArrayOutputStream.java
index 2ba92d6..e64f8ff 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/common/util/io/ExposedBufferByteArrayOutputStream.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/io/ExposedBufferByteArrayOutputStream.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.contrib.common.util.io;
 
 import java.io.ByteArrayOutputStream;
 
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/util/io/LineOutputStream.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/io/LineOutputStream.java
similarity index 98%
rename from sshd-contrib/src/main/java/org/apache/sshd/common/util/io/LineOutputStream.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/io/LineOutputStream.java
index 44012bc..b5085b1 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/common/util/io/LineOutputStream.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/io/LineOutputStream.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.contrib.common.util.io;
 
 import java.io.IOException;
 import java.io.OutputStream;
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/util/security/androidopenssl/AndroidOpenSSLSecurityProviderRegistrar.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/security/androidopenssl/AndroidOpenSSLSecurityProviderRegistrar.java
similarity index 96%
rename from sshd-contrib/src/main/java/org/apache/sshd/common/util/security/androidopenssl/AndroidOpenSSLSecurityProviderRegistrar.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/security/androidopenssl/AndroidOpenSSLSecurityProviderRegistrar.java
index 187bc29..5be511d 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/common/util/security/androidopenssl/AndroidOpenSSLSecurityProviderRegistrar.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/util/security/androidopenssl/AndroidOpenSSLSecurityProviderRegistrar.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.util.security.androidopenssl;
+package org.apache.sshd.contrib.common.util.security.androidopenssl;
 
 import java.security.Provider;
 import java.security.Security;
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/server/scp/SimpleAccessControlScpEventListener.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/scp/SimpleAccessControlScpEventListener.java
similarity index 98%
rename from sshd-contrib/src/main/java/org/apache/sshd/server/scp/SimpleAccessControlScpEventListener.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/server/scp/SimpleAccessControlScpEventListener.java
index 40e757c..ace3332 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/server/scp/SimpleAccessControlScpEventListener.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/scp/SimpleAccessControlScpEventListener.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.server.scp;
+package org.apache.sshd.contrib.server.scp;
 
 import java.io.IOException;
 import java.nio.file.AccessDeniedException;
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/session/proxyprotocol/ProxyProtocolAcceptor.java
similarity index 98%
rename from sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/server/session/proxyprotocol/ProxyProtocolAcceptor.java
index 05e3f69..21f65b0 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/session/proxyprotocol/ProxyProtocolAcceptor.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.server.session.proxyprotocol;
+package org.apache.sshd.contrib.server.session.proxyprotocol;
 
 import java.net.InetSocketAddress;
 import java.util.Arrays;
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/DetailedSftpErrorStatusDataHandler.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/subsystem/sftp/DetailedSftpErrorStatusDataHandler.java
similarity index 97%
rename from sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/DetailedSftpErrorStatusDataHandler.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/server/subsystem/sftp/DetailedSftpErrorStatusDataHandler.java
index ef78d19..62b574c 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/DetailedSftpErrorStatusDataHandler.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/subsystem/sftp/DetailedSftpErrorStatusDataHandler.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.server.subsystem.sftp;
+package org.apache.sshd.contrib.server.subsystem.sftp;
 
 import java.nio.file.FileSystemException;
 import java.util.Objects;
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
similarity index 99%
rename from sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
rename to sshd-contrib/src/main/java/org/apache/sshd/contrib/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
index 55474de..fa8c9b3 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.server.subsystem.sftp;
+package org.apache.sshd.contrib.server.subsystem.sftp;
 
 import java.io.IOException;
 import java.nio.file.AccessDeniedException;
diff --git a/sshd-contrib/src/test/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProviderTest.java b/sshd-contrib/src/test/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProviderTest.java
similarity index 97%
rename from sshd-contrib/src/test/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProviderTest.java
rename to sshd-contrib/src/test/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProviderTest.java
index 4f8ba5f..ee381cb 100644
--- a/sshd-contrib/src/test/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProviderTest.java
+++ b/sshd-contrib/src/test/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProviderTest.java
@@ -16,13 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.client.auth.password;
+package org.apache.sshd.contrib.client.auth.password;
 
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.util.test.BaseTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
diff --git a/sshd-contrib/src/test/java/org/apache/sshd/common/channel/throttle/ThrottlingChannelStreamWriterTest.java b/sshd-contrib/src/test/java/org/apache/sshd/contrib/common/channel/throttle/ThrottlingChannelStreamWriterTest.java
similarity index 97%
rename from sshd-contrib/src/test/java/org/apache/sshd/common/channel/throttle/ThrottlingChannelStreamWriterTest.java
rename to sshd-contrib/src/test/java/org/apache/sshd/contrib/common/channel/throttle/ThrottlingChannelStreamWriterTest.java
index 14eb5ca..d9ed03b 100644
--- a/sshd-contrib/src/test/java/org/apache/sshd/common/channel/throttle/ThrottlingChannelStreamWriterTest.java
+++ b/sshd-contrib/src/test/java/org/apache/sshd/contrib/common/channel/throttle/ThrottlingChannelStreamWriterTest.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sshd.common.channel.throttle;
+package org.apache.sshd.contrib.common.channel.throttle;
 
 import java.io.IOException;
 import java.io.StreamCorruptedException;
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.common.channel.IoWriteFutureImpl;
+import org.apache.sshd.common.channel.throttle.ChannelStreamWriter;
 import org.apache.sshd.common.io.IoWriteFuture;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
diff --git a/sshd-contrib/src/test/java/org/apache/sshd/common/signature/LegacyDSASignerTest.java b/sshd-contrib/src/test/java/org/apache/sshd/contrib/common/signature/LegacyDSASignerTest.java
similarity index 98%
rename from sshd-contrib/src/test/java/org/apache/sshd/common/signature/LegacyDSASignerTest.java
rename to sshd-contrib/src/test/java/org/apache/sshd/contrib/common/signature/LegacyDSASignerTest.java
index f9645b4..1ab162a 100644
--- a/sshd-contrib/src/test/java/org/apache/sshd/common/signature/LegacyDSASignerTest.java
+++ b/sshd-contrib/src/test/java/org/apache/sshd/contrib/common/signature/LegacyDSASignerTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.signature;
+package org.apache.sshd.contrib.common.signature;
 
 import java.io.IOException;
 import java.net.URL;
@@ -32,6 +32,7 @@ import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.config.keys.loader.pem.DSSPEMResourceKeyPairParser;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.random.JceRandomFactory;
+import org.apache.sshd.common.signature.SignatureDSA;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.security.SecurityUtils;
diff --git a/sshd-contrib/src/test/java/org/apache/sshd/common/util/io/LineOutputStreamTest.java b/sshd-contrib/src/test/java/org/apache/sshd/contrib/common/util/io/LineOutputStreamTest.java
similarity index 97%
rename from sshd-contrib/src/test/java/org/apache/sshd/common/util/io/LineOutputStreamTest.java
rename to sshd-contrib/src/test/java/org/apache/sshd/contrib/common/util/io/LineOutputStreamTest.java
index 5107f1b..7e58da1 100644
--- a/sshd-contrib/src/test/java/org/apache/sshd/common/util/io/LineOutputStreamTest.java
+++ b/sshd-contrib/src/test/java/org/apache/sshd/contrib/common/util/io/LineOutputStreamTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.common.util.io;
+package org.apache.sshd.contrib.common.util.io;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -30,6 +30,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
 import org.apache.sshd.util.test.JUnitTestSupport;
 import org.apache.sshd.util.test.NoIoTestCase;
diff --git a/sshd-contrib/src/test/java/org/apache/sshd/server/scp/SimpleAccessControlScpEventListenerTest.java b/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/scp/SimpleAccessControlScpEventListenerTest.java
similarity index 99%
rename from sshd-contrib/src/test/java/org/apache/sshd/server/scp/SimpleAccessControlScpEventListenerTest.java
rename to sshd-contrib/src/test/java/org/apache/sshd/contrib/server/scp/SimpleAccessControlScpEventListenerTest.java
index d270387..9aeee01 100644
--- a/sshd-contrib/src/test/java/org/apache/sshd/server/scp/SimpleAccessControlScpEventListenerTest.java
+++ b/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/scp/SimpleAccessControlScpEventListenerTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.server.scp;
+package org.apache.sshd.contrib.server.scp;
 
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
diff --git a/sshd-contrib/src/test/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListenerTest.java b/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/subsystem/sftp/SimpleAccessControlSftpEventListenerTest.java
similarity index 99%
rename from sshd-contrib/src/test/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListenerTest.java
rename to sshd-contrib/src/test/java/org/apache/sshd/contrib/server/subsystem/sftp/SimpleAccessControlSftpEventListenerTest.java
index c83eed6..78ba222 100644
--- a/sshd-contrib/src/test/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListenerTest.java
+++ b/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/subsystem/sftp/SimpleAccessControlSftpEventListenerTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.sshd.server.subsystem.sftp;
+package org.apache.sshd.contrib.server.subsystem.sftp;
 
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/signature/ssh-dss-1024 b/sshd-contrib/src/test/resources/org/apache/sshd/contrib/common/signature/ssh-dss-1024
similarity index 100%
rename from sshd-contrib/src/test/resources/org/apache/sshd/common/signature/ssh-dss-1024
rename to sshd-contrib/src/test/resources/org/apache/sshd/contrib/common/signature/ssh-dss-1024
diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/signature/ssh-dss-2048 b/sshd-contrib/src/test/resources/org/apache/sshd/contrib/common/signature/ssh-dss-2048
similarity index 100%
rename from sshd-contrib/src/test/resources/org/apache/sshd/common/signature/ssh-dss-2048
rename to sshd-contrib/src/test/resources/org/apache/sshd/contrib/common/signature/ssh-dss-2048


[mina-sshd] 04/04: [SSHD-1097] Implemented endless tarpit example in sshd-contrib

Posted by lg...@apache.org.
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 cbd61985ef49d17d6d1904fd7f96d42561129c7b
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Fri Jan 8 09:33:53 2021 +0200

    [SSHD-1097] Implemented endless tarpit example in sshd-contrib
---
 CHANGES.md                                         |   1 +
 README.md                                          |   3 +
 docs/event-listeners.md                            |   1 +
 docs/howto.md                                      |  25 ++
 .../org/apache/sshd/common/util/GenericUtils.java  |  10 +-
 .../org/apache/sshd/common/util/buffer/Buffer.java |  27 +-
 .../sshd/common/util/buffer/ByteArrayBuffer.java   |  18 +-
 .../sshd/contrib/common/io/EndlessWriteFuture.java |  90 +++++++
 .../contrib/common/io/ImmediateWriteFuture.java    |  32 +--
 .../EndlessTarpitSenderSupportDevelopment.java     | 280 +++++++++++++++++++++
 .../sshd/client/session/AbstractClientSession.java |  13 +-
 .../sshd/client/session/ClientSessionImpl.java     |   2 +-
 .../common/kex/extension/KexExtensionHandler.java  |  88 +++----
 .../session/ReservedSessionMessagesHandler.java    |  21 +-
 .../sshd/common/session/SessionWorkBuffer.java     |   3 +-
 .../common/session/helpers/AbstractSession.java    |  77 +++---
 .../sshd/common/session/helpers/SessionHelper.java |  22 +-
 .../sshd/server/session/AbstractServerSession.java |   2 +-
 .../sshd/server/session/ServerSessionImpl.java     |   3 +-
 .../kex/extension/KexExtensionHandlerTest.java     |   2 +-
 .../sshd/util/test/CoreTestSupportUtils.java       |  10 +-
 .../sshd/scp/client/SimpleScpClientImpl.java       |   3 +-
 22 files changed, 589 insertions(+), 144 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 5dada51..b2bc036 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -28,6 +28,7 @@
 * [SSHD-1091](https://issues.apache.org/jira/browse/SSHD-1091) Renamed `sshd-contrib` top-level package in order to align naming convention.
 * [SSHD-1097](https://issues.apache.org/jira/browse/SSHD-1097) Added more `SessionListener` callbacks related to the initial version and key exchange
 * [SSHD-1097](https://issues.apache.org/jira/browse/SSHD-1097) Added more capability to send peer identification via `ReservedSessionMessagesHandler`
+* [SSHD-1097](https://issues.apache.org/jira/browse/SSHD-1097) Implemented [endless tarpit](https://nullprogram.com/blog/2019/03/22/) example in sshd-contrib
 * [SSHD-1109](https://issues.apache.org/jira/browse/SSHD-1109) Replace log4j with logback as the slf4j logger implementation for tests
 * [SSHD-1114](https://issues.apache.org/jira/browse/SSHD-1114) Added callbacks for client-side password authentication progress
 * [SSHD-1114](https://issues.apache.org/jira/browse/SSHD-1114) Added callbacks for client-side public key authentication progress
diff --git a/README.md b/README.md
index ab548c6..9fab4f7 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,7 @@ based applications requiring SSH support.
     * `copy-file`, `copy-data` - [DRAFT 00 - sections 6, 7](http://tools.ietf.org/id/draft-ietf-secsh-filexfer-extensions-00.txt)
     * `space-available` - [DRAFT 09 - section 9.3](http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-09.txt)
     * Several [OpenSSH SFTP extensions](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL)
+* [Endless tarpit](https://nullprogram.com/blog/2019/03/22/) - see [HOWTO(s)](./docs/howto.md) section.
 
 ## Implemented/available support
 
@@ -172,3 +173,5 @@ implementation of the logging API can be selected from the many existing adaptor
 ## [Configuration/data files parsing support](./docs/files-parsing.md)
 
 ## [Extension modules](./docs/extensions.md)
+
+# [HOWTO(s)](./docs/howto.md)
\ No newline at end of file
diff --git a/docs/event-listeners.md b/docs/event-listeners.md
index 1f17a98..3527964 100644
--- a/docs/event-listeners.md
+++ b/docs/event-listeners.md
@@ -100,6 +100,7 @@ An **experimental** implementation example is available for the client side - se
 
 Can be used to handle the following cases:
 
+* Intervene during the initial identification and KEX
 * [SSH_MSG_IGNORE](https://tools.ietf.org/html/rfc4253#section-11.2)
 * [SSH_MSG_DEBUG](https://tools.ietf.org/html/rfc4253#section-11.3)
 * [SSH_MSG_UNIMPLEMENTED](https://tools.ietf.org/html/rfc4253#section-11.4)
diff --git a/docs/howto.md b/docs/howto.md
new file mode 100644
index 0000000..e9ea60b
--- /dev/null
+++ b/docs/howto.md
@@ -0,0 +1,25 @@
+# HOWTO(s)
+
+This section contains some useful "cookbook recipes" for getting the most out of the code. Please note that it does **not** covers code samples that already appear in the previous sections - such as creating sessions, managing authentication, 3-way SCP, mounting file systems via SFTP, etc... Instead, it focuses on more "exotic" implementations that are not usually part of the normal SSH flow.
+
+## [Endless tarpit](https://nullprogram.com/blog/2019/03/22/)
+
+In order to achieve this one needs to use a `ReservedSessionMessagesHandler` on the server side that overrides the session identification and KEX message callbacks as follows:
+
+* When `sendIdentification` callback is invoked
+
+    * Check if you wish to trap the peer into the endless tarpit - if not, then return `null`
+    
+    * Spawn a thread that will feed the peer session with periodic infinite data.
+    
+    * Return a never succeeding `IoWriteFuture` - see `EndlessWriteFuture` in *sshd-contrib* package for such an implementation
+    
+* When `sendKexInitRequest` callback is invoked
+
+    * Check if you wish to trap the peer into the endless tarpit - if not, then return `null`
+
+    * Return an `IoWriteFuture` that "succeeds" immediately - see `ImmediateWriteFuture` in *sshd-contrib* package for such an implementation.
+
+The idea is to prevent the normal session establish flow by taking over the initial handshake identification and blocking the initial KEX message from the server.
+
+A sample implementation can be found in the `EndlessTarpitSenderSupportDevelopment` class in the *sshd-contrib* package *test* section.
\ No newline at end of file
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index 9b53eb6..181a902 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -928,11 +928,15 @@ public final class GenericUtils {
         return current;
     }
 
-    public static IOException toIOException(Throwable e) {
+    public static void rethrowAsIoException(Throwable e) throws IOException {
         if (e instanceof IOException) {
-            return (IOException) e;
+            throw (IOException) e;
+        } else if (e instanceof RuntimeException) {
+            throw (RuntimeException) e;
+        } else if (e instanceof Error) {
+            throw (Error) e;
         } else {
-            return new IOException(e);
+            throw new IOException(e);
         }
     }
 
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java
index a691a8f..edf5137 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java
@@ -157,18 +157,20 @@ public abstract class Buffer implements Readable {
     /**
      * Reset read/write positions to zero - <B>Note:</B> zeroes any previously existing data
      *
-     * @see #clear(boolean)
+     * @return Reference to this buffer
+     * @see    #clear(boolean)
      */
-    public void clear() {
-        clear(true);
+    public Buffer clear() {
+        return clear(true);
     }
 
     /**
      * Reset read/write positions to zero
      *
-     * @param wipeData Whether to zero any previously existing data
+     * @param  wipeData Whether to zero any previously existing data
+     * @return          Reference to this buffer
      */
-    public abstract void clear(boolean wipeData);
+    public abstract Buffer clear(boolean wipeData);
 
     public boolean isValidMessageStructure(Class<?>... fieldTypes) {
         return isValidMessageStructure(GenericUtils.isEmpty(fieldTypes) ? Collections.emptyList() : Arrays.asList(fieldTypes));
@@ -992,17 +994,18 @@ public abstract class Buffer implements Readable {
         }
     }
 
-    protected void ensureCapacity(int capacity) {
-        ensureCapacity(capacity, BufferUtils.DEFAULT_BUFFER_GROWTH_FACTOR);
+    public Buffer ensureCapacity(int capacity) {
+        return ensureCapacity(capacity, BufferUtils.DEFAULT_BUFFER_GROWTH_FACTOR);
     }
 
     /**
-     * @param capacity     The required capacity
-     * @param growthFactor An {@link IntUnaryOperator} that is invoked if the current capacity is insufficient. The
-     *                     argument is the minimum required new data length, the function result should be the effective
-     *                     new data length to be allocated - if less than minimum then an exception is thrown
+     * @param  capacity     The required capacity
+     * @param  growthFactor An {@link IntUnaryOperator} that is invoked if the current capacity is insufficient. The
+     *                      argument is the minimum required new data length, the function result should be the
+     *                      effective new data length to be allocated - if less than minimum then an exception is thrown
+     * @return              This buffer instance
      */
-    public abstract void ensureCapacity(int capacity, IntUnaryOperator growthFactor);
+    public abstract Buffer ensureCapacity(int capacity, IntUnaryOperator growthFactor);
 
     /**
      * @return Current size of underlying backing data bytes array
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
index 5de36a6..04085ed 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java
@@ -184,13 +184,15 @@ public class ByteArrayBuffer extends Buffer {
     }
 
     @Override
-    public void clear(boolean wipeData) {
+    public Buffer clear(boolean wipeData) {
         rpos = 0;
         wpos = 0;
 
         if (wipeData) {
             Arrays.fill(data, (byte) 0);
         }
+
+        return this;
     }
 
     @Override
@@ -207,11 +209,11 @@ public class ByteArrayBuffer extends Buffer {
 
     @Override
     public int putBuffer(Readable buffer, boolean expand) {
-        int r = expand ? buffer.available() : Math.min(buffer.available(), capacity());
-        ensureCapacity(r);
-        buffer.getRawBytes(data, wpos, r);
-        wpos += r;
-        return r;
+        int required = expand ? buffer.available() : Math.min(buffer.available(), capacity());
+        ensureCapacity(required);
+        buffer.getRawBytes(data, wpos, required);
+        wpos += required;
+        return required;
     }
 
     @Override
@@ -259,7 +261,7 @@ public class ByteArrayBuffer extends Buffer {
     }
 
     @Override
-    public void ensureCapacity(int capacity, IntUnaryOperator growthFactor) {
+    public Buffer ensureCapacity(int capacity, IntUnaryOperator growthFactor) {
         ValidateUtils.checkTrue(capacity >= 0, "Negative capacity requested: %d", capacity);
 
         int maxSize = size();
@@ -276,6 +278,8 @@ public class ByteArrayBuffer extends Buffer {
             System.arraycopy(data, 0, tmp, 0, data.length);
             data = tmp;
         }
+
+        return this;
     }
 
     @Override
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/io/EndlessWriteFuture.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/io/EndlessWriteFuture.java
new file mode 100644
index 0000000..612c93b
--- /dev/null
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/io/EndlessWriteFuture.java
@@ -0,0 +1,90 @@
+/*
+ * 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.contrib.common.io;
+
+import java.io.IOException;
+
+import org.apache.sshd.common.future.SshFutureListener;
+import org.apache.sshd.common.io.IoWriteFuture;
+
+/**
+ * Never signals a successful write completion and ignores all listeners
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class EndlessWriteFuture implements IoWriteFuture {
+    public static final EndlessWriteFuture INSTANCE = new EndlessWriteFuture();
+
+    public EndlessWriteFuture() {
+        super();
+    }
+
+    @Override
+    public IoWriteFuture verify(long timeoutMillis) throws IOException {
+        await(timeoutMillis);
+        return null;
+    }
+
+    @Override
+    public boolean isDone() {
+        return false;
+    }
+
+    @Override
+    public Object getId() {
+        return "ENDLESS";
+    }
+
+    @Override
+    public boolean awaitUninterruptibly(long timeoutMillis) {
+        try {
+            Thread.sleep(timeoutMillis);
+        } catch (InterruptedException e) {
+            // ignored
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean await(long timeoutMillis) throws IOException {
+        return awaitUninterruptibly(timeoutMillis);
+    }
+
+    @Override
+    public IoWriteFuture removeListener(SshFutureListener<IoWriteFuture> listener) {
+        return this;
+    }
+
+    @Override
+    public IoWriteFuture addListener(SshFutureListener<IoWriteFuture> listener) {
+        return this;
+    }
+
+    @Override
+    public boolean isWritten() {
+        return false;
+    }
+
+    @Override
+    public Throwable getException() {
+        return null;
+    }
+}
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/SessionWorkBuffer.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/io/ImmediateWriteFuture.java
similarity index 55%
copy from sshd-core/src/main/java/org/apache/sshd/common/session/SessionWorkBuffer.java
copy to sshd-contrib/src/main/java/org/apache/sshd/contrib/common/io/ImmediateWriteFuture.java
index 00815a2..0711e40 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/SessionWorkBuffer.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/common/io/ImmediateWriteFuture.java
@@ -17,33 +17,19 @@
  * under the License.
  */
 
-package org.apache.sshd.common.session;
+package org.apache.sshd.contrib.common.io;
 
-import java.util.Objects;
-
-import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.channel.IoWriteFutureImpl;
+import org.apache.sshd.common.util.buffer.Buffer;
 
 /**
+ * Succeeds immediately upon construction
+ *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
-public class SessionWorkBuffer extends ByteArrayBuffer implements SessionHolder<Session> {
-    private final Session session;
-
-    public SessionWorkBuffer(Session session) {
-        this.session = Objects.requireNonNull(session, "No session");
-    }
-
-    @Override
-    public Session getSession() {
-        return session;
-    }
-
-    @Override
-    public void clear(boolean wipeData) {
-        throw new UnsupportedOperationException("Not allowed to clear session work buffer of " + getSession());
-    }
-
-    public void forceClear(boolean wipeData) {
-        super.clear(wipeData);
+public class ImmediateWriteFuture extends IoWriteFutureImpl {
+    public ImmediateWriteFuture(Object id, Buffer buffer) {
+        super(id, buffer);
+        setValue(Boolean.TRUE);
     }
 }
diff --git a/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/session/EndlessTarpitSenderSupportDevelopment.java b/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/session/EndlessTarpitSenderSupportDevelopment.java
new file mode 100644
index 0000000..9298b3b
--- /dev/null
+++ b/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/session/EndlessTarpitSenderSupportDevelopment.java
@@ -0,0 +1,280 @@
+/*
+ * 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.contrib.server.session;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Base64.Encoder;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.io.IoSession;
+import org.apache.sshd.common.io.IoWriteFuture;
+import org.apache.sshd.common.kex.KexProposalOption;
+import org.apache.sshd.common.random.Random;
+import org.apache.sshd.common.session.ReservedSessionMessagesHandler;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.session.SessionListener;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
+import org.apache.sshd.contrib.common.io.EndlessWriteFuture;
+import org.apache.sshd.contrib.common.io.ImmediateWriteFuture;
+import org.apache.sshd.core.CoreModuleProperties;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.CoreTestSupportUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @see    <A HREF="https://nullprogram.com/blog/2019/03/22/">Endless tarpit</A>
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public final class EndlessTarpitSenderSupportDevelopment extends AbstractLoggingBean implements Runnable, SessionListener {
+    private static final Collection<EndlessTarpitSenderSupportDevelopment> THREADS = new LinkedList<>();
+
+    private final Random randomizer;
+    private final byte[] dataBuffer;
+    private final byte[] outputBuffer;
+    private AtomicLong numSent = new AtomicLong();
+    private final ServerSession session;
+    private final AtomicBoolean okToRun = new AtomicBoolean(true);
+
+    private EndlessTarpitSenderSupportDevelopment(ServerSession session, int lineLength) {
+        this.session = session;
+        this.dataBuffer = new byte[(lineLength * 4) / 6 /* BASE64 */];
+        this.outputBuffer = new byte[lineLength + 8 /* some padding */ + 2 /* CRLF */];
+        FactoryManager manager = session.getFactoryManager();
+        Factory<Random> randomFactory = manager.getRandomFactory();
+        this.randomizer = randomFactory.create();
+        this.session.addSessionListener(this);
+    }
+
+    @Override
+    public void sessionException(Session session, Throwable t) {
+        terminate("sessionException");
+    }
+
+    @Override
+    public void sessionDisconnect(
+            Session session, int reason, String msg, String language, boolean initiator) {
+        terminate("sessionDisconnect");
+    }
+
+    @Override
+    public void sessionClosed(Session session) {
+        terminate("sessionClosed");
+    }
+
+    private IoWriteFuture sendRandomLine() throws IOException {
+        randomizer.fill(dataBuffer);
+
+        Encoder encoder = Base64.getEncoder();
+        int len = encoder.encode(dataBuffer, outputBuffer);
+        outputBuffer[len] = (byte) '\r';
+        outputBuffer[len + 1] = (byte) '\n';
+
+        byte[] packet = Arrays.copyOf(outputBuffer, len + 2);
+        String line = new String(packet, 0, packet.length - 2, StandardCharsets.US_ASCII);
+        IoSession networkSession = session.getIoSession();
+        IoWriteFuture future = networkSession.writeBuffer(new ByteArrayBuffer(packet));
+        long count = numSent.incrementAndGet();
+        log.info("sendRandomLine({}) sent line #{}: {}", session, count, line);
+        return future;
+    }
+
+    @Override
+    public void run() {
+        try {
+            synchronized (THREADS) {
+                THREADS.add(this);
+            }
+
+            while (okToRun.get()) {
+                sendRandomLine();
+
+                synchronized (okToRun) {
+                    okToRun.wait(TimeUnit.SECONDS.toMillis(5L));
+                }
+            }
+        } catch (Exception e) {
+            log.error("run(" + session + ") failure", e);
+        } finally {
+            log.info("closing({})", session);
+            try {
+                session.close(true);
+            } finally {
+                session.removeSessionListener(this);
+
+                synchronized (THREADS) {
+                    THREADS.remove(this);
+                }
+            }
+        }
+    }
+
+    private void terminate(Object logHint) {
+        boolean terminated;
+        synchronized (okToRun) {
+            terminated = okToRun.getAndSet(false);
+            okToRun.notifyAll();
+        }
+
+        if (terminated) {
+            log.info("terminate({}) terminated {}", logHint, session);
+        }
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////
+
+    private static <F extends FactoryManager> F setupTimeouts(F manager) {
+        CoreModuleProperties.NIO2_READ_TIMEOUT.set(manager, Duration.ofMinutes(15L));
+        CoreModuleProperties.IDLE_TIMEOUT.set(manager, Duration.ZERO);
+        CoreModuleProperties.AUTH_TIMEOUT.set(manager, Duration.ZERO);
+        return manager;
+    }
+
+    private static void startServer(String address, int port) throws Exception {
+        try (SshServer server = CoreTestSupportUtils.setupTestServer(EndlessTarpitSenderSupportDevelopment.class);
+             BufferedReader stdin = new BufferedReader(
+                     new InputStreamReader(new NoCloseInputStream(System.in), Charset.defaultCharset()))) {
+            setupTimeouts(server);
+
+            if (GenericUtils.isNotEmpty(address)) {
+                server.setHost(address);
+            }
+            server.setPort(port);
+            server.setReservedSessionMessagesHandler(new ReservedSessionMessagesHandler() {
+                private final Logger log = LoggerFactory.getLogger(EndlessTarpitSenderSupportDevelopment.class);
+
+                @Override
+                @SuppressWarnings("synthetic-access")
+                public IoWriteFuture sendIdentification(Session session, String version, List<String> extraLines)
+                        throws Exception {
+                    EndlessTarpitSenderSupportDevelopment tarpit = new EndlessTarpitSenderSupportDevelopment(
+                            (ServerSession) session, 32);
+                    Thread thread = new Thread(tarpit, "t" + session.getIoSession().getRemoteAddress());
+                    thread.start();
+                    log.info("sendIdentification({})[{}] Started endless sender", session, version);
+                    return EndlessWriteFuture.INSTANCE;
+                }
+
+                @Override
+                public IoWriteFuture sendKexInitRequest(
+                        Session session, Map<KexProposalOption, String> proposal, Buffer packet)
+                        throws Exception {
+                    log.info("sendKexInitRequest({}) suppressed KEX sending", session);
+                    return new ImmediateWriteFuture(session, packet);
+                }
+
+            });
+            System.err.append("Starting SSHD on " + address + ":" + port);
+            server.start();
+
+            try {
+                while (true) {
+                    System.out.println("Running on port " + port + " (Q)uit: ");
+                    String line = stdin.readLine();
+                    line = GenericUtils.trimToEmpty(line);
+                    if ("q".equalsIgnoreCase(line) || "quit".equalsIgnoreCase(line)) {
+                        break;
+                    }
+                }
+            } finally {
+                System.err.append("Stopping server on port ").println(port);
+                server.stop();
+            }
+        } finally {
+            for (EndlessTarpitSenderSupportDevelopment t : THREADS) {
+                t.terminate("main");
+            }
+        }
+    }
+
+    private static void startClient(String host, int port) throws Exception {
+        try (SshClient client = CoreTestSupportUtils.setupTestClient(EndlessTarpitSenderSupportDevelopment.class)) {
+            setupTimeouts(client);
+
+            client.addSessionListener(new SessionListener() {
+                private final Logger log = LoggerFactory.getLogger(EndlessTarpitSenderSupportDevelopment.class);
+                private final AtomicInteger lastCount = new AtomicInteger();
+
+                @Override
+                public void sessionEstablished(Session session) {
+                    log.info("sessionEstablished({})", session);
+                }
+
+                @Override
+                public void sessionPeerIdentificationLine(
+                        Session session, String line, List<String> extraLines) {
+                    if (lastCount.get() < GenericUtils.size(extraLines)) {
+                        int num = lastCount.incrementAndGet();
+                        log.info("sessionPeerIdentificationLine({})[{}] {}", session, num, line);
+                    }
+                }
+
+            });
+
+            client.start();
+            Duration waitTime = Duration.ofMinutes(15L);
+            try (ClientSession session = client.connect(host, host, port)
+                    .verify(waitTime)
+                    .getSession()) {
+                session.addPasswordIdentity(host);
+                session.auth().verify(waitTime);
+            } finally {
+                client.stop();
+            }
+        }
+    }
+
+    // optional args[0]=client/server - default=server, optional args[1]=port (default 22), optional args[2]=listen/connect address (default=localhost)
+    public static void main(String[] args) throws Exception {
+        int numArgs = GenericUtils.length(args);
+        String mode = (numArgs > 0) ? args[0] : "server";
+        int port = (numArgs > 1) ? Integer.parseInt(args[1]) : SshConstants.DEFAULT_PORT;
+        if ("server".equalsIgnoreCase(mode)) {
+            startServer((numArgs > 2) ? args[2] : null, port);
+        } else {
+            startClient((numArgs > 2) ? args[2] : BaseTestSupport.TEST_LOCALHOST, port);
+        }
+    }
+}
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 3bab23a..b81ac20 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
@@ -556,7 +556,7 @@ public abstract class AbstractClientSession extends AbstractSession implements C
     }
 
     @Override
-    protected byte[] sendKexInit(Map<KexProposalOption, String> proposal) throws IOException {
+    protected byte[] sendKexInit(Map<KexProposalOption, String> proposal) throws Exception {
         mergeProposals(clientProposal, proposal);
         return super.sendKexInit(proposal);
     }
@@ -683,10 +683,13 @@ public abstract class AbstractClientSession extends AbstractSession implements C
                 proposal.put(KexProposalOption.C2SENC, BuiltinCiphers.Constants.NONE);
                 proposal.put(KexProposalOption.S2CENC, BuiltinCiphers.Constants.NONE);
 
-                byte[] seed;
-                synchronized (kexState) {
-                    seed = sendKexInit(proposal);
-                    setKexSeed(seed);
+                try {
+                    synchronized (kexState) {
+                        byte[] seed = sendKexInit(proposal);
+                        setKexSeed(seed);
+                    }
+                } catch (Exception e) {
+                    GenericUtils.rethrowAsIoException(e);
                 }
             }
 
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
index 72c41ae..8367583 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
@@ -225,7 +225,7 @@ public class ClientSessionImpl extends AbstractClientSession {
     }
 
     @Override
-    protected void signalSessionEvent(SessionListener.Event event) throws IOException {
+    protected void signalSessionEvent(SessionListener.Event event) throws Exception {
         if (SessionListener.Event.KeyEstablished.equals(event)) {
             sendInitialServiceRequest();
         }
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
index ae039f8..745921a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/kex/extension/KexExtensionHandler.java
@@ -80,16 +80,16 @@ public interface KexExtensionHandler {
      * method is called during the negotiation phase even if {@code isKexExtensionsAvailable} returns {@code false} for
      * the session.
      *
-     * @param  session     The {@link Session} initiating or receiving the proposal
-     * @param  initiator   {@code true} if the proposal is about to be sent, {@code false} if this is a proposal
-     *                     received from the peer.
-     * @param  proposal    The proposal contents - <B>Caveat emptor:</B> the proposal is <U>modifiable</U> i.e., the
-     *                     handler can modify it before being sent or before being processed (if incoming)
-     * @throws IOException If failed to handle the request
+     * @param  session   The {@link Session} initiating or receiving the proposal
+     * @param  initiator {@code true} if the proposal is about to be sent, {@code false} if this is a proposal received
+     *                   from the peer.
+     * @param  proposal  The proposal contents - <B>Caveat emptor:</B> the proposal is <U>modifiable</U> i.e., the
+     *                   handler can modify it before being sent or before being processed (if incoming)
+     * @throws Exception If failed to handle the request
      */
     default void handleKexInitProposal(
             Session session, boolean initiator, Map<KexProposalOption, String> proposal)
-            throws IOException {
+            throws Exception {
         // ignored
     }
 
@@ -98,20 +98,20 @@ public interface KexExtensionHandler {
      * called during the negotiation phase even if {@code isKexExtensionsAvailable} returns {@code false} for the
      * session.
      *
-     * @param  session     The {@link Session} executing the negotiation
-     * @param  option      The negotiated {@link KexProposalOption}
-     * @param  nValue      The negotiated option value (may be {@code null}/empty).
-     * @param  c2sOptions  The client proposals
-     * @param  cValue      The client-side value for the option (may be {@code null}/empty).
-     * @param  s2cOptions  The server proposals
-     * @param  sValue      The server-side value for the option (may be {@code null}/empty).
-     * @throws IOException If failed to handle the invocation
+     * @param  session    The {@link Session} executing the negotiation
+     * @param  option     The negotiated {@link KexProposalOption}
+     * @param  nValue     The negotiated option value (may be {@code null}/empty).
+     * @param  c2sOptions The client proposals
+     * @param  cValue     The client-side value for the option (may be {@code null}/empty).
+     * @param  s2cOptions The server proposals
+     * @param  sValue     The server-side value for the option (may be {@code null}/empty).
+     * @throws Exception  If failed to handle the invocation
      */
     default void handleKexExtensionNegotiation(
             Session session, KexProposalOption option, String nValue,
             Map<KexProposalOption, String> c2sOptions, String cValue,
             Map<KexProposalOption, String> s2cOptions, String sValue)
-            throws IOException {
+            throws Exception {
         // do nothing
     }
 
@@ -131,12 +131,12 @@ public interface KexExtensionHandler {
      * Invoked in order to allow the handler to send an {@code SSH_MSG_EXT_INFO} message. <B>Note:</B> this method is
      * called only if {@code isKexExtensionsAvailable} returns {@code true} for the session.
      *
-     * @param  session     The {@link Session}
-     * @param  phase       The phase at which the handler is invoked
-     * @throws IOException If failed to handle the invocation
-     * @see                <A HREF="https://tools.ietf.org/html/rfc8308#section-2.4">RFC-8308 - section 2.4</A>
+     * @param  session   The {@link Session}
+     * @param  phase     The phase at which the handler is invoked
+     * @throws Exception If failed to handle the invocation
+     * @see              <A HREF="https://tools.ietf.org/html/rfc8308#section-2.4">RFC-8308 - section 2.4</A>
      */
-    default void sendKexExtensions(Session session, KexPhase phase) throws IOException {
+    default void sendKexExtensions(Session session, KexPhase phase) throws Exception {
         // do nothing
     }
 
@@ -144,15 +144,15 @@ public interface KexExtensionHandler {
      * Parses the {@code SSH_MSG_EXT_INFO} message. <B>Note:</B> this method is called regardless of whether
      * {@code isKexExtensionsAvailable} returns {@code true} for the session.
      *
-     * @param  session     The {@link Session} through which the message was received
-     * @param  buffer      The message buffer
-     * @return             {@code true} if message handled - if {@code false} then {@code SSH_MSG_UNIMPLEMENTED} will be
-     *                     generated
-     * @throws IOException If failed to handle the message
-     * @see                <A HREF="https://tools.ietf.org/html/rfc8308#section-2.3">RFC-8308 - section 2.3</A>
-     * @see                #handleKexExtensionRequest(Session, int, int, String, byte[])
+     * @param  session   The {@link Session} through which the message was received
+     * @param  buffer    The message buffer
+     * @return           {@code true} if message handled - if {@code false} then {@code SSH_MSG_UNIMPLEMENTED} will be
+     *                   generated
+     * @throws Exception If failed to handle the message
+     * @see              <A HREF="https://tools.ietf.org/html/rfc8308#section-2.3">RFC-8308 - section 2.3</A>
+     * @see              #handleKexExtensionRequest(Session, int, int, String, byte[])
      */
-    default boolean handleKexExtensionsMessage(Session session, Buffer buffer) throws IOException {
+    default boolean handleKexExtensionsMessage(Session session, Buffer buffer) throws Exception {
         int count = buffer.getInt();
         for (int index = 0; index < count; index++) {
             String name = buffer.getString();
@@ -169,31 +169,31 @@ public interface KexExtensionHandler {
      * Parses the {@code SSH_MSG_NEWCOMPRESS} message. <B>Note:</B> this method is called regardless of whether
      * {@code isKexExtensionsAvailable} returns {@code true} for the session.
      *
-     * @param  session     The {@link Session} through which the message was received
-     * @param  buffer      The message buffer
-     * @return             {@code true} if message handled - if {@code false} then {@code SSH_MSG_UNIMPLEMENTED} will be
-     *                     generated
-     * @throws IOException If failed to handle the message
-     * @see                <A HREF="https://tools.ietf.org/html/rfc8308#section-3.2">RFC-8308 - section 3.2</A>
+     * @param  session   The {@link Session} through which the message was received
+     * @param  buffer    The message buffer
+     * @return           {@code true} if message handled - if {@code false} then {@code SSH_MSG_UNIMPLEMENTED} will be
+     *                   generated
+     * @throws Exception If failed to handle the message
+     * @see              <A HREF="https://tools.ietf.org/html/rfc8308#section-3.2">RFC-8308 - section 3.2</A>
      */
-    default boolean handleKexCompressionMessage(Session session, Buffer buffer) throws IOException {
+    default boolean handleKexCompressionMessage(Session session, Buffer buffer) throws Exception {
         return true;
     }
 
     /**
      * Invoked by {@link #handleKexExtensionsMessage(Session, Buffer)} in order to handle a specific extension.
      *
-     * @param  session     The {@link Session} through which the message was received
-     * @param  index       The 0-based extension index
-     * @param  count       The total extensions in the message
-     * @param  name        The extension name
-     * @param  data        The extension data
-     * @return             {@code true} whether to proceed to the next extension or stop processing the rest
-     * @throws IOException If failed to handle the extension
+     * @param  session   The {@link Session} through which the message was received
+     * @param  index     The 0-based extension index
+     * @param  count     The total extensions in the message
+     * @param  name      The extension name
+     * @param  data      The extension data
+     * @return           {@code true} whether to proceed to the next extension or stop processing the rest
+     * @throws Exception If failed to handle the extension
      */
     default boolean handleKexExtensionRequest(
             Session session, int index, int count, String name, byte[] data)
-            throws IOException {
+            throws Exception {
         return true;
     }
 }
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/ReservedSessionMessagesHandler.java b/sshd-core/src/main/java/org/apache/sshd/common/session/ReservedSessionMessagesHandler.java
index ecc49f1..496f137 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/ReservedSessionMessagesHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/ReservedSessionMessagesHandler.java
@@ -20,8 +20,10 @@
 package org.apache.sshd.common.session;
 
 import java.util.List;
+import java.util.Map;
 
 import org.apache.sshd.common.io.IoWriteFuture;
+import org.apache.sshd.common.kex.KexProposalOption;
 import org.apache.sshd.common.util.SshdEventListener;
 import org.apache.sshd.common.util.buffer.Buffer;
 
@@ -45,7 +47,7 @@ public interface ReservedSessionMessagesHandler extends SshdEventListener {
      * @return            A {@link IoWriteFuture} that can be used to wait for the data to be sent successfully. If
      *                    {@code null} then the session will send the identification, otherwise it is assumed that the
      *                    handler has sent it.
-     * @throws Exception
+     * @throws Exception  if failed to handle the callback
      * @see               <A HREF="https://tools.ietf.org/html/rfc4253#section-4.2">RFC 4253 - section 4.2 - Protocol
      *                    Version Exchange</A>
      */
@@ -56,6 +58,23 @@ public interface ReservedSessionMessagesHandler extends SshdEventListener {
     }
 
     /**
+     * Invoked before sending the {@code SSH_MSG_KEXINIT} packet
+     *
+     * @param  session   The {@code Session} through which the key exchange is being managed
+     * @param  proposal  The KEX proposal that was used to build the packet
+     * @param  packet    The packet containing the fully encoded message - <B>Caveat:</B> this packet later serves as
+     *                   part of the key generation, so care must be taken if manipulating it.
+     * @return           A non-{@code null} {@link IoWriteFuture} to signal that handler took care of the KEX packet
+     *                   delivery.
+     * @throws Exception if failed to handle the callback
+     */
+    default IoWriteFuture sendKexInitRequest(
+            Session session, Map<KexProposalOption, String> proposal, Buffer packet)
+            throws Exception {
+        return null;
+    }
+
+    /**
      * Invoked when an {@code SSH_MSG_IGNORE} packet is received
      *
      * @param  session   The {@code Session} through which the message was received
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/SessionWorkBuffer.java b/sshd-core/src/main/java/org/apache/sshd/common/session/SessionWorkBuffer.java
index 00815a2..5abca14 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/SessionWorkBuffer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/SessionWorkBuffer.java
@@ -21,6 +21,7 @@ package org.apache.sshd.common.session;
 
 import java.util.Objects;
 
+import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 
 /**
@@ -39,7 +40,7 @@ public class SessionWorkBuffer extends ByteArrayBuffer implements SessionHolder<
     }
 
     @Override
-    public void clear(boolean wipeData) {
+    public Buffer clear(boolean wipeData) {
         throw new UnsupportedOperationException("Not allowed to clear session work buffer of " + getSession());
     }
 
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 13bb2f9..6a2b146 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
@@ -376,7 +376,7 @@ public abstract class AbstractSession extends SessionHelper {
     public void messageReceived(Readable buffer) throws Exception {
         synchronized (decodeLock) {
             decoderBuffer.putBuffer(buffer);
-            // One of those property will be set by the constructor and the other
+            // One of those properties will be set by the constructor and the other
             // one should be set by the readIdentification method
             if ((clientVersion == null) || (serverVersion == null)) {
                 if (readIdentification(decoderBuffer)) {
@@ -568,10 +568,10 @@ public abstract class AbstractSession extends SessionHelper {
     /**
      * Send a message to put new keys into use.
      *
-     * @return             An {@link IoWriteFuture} that can be used to wait and check the result of sending the packet
-     * @throws IOException if an error occurs sending the message
+     * @return           An {@link IoWriteFuture} that can be used to wait and check the result of sending the packet
+     * @throws Exception if an error occurs sending the message
      */
-    protected IoWriteFuture sendNewKeys() throws IOException {
+    protected IoWriteFuture sendNewKeys() throws Exception {
         if (log.isDebugEnabled()) {
             log.debug("sendNewKeys({}) Send SSH_MSG_NEWKEYS", this);
         }
@@ -897,6 +897,8 @@ public abstract class AbstractSession extends SessionHelper {
                                 "Failed (" + e.getClass().getSimpleName() + ")"
                                               + " to check re-key necessity: " + e.getMessage()),
                         e);
+            } catch (Exception e) {
+                GenericUtils.rethrowAsIoException(e);
             }
         }
     }
@@ -1507,14 +1509,16 @@ public abstract class AbstractSession extends SessionHelper {
     /**
      * Send the key exchange initialization packet. This packet contains random data along with our proposal.
      *
-     * @param  proposal    our proposal for key exchange negotiation
-     * @return             the sent packet data which must be kept for later use when deriving the session keys
-     * @throws IOException if an error occurred sending the packet
+     * @param  proposal  our proposal for key exchange negotiation
+     * @return           the sent packet data which must be kept for later use when deriving the session keys
+     * @throws Exception if an error occurred sending the packet
      */
-    protected byte[] sendKexInit(Map<KexProposalOption, String> proposal) throws IOException {
-        if (log.isDebugEnabled()) {
+    protected byte[] sendKexInit(Map<KexProposalOption, String> proposal) throws Exception {
+        boolean debugEnabled = log.isDebugEnabled();
+        if (debugEnabled) {
             log.debug("sendKexInit({}) Send SSH_MSG_KEXINIT", this);
         }
+
         Buffer buffer = createBuffer(SshConstants.SSH_MSG_KEXINIT);
         int p = buffer.wpos();
         buffer.wpos(p + SshConstants.MSG_KEX_COOKIE_SIZE);
@@ -1538,20 +1542,30 @@ public abstract class AbstractSession extends SessionHelper {
 
         buffer.putBoolean(false); // first kex packet follows
         buffer.putInt(0); // reserved (FFU)
+
+        ReservedSessionMessagesHandler handler = getReservedSessionMessagesHandler();
+        IoWriteFuture future = (handler == null) ? null : handler.sendKexInitRequest(this, proposal, buffer);
         byte[] data = buffer.getCompactData();
-        writePacket(buffer);
+        if (future == null) {
+            future = writePacket(buffer);
+        } else {
+            if (debugEnabled) {
+                log.debug("sendKexInit({}) KEX handled by reserved messages handler", this);
+            }
+        }
+
         return data;
     }
 
     /**
      * Receive the remote key exchange init message. The packet data is returned for later use.
      *
-     * @param  buffer      the {@link Buffer} containing the key exchange init packet
-     * @param  proposal    the remote proposal to fill
-     * @return             the packet data
-     * @throws IOException If failed to handle the message
+     * @param  buffer    the {@link Buffer} containing the key exchange init packet
+     * @param  proposal  the remote proposal to fill
+     * @return           the packet data
+     * @throws Exception If failed to handle the message
      */
-    protected byte[] receiveKexInit(Buffer buffer, Map<KexProposalOption, String> proposal) throws IOException {
+    protected byte[] receiveKexInit(Buffer buffer, Map<KexProposalOption, String> proposal) throws Exception {
         // Recreate the packet payload which will be needed at a later time
         byte[] d = buffer.array();
         byte[] data = new byte[buffer.available() + 1 /* the opcode */];
@@ -1791,10 +1805,10 @@ public abstract class AbstractSession extends SessionHelper {
      * Compute the negotiated proposals by merging the client and server proposal. The negotiated proposal will also be
      * stored in the {@link #negotiationResult} property.
      *
-     * @return             The negotiated options {@link Map}
-     * @throws IOException If negotiation failed
+     * @return           The negotiated options {@link Map}
+     * @throws Exception If negotiation failed
      */
-    protected Map<KexProposalOption, String> negotiate() throws IOException {
+    protected Map<KexProposalOption, String> negotiate() throws Exception {
         Map<KexProposalOption, String> c2sOptions = getClientKexProposals();
         Map<KexProposalOption, String> s2cOptions = getServerKexProposals();
         signalNegotiationStart(c2sOptions, s2cOptions);
@@ -2104,6 +2118,9 @@ public abstract class AbstractSession extends SessionHelper {
                             "Failed (" + e.getClass().getSimpleName() + ")"
                                           + " to generate keys for exchange: " + e.getMessage()),
                     e);
+        } catch (Exception e) {
+            GenericUtils.rethrowAsIoException(e);
+            return null;    // actually dead code
         }
 
         return ValidateUtils.checkNotNull(
@@ -2113,26 +2130,24 @@ public abstract class AbstractSession extends SessionHelper {
     /**
      * Checks if a re-keying is required and if so initiates it
      *
-     * @return                          A {@link KeyExchangeFuture} to wait for the initiated exchange or {@code null}
-     *                                  if no need to re-key or an exchange is already in progress
-     * @throws IOException              If failed load the keys or send the request
-     * @throws GeneralSecurityException If failed to generate the necessary keys
-     * @see                             #isRekeyRequired()
-     * @see                             #requestNewKeysExchange()
+     * @return           A {@link KeyExchangeFuture} to wait for the initiated exchange or {@code null} if no need to
+     *                   re-key or an exchange is already in progress
+     * @throws Exception If failed load/generate the keys or send the request
+     * @see              #isRekeyRequired()
+     * @see              #requestNewKeysExchange()
      */
-    protected KeyExchangeFuture checkRekey() throws IOException, GeneralSecurityException {
+    protected KeyExchangeFuture checkRekey() throws Exception {
         return isRekeyRequired() ? requestNewKeysExchange() : null;
     }
 
     /**
      * Initiates a new keys exchange if one not already in progress
      *
-     * @return                          A {@link KeyExchangeFuture} to wait for the initiated exchange or {@code null}
-     *                                  if an exchange is already in progress
-     * @throws IOException              If failed to load the keys or send the request
-     * @throws GeneralSecurityException If failed to generate the keys
+     * @return           A {@link KeyExchangeFuture} to wait for the initiated exchange or {@code null} if an exchange
+     *                   is already in progress
+     * @throws Exception If failed to load/generate the keys or send the request
      */
-    protected KeyExchangeFuture requestNewKeysExchange() throws IOException, GeneralSecurityException {
+    protected KeyExchangeFuture requestNewKeysExchange() throws Exception {
         if (!kexState.compareAndSet(KexState.DONE, KexState.INIT)) {
             if (log.isDebugEnabled()) {
                 log.debug("requestNewKeysExchange({}) KEX state not DONE: {}", this, kexState);
@@ -2259,7 +2274,7 @@ public abstract class AbstractSession extends SessionHelper {
         }
     }
 
-    protected byte[] sendKexInit() throws IOException, GeneralSecurityException {
+    protected byte[] sendKexInit() throws Exception {
         String resolvedAlgorithms = resolveAvailableSignaturesProposal();
         if (GenericUtils.isEmpty(resolvedAlgorithms)) {
             throw new SshException(
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 a5d7542..4de89bf 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
@@ -214,7 +214,11 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
     @Override
     public void setAuthenticated() throws IOException {
         this.authed = true;
-        signalSessionEvent(SessionListener.Event.Authenticated);
+        try {
+            signalSessionEvent(SessionListener.Event.Authenticated);
+        } catch (Exception e) {
+            GenericUtils.rethrowAsIoException(e);
+        }
     }
 
     /**
@@ -691,10 +695,10 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
     /**
      * Sends a session event to all currently registered session listeners
      *
-     * @param  event       The event to send
-     * @throws IOException If any of the registered listeners threw an exception.
+     * @param  event     The event to send
+     * @throws Exception If any of the registered listeners threw an exception.
      */
-    protected void signalSessionEvent(SessionListener.Event event) throws IOException {
+    protected void signalSessionEvent(SessionListener.Event event) throws Exception {
         try {
             invokeSessionSignaller(l -> {
                 signalSessionEvent(l, event);
@@ -704,13 +708,10 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
             Throwable t = GenericUtils.peelException(err);
             debug("sendSessionEvent({})[{}] failed ({}) to inform listeners: {}",
                     this, event, t.getClass().getSimpleName(), t.getMessage(), t);
-            if (t instanceof IOException) {
-                throw (IOException) t;
-            } else if (t instanceof RuntimeException) {
-                throw (RuntimeException) t;
+            if (t instanceof Exception) {
+                throw (Exception) t;
             } else {
-                throw new IOException(
-                        "Failed (" + t.getClass().getSimpleName() + ") to send session event: " + t.getMessage(), t);
+                throw new RuntimeSshException(t);
             }
         }
     }
@@ -1279,6 +1280,7 @@ public abstract class SessionHelper extends AbstractKexFactoryManager implements
             Throwable e = GenericUtils.peelException(err);
             debug("signalSessionClosed({}) {} while signal session closed: {}",
                     this, e.getClass().getSimpleName(), e.getMessage(), e);
+            // Do not re-throw since session closed anyway
         }
     }
 
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 b388a6c..5cff5f9 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
@@ -354,7 +354,7 @@ public abstract class AbstractServerSession extends AbstractSession implements S
     }
 
     @Override
-    protected byte[] sendKexInit(Map<KexProposalOption, String> proposal) throws IOException {
+    protected byte[] sendKexInit(Map<KexProposalOption, String> proposal) throws Exception {
         mergeProposals(serverProposal, proposal);
         return super.sendKexInit(proposal);
     }
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java
index 0b4a588..ea99c9b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java
@@ -39,7 +39,8 @@ public class ServerSessionImpl extends AbstractServerSession {
 
         String headerConfig = CoreModuleProperties.SERVER_EXTRA_IDENTIFICATION_LINES.getOrNull(this);
         String[] headers = GenericUtils.split(headerConfig, CoreModuleProperties.SERVER_EXTRA_IDENT_LINES_SEPARATOR);
-        // We intentionally create a modifiable array so as to allow users to modify it via SessionListener or ReservedSessionMessagesHandler
+        // We intentionally create a modifiable array so as to allow users to
+        // modify it via SessionListener or ReservedSessionMessagesHandler
         List<String> extraLines = GenericUtils.isEmpty(headers)
                 ? new ArrayList<>()
                 : new ArrayList<>(Arrays.asList(headers));
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/kex/extension/KexExtensionHandlerTest.java b/sshd-core/src/test/java/org/apache/sshd/common/kex/extension/KexExtensionHandlerTest.java
index d38c817..307cccc 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/kex/extension/KexExtensionHandlerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/kex/extension/KexExtensionHandlerTest.java
@@ -53,7 +53,7 @@ public class KexExtensionHandlerTest extends JUnitTestSupport {
     }
 
     @Test
-    public void testEncodeDecodeExtensionMessage() throws IOException {
+    public void testEncodeDecodeExtensionMessage() throws Exception {
         List<Map.Entry<String, ?>> expected = Arrays.asList(
                 new SimpleImmutableEntry<>(
                         DelayCompression.NAME,
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/CoreTestSupportUtils.java b/sshd-core/src/test/java/org/apache/sshd/util/test/CoreTestSupportUtils.java
index 6e52784..686d2de 100644
--- a/sshd-core/src/test/java/org/apache/sshd/util/test/CoreTestSupportUtils.java
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/CoreTestSupportUtils.java
@@ -56,7 +56,10 @@ public final class CoreTestSupportUtils {
     }
 
     public static SshClient setupTestClient(Class<?> anchor) {
-        SshClient client = SshClient.setUpDefaultClient();
+        return setupTestClient(SshClient.setUpDefaultClient(), anchor);
+    }
+
+    public static <C extends SshClient> C setupTestClient(C client, Class<?> anchor) {
         client.setServerKeyVerifier(AcceptAllServerKeyVerifier.INSTANCE);
         client.setHostConfigEntryResolver(HostConfigEntryResolver.EMPTY);
         client.setKeyIdentityProvider(KeyIdentityProvider.EMPTY_KEYS_PROVIDER);
@@ -77,7 +80,10 @@ public final class CoreTestSupportUtils {
     }
 
     public static SshServer setupTestServer(Class<?> anchor) {
-        SshServer sshd = SshServer.setUpDefaultServer();
+        return setupTestServer(SshServer.setUpDefaultServer(), anchor);
+    }
+
+    public static <S extends SshServer> S setupTestServer(S sshd, Class<?> anchor) {
         sshd.setKeyPairProvider(CommonTestSupportUtils.createTestHostKeyProvider(anchor));
         sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
         sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
diff --git a/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java b/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java
index 7b8e474..54b865f 100644
--- a/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java
+++ b/sshd-scp/src/main/java/org/apache/sshd/scp/client/SimpleScpClientImpl.java
@@ -109,7 +109,8 @@ public class SimpleScpClientImpl extends AbstractLoggingBean implements SimpleSc
                 e.addSuppressed(t);
             }
 
-            throw GenericUtils.toIOException(e);
+            GenericUtils.rethrowAsIoException(e);
+            return null;    // actually dead code...
         }
     }