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 38ed37f910d5dba2f8a7c159e2e5fbe1f4b86fa8
Author: Lyor Goldstein <lgoldst...@apache.org>
AuthorDate: Thu Oct 3 11:47:56 2019 +0300

    [SSHD-947] Use separate configuration to control sending client session 
identity and KEX-INIT message
---
 CHANGES.md                                         |  3 +++
 docs/client-setup.md                               | 22 ++++++++++++++++++
 .../apache/sshd/client/ClientFactoryManager.java   | 13 ++++++++++-
 .../sshd/client/session/AbstractClientSession.java | 26 +++++++++++++++-------
 .../sshd/client/session/ClientSessionImpl.java     |  8 +++++--
 .../common/session/helpers/AbstractSession.java    |  6 +++--
 .../sshd/common/session/helpers/SessionHelper.java |  5 +++--
 7 files changed, 68 insertions(+), 15 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index fdaae65..7fa2f29 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -70,3 +70,6 @@ exchange via properties.
 
 * [SSHD-943](https://issues.apache.org/jira/browse/SSHD-943) - Provide session 
instance when KEX factory is invoked in order to create a KeyExchange instance.
 
+* [SSHD-947](https://issues.apache.org/jira/browse/SSHD-947) - Added 
configuration allowing the user to specify whether client should wait
+for the server's identification before sending KEX-INIT message.
+
diff --git a/docs/client-setup.md b/docs/client-setup.md
index ce6453e..1c8d36a 100644
--- a/docs/client-setup.md
+++ b/docs/client-setup.md
@@ -164,6 +164,28 @@ this can be modified so that the client waits for the 
server's identification be
     client.start();
 ```
 
+A similar configuration can be applied to sending the initial 
`SSH_MSG_KEXINIT` message - i.e., the client can be configured
+to wait until the server's identification is received before sending the 
message. This is done in order to allow clients to
+customize the KEX phase according to the parsed server identification.
+
+```java
+    SshClient client = ...setup client...
+    PropertyResolverUtils.updateProperty(
+       client, ClientFactoryManager.SEND_IMMEDIATE_KEXINIT, false);
+    client.start();
+```
+
+**Note:** if immediate sending of the client's identification is disabled, 
`SSH_MSG_KEXINIT` message sending is also
+automatically delayed until after the server's identification is received.
+
+A viable configuration might be to send the client's identification 
immediately, but delay the client's `SSH_MSG_KEXINIT`
+message sending until the server's identification is received so that the 
client can customize the session based on the
+server's identity. This is a more likely configuration then delaying the 
client's own identification in order to be able
+to cope with port multiplexors such as 
[sslh](http://www.rutschle.net/tech/sslh/README.html). Such multiplexors usually
+require that the client send an initial packet immediately after connection is 
established so that they can analyze it
+and route it to the correct server (_ssh_ in this case). If we delay the 
client's identification, then obviously no server
+identification will ever be received since the multiplexor does not know how 
to route the connection.
+
 ## Keeping the session alive while no traffic
 
 The client-side implementation has a 2 builtin mechanisms for maintaining the 
session alive as far as the **server** is concerned
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java 
b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
index 42197f7..5c8694b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
@@ -49,7 +49,7 @@ public interface ClientFactoryManager
 
     /**
      * Whether to send the identification string immediately upon session 
connection
-     * being established or wait for the peer's identification before sending 
our own.
+     * being established or wait for the server's identification before 
sending our own.
      *
      * @see <A HREF="https://tools.ietf.org/html/rfc4253#section-4.2";>RFC 4253 
- section 4.2 - Protocol Version Exchange</A>
      */
@@ -61,6 +61,17 @@ public interface ClientFactoryManager
     boolean DEFAULT_SEND_IMMEDIATE_IDENTIFICATION = true;
 
     /**
+     * Whether to send {@code SSH_MSG_KEXINIT} immediately after sending
+     * the client identification string or wait until the severer's one
+     * has been received.
+     *
+     * @see #SEND_IMMEDIATE_IDENTIFICATION
+     */
+    String SEND_IMMEDIATE_KEXINIT = "send-immediate-kex-init";
+
+    boolean DEFAULT_SEND_KEXINIT = true;
+
+    /**
      * Key used to set the heartbeat interval in milliseconds (0 to disable = 
default)
      */
     String HEARTBEAT_INTERVAL = "heartbeat-interval";
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 91bac20..285d2a1 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
@@ -78,7 +78,8 @@ import org.apache.sshd.common.util.net.SshdSocketAddress;
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
 public abstract class AbstractClientSession extends AbstractSession implements 
ClientSession {
-    protected final boolean sendImmediateIdentification;
+    protected final boolean sendImmediateClientIdentification;
+    protected final boolean sendImmediateKexInit;
 
     private final List<Object> identities = new CopyOnWriteArrayList<>();
     private final AuthenticationIdentitiesProvider identitiesProvider;
@@ -94,12 +95,16 @@ public abstract class AbstractClientSession extends 
AbstractSession implements C
 
     protected AbstractClientSession(ClientFactoryManager factoryManager, 
IoSession ioSession) {
         super(false, factoryManager, ioSession);
-        this.sendImmediateIdentification = 
PropertyResolverUtils.getBooleanProperty(
+
+        sendImmediateClientIdentification = 
PropertyResolverUtils.getBooleanProperty(
             factoryManager, ClientFactoryManager.SEND_IMMEDIATE_IDENTIFICATION,
             ClientFactoryManager.DEFAULT_SEND_IMMEDIATE_IDENTIFICATION);
+        sendImmediateKexInit = PropertyResolverUtils.getBooleanProperty(
+                factoryManager, ClientFactoryManager.SEND_IMMEDIATE_KEXINIT,
+                ClientFactoryManager.DEFAULT_SEND_KEXINIT);
 
         identitiesProvider = 
AuthenticationIdentitiesProvider.wrapIdentities(identities);
-        this.connectionContext = (AttributeRepository) 
ioSession.getAttribute(AttributeRepository.class);
+        connectionContext = (AttributeRepository) 
ioSession.getAttribute(AttributeRepository.class);
     }
 
     @Override
@@ -248,9 +253,7 @@ public abstract class AbstractClientSession extends 
AbstractSession implements C
         }
     }
 
-    protected void initializeKexPhase() throws Exception {
-        sendClientIdentification();
-
+    protected void initializeKeyExchangePhase() throws Exception {
         KexExtensionHandler extHandler = getKexExtensionHandler();
         if ((extHandler == null) || 
(!extHandler.isKexExtensionsAvailable(this, AvailabilityPhase.PREKEX))) {
             kexState.set(KexState.INIT);
@@ -464,8 +467,15 @@ public abstract class AbstractClientSession extends 
AbstractSession implements C
         }
 
         signalExtraServerVersionInfo(serverVersion, ident);
-        if (!sendImmediateIdentification) {
-            initializeKexPhase();
+
+        // Now that we have the server's identity reported see if have delayed 
any of out duties...
+        if (!sendImmediateClientIdentification) {
+            sendClientIdentification();
+            // if client identification not sent then KEX-INIT was not sent 
either
+            initializeKeyExchangePhase();
+        } else if (!sendImmediateKexInit) {
+            // if client identification sent, perhaps we delayed KEX-INIT
+            initializeKeyExchangePhase();
         }
 
         return true;
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 7abc2ea..84bb1b8 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
@@ -97,8 +97,12 @@ public class ClientSessionImpl extends AbstractClientSession 
{
          */
         initializeProxyConnector();
 
-        if (sendImmediateIdentification) {
-            initializeKexPhase();
+        if (sendImmediateClientIdentification) {
+            sendClientIdentification();
+
+            if (sendImmediateKexInit) {
+                initializeKeyExchangePhase();
+            }
         }
     }
 
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 4bb43e2..5144e95 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
@@ -220,7 +220,8 @@ public abstract class AbstractSession extends SessionHelper 
{
 
         attachSession(ioSession, this);
 
-        Factory<Random> factory = 
ValidateUtils.checkNotNull(factoryManager.getRandomFactory(), "No random 
factory for %s", ioSession);
+        Factory<Random> factory =
+            ValidateUtils.checkNotNull(factoryManager.getRandomFactory(), "No 
random factory for %s", ioSession);
         random = ValidateUtils.checkNotNull(factory.create(), "No randomizer 
instance for %s", ioSession);
 
         refreshConfiguration();
@@ -2112,7 +2113,8 @@ public abstract class AbstractSession extends 
SessionHelper {
      * @param session The SSH session to attach
      * @throws MultipleAttachedSessionException If a previous session already 
attached
      */
-    public static void attachSession(IoSession ioSession, AbstractSession 
session) throws MultipleAttachedSessionException {
+    public static void attachSession(IoSession ioSession, AbstractSession 
session)
+            throws MultipleAttachedSessionException {
         Objects.requireNonNull(ioSession, "No I/O session");
         Objects.requireNonNull(session, "No SSH session");
         Object prev = ioSession.setAttributeIfAbsent(SESSION, session);
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 f58ed6e..55324a5 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
@@ -735,12 +735,13 @@ public abstract class SessionHelper extends 
AbstractKexFactoryManager implements
      * @throws IOException If failed to send the packet
      */
     protected IoWriteFuture sendIdentification(String ident) throws 
IOException {
-        byte[] data = (ident + "\r\n").getBytes(StandardCharsets.UTF_8);
         if (log.isDebugEnabled()) {
-            log.debug("sendIdentification({}): {}", this, ident.replace('\r', 
'|').replace('\n', '|'));
+            log.debug("sendIdentification({}): {}",
+                this, ident.replace('\r', '|').replace('\n', '|'));
         }
 
         IoSession networkSession = getIoSession();
+        byte[] data = (ident + "\r\n").getBytes(StandardCharsets.UTF_8);
         return networkSession.writePacket(new ByteArrayBuffer(data));
     }
 

Reply via email to