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:50 UTC
[mina-sshd] 04/04: [SSHD-1097] Implemented endless tarpit example
in sshd-contrib
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...
}
}