Skip to content

Commit 58f0f00

Browse files
author
Lyor Goldstein
committed
[apacheGH-445] Added StrictKexTest
1 parent 1c11e3a commit 58f0f00

File tree

13 files changed

+603
-21
lines changed

13 files changed

+603
-21
lines changed

CHANGES.md

+11
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,18 @@ Provide (read-only) public access to internal session state values related to KE
5656
* *getSessionKexDetails*
5757
* *getSessionCountersDetails*
5858

59+
### Added `SessionListener#sessionPeerIdentificationSent` callback
60+
61+
Invoked after successful or failed attempt to send client/server identification to peer. The callback provides the failure error if such occurred.
62+
5963
## Potential compatibility issues
6064

65+
### Added finite wait time for default implementation of `ClientSession#executeRemoteCommand`
66+
67+
* `CoreModuleProperties#EXEC_CHANNEL_OPEN_TIMEOUT` - default = 30 seconds.
68+
* `CoreModuleProperties#EXEC_CHANNEL_CMD_TIMEOUT` - default = 30 seconds.
69+
70+
This may cause failures for code that was running long execution commands using the default method implementations.
71+
6172
## Major Code Re-factoring
6273

sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ public static void outputDebugMessage(String format, Object o) {
703703

704704
public static void outputDebugMessage(String format, Object... args) {
705705
if (OUTPUT_DEBUG_MESSAGES) {
706-
outputDebugMessage(String.format(format, args));
706+
outputDebugMessage(GenericUtils.isEmpty(args) ? format : String.format(format, args));
707707
}
708708
}
709709

@@ -713,6 +713,24 @@ public static void outputDebugMessage(Object message) {
713713
}
714714
}
715715

716+
public static void failWithWrittenErrorMessage(String format, Object... args) {
717+
failWithWrittenErrorMessage(GenericUtils.isEmpty(args) ? format : String.format(format, args));
718+
}
719+
720+
public static void failWithWrittenErrorMessage(Object message) {
721+
writeErrorMessage(message);
722+
fail(Objects.toString(message));
723+
}
724+
725+
public static void writeErrorMessage(String format, Object... args) {
726+
writeErrorMessage(GenericUtils.isEmpty(args) ? format : String.format(format, args));
727+
}
728+
729+
public static void writeErrorMessage(Object message) {
730+
System.err.append("===[ERROR]=== ").println(message);
731+
System.err.flush();
732+
}
733+
716734
/* ---------------------------------------------------------------------------- */
717735

