[SSHD-1097] Added capability to send peer identification via ReservedSessionMessagesHandler
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 @@
         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 @@
     }
 
     @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 @@
     /**
      * 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 @@
     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 @@
 
         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));