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)); }