718736
public static void replaceJULLoggers() {

sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
6060
import org.apache.sshd.common.util.io.output.NullOutputStream;
6161
import org.apache.sshd.common.util.net.SshdSocketAddress;
62+
import org.apache.sshd.core.CoreModuleProperties;
6263

6364
/**
6465
* <P>
@@ -304,10 +305,12 @@ default void executeRemoteCommand(
304305
ClientChannel channel = createExecChannel(command)) {
305306
channel.setOut(channelOut);
306307
channel.setErr(channelErr);
307-
channel.open().await(); // TODO use verify and a configurable timeout
308308

309-
// TODO use a configurable timeout
310-
Collection<ClientChannelEvent> waitMask = channel.waitFor(REMOTE_COMMAND_WAIT_EVENTS, 0L);
309+
Duration openTimeout = CoreModuleProperties.EXEC_CHANNEL_OPEN_TIMEOUT.getRequired(channel);
310+
channel.open().verify(openTimeout);
311+
312+
Duration execTimeout = CoreModuleProperties.EXEC_CHANNEL_CMD_TIMEOUT.getRequired(channel);
313+
Collection<ClientChannelEvent> waitMask = channel.waitFor(REMOTE_COMMAND_WAIT_EVENTS, execTimeout);
311314
if (waitMask.contains(ClientChannelEvent.TIMEOUT)) {
312315
throw new SocketTimeoutException("Failed to retrieve command result in time: " + command);
313316
}

sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public class ClientSessionImpl extends AbstractClientSession {
7474

7575
public ClientSessionImpl(ClientFactoryManager client, IoSession ioSession) throws Exception {
7676
super(client, ioSession);
77+
7778
if (log.isDebugEnabled()) {
7879
log.debug("Client session created: {}", ioSession);
7980
}

sshd-core/src/main/java/org/apache/sshd/common/session/SessionListener.java

+15
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,21 @@ default void sessionPeerIdentificationLine(
102102
Session session, String line, List<String> extraLines) {
103103
// ignored
104104
}
105+
/**
106+
* Identification sent to peer
107+
*
108+
* @param session The {@link Session} instance
109+
* @param version The resolved identification version
110+
* @param extraLines Extra data preceding the identification to be sent. <B>Note:</B> the list is modifiable only if
111+
* this is a server session. The user may modify it based on the peer.
112+
* @param error {@code null} if sending was successful
113+
* @see <A HREF="https://tools.ietf.org/html/rfc4253#section-4.2">RFC 4253 - section 4.2 - Protocol
114+
* Version Exchange</A>
115+
*/
116+
default void sessionPeerIdentificationSent(
117+
Session session, String version, List<String> extraLines, Throwable error) {
118+
// ignored
119+
}
105120

106121
/**
107122
* The peer's identification version was received

sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,8 @@ protected void doHandleMessage(Buffer buffer) throws Exception {
601601
&& CoreModuleProperties.USE_STRICT_KEX.getRequired(this)
602602
&& (cmd != SshConstants.SSH_MSG_KEXINIT)) {
603603
log.error("doHandleMessage({}) invalid 1st message: {}", this, SshConstants.getCommandMessageName(cmd));
604-
throw new SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Strict KEX Error");
604+
disconnect(SshConstants.SSH2_DISCONNECT_PROTOCOL_ERROR, "Strict KEX Error");
605+
return;
605606
}
606607

607608
if (log.isDebugEnabled()) {

sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java

+58-14
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.apache.sshd.common.channel.throttle.ChannelStreamWriterResolverManager;
5050
import org.apache.sshd.common.digest.Digest;
5151
import org.apache.sshd.common.forward.Forwarder;
52+
import org.apache.sshd.common.future.SshFutureListener;
5253
import org.apache.sshd.common.io.IoSession;
5354
import org.apache.sshd.common.io.IoWriteFuture;
5455
import org.apache.sshd.common.kex.AbstractKexFactoryManager;
@@ -77,6 +78,7 @@
7778
/**
7879
* Contains split code in order to make {@link AbstractSession} class smaller
7980
*/
81+
@SuppressWarnings("checkstyle:MethodCount")
8082
public abstract class SessionHelper extends AbstractKexFactoryManager implements Session {
8183

8284
// Session timeout measurements
@@ -628,6 +630,31 @@ protected void signalSendIdentification(SessionListener listener, String version
628630
listener.sessionPeerIdentificationSend(this, version, extraLines);
629631
}
630632

633+
protected void signalIdentificationSent(String version, List<String> extraLines, Throwable error) throws Exception {
634+
try {
635+
invokeSessionSignaller(l -> {
636+
signalIdentificationSent(l, version, extraLines, error);
637+
return null;
638+
});
639+
} catch (Throwable err) {
640+
Throwable e = ExceptionUtils.peelException(err);
641+
if (e instanceof Exception) {
642+
throw (Exception) e;
643+
} else {
644+
throw new RuntimeSshException(e);
645+
}
646+
}
647+
}
648+
649+
protected void signalIdentificationSent(
650+
SessionListener listener, String version, List<String> extraLines, Throwable err) throws Exception {
651+
if (listener == null) {
652+
return;
653+
}
654+
655+
listener.sessionPeerIdentificationSent(this, version, extraLines, err);
656+
}
657+
631658
protected void signalReadPeerIdentificationLine(String line, List<String> extraLines) throws Exception {
632659
try {
633660
invokeSessionSignaller(l -> {
@@ -833,28 +860,45 @@ protected IoWriteFuture sendIdentification(String version, List<String> extraLin
833860
ReservedSessionMessagesHandler handler = getReservedSessionMessagesHandler();
834861
IoWriteFuture future = (handler == null) ? null : handler.sendIdentification(this, version, extraLines);
835862
boolean debugEnabled = log.isDebugEnabled();
836-
if (future != null) {
863+
if (future == null) {
864+
String ident = version;
865+
if (GenericUtils.size(extraLines) > 0) {
866+
ident = GenericUtils.join(extraLines, "\r\n") + "\r\n" + version;
867+
}
868+
869+
if (debugEnabled) {
870+
log.debug("sendIdentification({}): {}",
871+
this, ident.replace('\r', '|').replace('\n', '|'));
872+
}
873+
874+
IoSession networkSession = getIoSession();
875+
byte[] data = (ident + "\r\n").getBytes(StandardCharsets.UTF_8);
876+
future = networkSession.writeBuffer(new ByteArrayBuffer(data));
877+
} else {
837878
if (debugEnabled) {
838879
log.debug("sendIdentification({})[{}] sent {} lines via reserved handler",
839880
this, version, GenericUtils.size(extraLines));
840881
}
841-
842-
return future;
843882
}
844883

845-
String ident = version;
846-
if (GenericUtils.size(extraLines) > 0) {
847-
ident = GenericUtils.join(extraLines, "\r\n") + "\r\n" + version;
848-
}
884+
future.addListener(new SshFutureListener<IoWriteFuture>() {
885+
@Override
886+
public void operationComplete(IoWriteFuture future) {
887+
try {
888+
signalIdentificationSent(version, extraLines, future.getException());
889+
} catch(Throwable err) {
890+
Throwable e = ExceptionUtils.peelException(err);
891+
if (e instanceof RuntimeException) {
892+
throw (RuntimeException) e;
893+
} else {
894+
throw new RuntimeSshException(e);
895+
}
849896

850-
if (debugEnabled) {
851-
log.debug("sendIdentification({}): {}",
852-
this, ident.replace('\r', '|').replace('\n', '|'));
853-
}
897+
}
898+
}
899+
});
854900

855-
IoSession networkSession = getIoSession();
856-
byte[] data = (ident + "\r\n").getBytes(StandardCharsets.UTF_8);
857-
return networkSession.writeBuffer(new ByteArrayBuffer(data));
901+
return future;
858902
}
859903

860904
/**

sshd-core/src/main/java/org/apache/sshd/core/CoreModuleProperties.java

+15
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ public final class CoreModuleProperties {
6161
public static final Property<Duration> CHANNEL_OPEN_TIMEOUT
6262
= Property.duration("ssh-agent-server-channel-open-timeout", Duration.ofSeconds(30));
6363

64+
/**
65+
* Value that can be set on the {@link org.apache.sshd.common.FactoryManager} the session or the channel to
66+
* configure the channel open timeout value (millis) for executing a remote command using default implementation.
67+
*/
68+
public static final Property<Duration> EXEC_CHANNEL_OPEN_TIMEOUT
69+
= Property.duration("ssh-exec-channel-open-timeout", Duration.ofSeconds(30));
70+
71+
/**
72+
* Value that can be set on the {@link org.apache.sshd.common.FactoryManager} the session or the channel to
73+
* configure the channel command execution timeout value (millis) for executing a remote command using default
74+
* implementation.
75+
*/
76+
public static final Property<Duration> EXEC_CHANNEL_CMD_TIMEOUT
77+
= Property.duration("ssh-exec-channel-cmd-timeout", Duration.ofSeconds(30));
78+
6479
/**
6580
* Value used to configure the type of proxy forwarding channel to be used. See also
6681
* https://tools.ietf.org/html/draft-ietf-secsh-agent-02

sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java

-1
Original file line numberDiff line numberDiff line change
@@ -1510,7 +1510,6 @@ public void testKeyboardInteractiveInSessionUserInteractiveFailure() throws Exce
15101510
CoreModuleProperties.PASSWORD_PROMPTS.set(client, maxPrompts);
15111511
AtomicInteger numberOfRequests = new AtomicInteger();
15121512
UserAuthKeyboardInteractiveFactory auth = new UserAuthKeyboardInteractiveFactory() {
1513-
15141513
@Override
15151514
public UserAuthKeyboardInteractive createUserAuth(ClientSession session) throws IOException {
15161515
return new UserAuthKeyboardInteractive() {

0 commit comments

Comments
 (0)