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 2016/01/04 13:46:03 UTC
[1/2] mina-sshd git commit: [SSHD-619] Send intermittent
SSH_MSG_IGNORE messages in order to further strengthen cryptographic strength
Repository: mina-sshd
Updated Branches:
refs/heads/master 00beb5a87 -> 926b16be1
[SSHD-619] Send intermittent SSH_MSG_IGNORE messages in order to further strengthen cryptographic strength
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/a535e450
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/a535e450
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/a535e450
Branch: refs/heads/master
Commit: a535e45098100b4e0e0e1966d45eebce3e0af626
Parents: 00beb5a
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Mon Jan 4 14:45:31 2016 +0200
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Mon Jan 4 14:45:31 2016 +0200
----------------------------------------------------------------------
.../org/apache/sshd/common/FactoryManager.java | 84 +++++++++++
.../org/apache/sshd/common/io/IoSession.java | 1 -
.../sshd/common/session/AbstractSession.java | 118 +++++++++++++--
.../sshd/server/ServerFactoryManager.java | 47 ------
.../common/session/AbstractSessionTest.java | 149 ++++++++++++++++++-
5 files changed, 337 insertions(+), 62 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a535e450/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java
index 0e2ff1f..eadc6cb 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/FactoryManager.java
@@ -198,6 +198,90 @@ public interface FactoryManager extends KexFactoryManager, SessionListenerManage
String DEFAULT_VERSION = "SSHD-UNKNOWN";
/**
+ * Key re-exchange will be automatically performed after the session
+ * has sent or received the given amount of bytes. If non-positive,
+ * then disabled. The default value is {@link #DEFAULT_REKEY_BYTES_LIMIT}
+ */
+ String REKEY_BYTES_LIMIT = "rekey-bytes-limit";
+
+ /**
+ * Default value for {@link #REKEY_BYTES_LIMIT} if no override
+ * @see <A HREF="https://tools.ietf.org/html/rfc4253#page-23">RFC4253 section 9</A>
+ */
+ long DEFAULT_REKEY_BYTES_LIMIT = 1024L * 1024L * 1024L; // 1GB
+
+ /**
+ * Key re-exchange will be automatically performed after the specified
+ * amount of time has elapsed since the last key exchange - in milliseconds.
+ * If non-positive then disabled. The default value is {@link #DEFAULT_REKEY_TIME_LIMIT}
+ */
+ String REKEY_TIME_LIMIT = "rekey-time-limit";
+
+ /**
+ * Default value for {@link #REKEY_TIME_LIMIT} if none specified
+ * @see <A HREF="https://tools.ietf.org/html/rfc4253#page-23">RFC4253 section 9</A>
+ */
+ long DEFAULT_REKEY_TIME_LIMIT = 60L * 60L * 1000L; // 1 hour
+
+ /**
+ * Key re-exchange will be automatically performed after the specified
+ * number of packets has been exchanged - positive 64-bit value. If
+ * non-positive then disabled. The default is {@link #DEFAULT_REKEY_PACKETS_LIMIT}
+ */
+ String REKEY_PACKETS_LIMIT = "rekey-packets-limit";
+
+ /**
+ * Default value for {@link #REKEY_PACKETS_LIMIT} if none specified
+ * @see <A HREF="https://tools.ietf.org/html/rfc4344#page-3">RFC4344 section 3.1</A>
+ */
+ long DEFAULT_REKEY_PACKETS_LIMIT = 1L << 31;
+
+ /**
+ * Key re-exchange will be automatically performed after the specified
+ * number of cipher blocks has been processed - positive 64-bit value. If
+ * non-positive then disabled. The default is calculated according to
+ * <A HREF="https://tools.ietf.org/html/rfc4344#page-3">RFC4344 section 3.2</A>
+ */
+ String REKEY_BLOCKS_LIMIT = "rekey-blocks-limit";
+
+ /**
+ * Average number of packets to be skipped before an {@code SSH_MSG_IGNORE}
+ * message is inserted in the stream. If non-positive, then feature is disabled
+ * @see #IGNORE_MESSAGE_VARIANCE
+ */
+ String IGNORE_MESSAGE_FREQUENCY = "ignore-message-frequency";
+
+ /**
+ * Default value of {@link #IGNORE_MESSAGE_FREQUENCY} if none set.
+ */
+ long DEFAULT_IGNORE_MESSAGE_FREQUENCY = 1024L;
+
+ /**
+ * The variance to be used around the configured {@link #IGNORE_MESSAGE_FREQUENCY}
+ * value in order to avoid insertion at a set frequency. If zero, then <U>exact</U>
+ * frequency is used. If negative, then the <U>absolute</U> value is used. If
+ * greater or equal to the frequency, then assumed to be zero - i.e., no variance
+ */
+ String IGNORE_MESSAGE_VARIANCE = "ignore-message-variance";
+
+ /**
+ * Default value for {@link #IGNORE_MESSAGE_VARIANCE} if none configured
+ */
+ int DEFAULT_IGNORE_MESSAGE_VARIANCE = 32;
+
+ /**
+ * Minimum size of {@code SSH_MSG_IGNORE} payload to send if feature enabled. If
+ * non-positive then no message is sent. Otherwise, the actual size is between this
+ * size and twice its value
+ */
+ String IGNORE_MESSAGE_SIZE = "ignore-message-size";
+
+ /**
+ * Value of {@link #IGNORE_MESSAGE_SIZE} if none configured
+ */
+ int DEFAULT_IGNORE_MESSAGE_SIZE = 16;
+
+ /**
* An upper case string identifying the version of the software used on
* client or server side. This version includes the name of the software
* and usually looks like this: <code>SSHD-1.0</code>
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a535e450/sshd-core/src/main/java/org/apache/sshd/common/io/IoSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/io/IoSession.java b/sshd-core/src/main/java/org/apache/sshd/common/io/IoSession.java
index 3885e31..2861b80 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/io/IoSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/io/IoSession.java
@@ -69,7 +69,6 @@ public interface IoSession extends Closeable {
*/
IoWriteFuture write(Buffer buffer);
-
/**
* Closes this session immediately or after all queued write requests
* are flushed. This operation is asynchronous. Wait for the returned
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a535e450/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
index 95edc26..92f5843 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/AbstractSession.java
@@ -39,6 +39,7 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.common.Closeable;
+import org.apache.sshd.common.Factory;
import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.NamedResource;
@@ -75,7 +76,6 @@ import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.BufferUtils;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
-import org.apache.sshd.server.ServerFactoryManager;
/**
* <P>
@@ -189,14 +189,21 @@ public abstract class AbstractSession extends AbstractKexFactoryManager implemen
protected final AtomicLong inBlocksCount = new AtomicLong(0L);
protected final AtomicLong outBlocksCount = new AtomicLong(0L);
protected final AtomicLong lastKeyTimeValue = new AtomicLong(0L);
+ // we initialize them here in case super constructor calls some methods that use these values
+ protected long maxRekyPackets = FactoryManager.DEFAULT_REKEY_PACKETS_LIMIT;
+ protected long maxRekeyBytes = FactoryManager.DEFAULT_REKEY_BYTES_LIMIT;
+ protected long maxRekeyInterval = FactoryManager.DEFAULT_REKEY_TIME_LIMIT;
protected final Queue<PendingWriteFuture> pendingPackets = new LinkedList<>();
protected Service currentService;
- // we initialize them here in case super constructor calls some methods that use these values
- protected long maxRekyPackets = ServerFactoryManager.DEFAULT_REKEY_PACKETS_LIMIT;
- protected long maxRekeyBytes = ServerFactoryManager.DEFAULT_REKEY_BYTES_LIMIT;
- protected long maxRekeyInterval = ServerFactoryManager.DEFAULT_REKEY_TIME_LIMIT;
- protected final AtomicLong maxRekeyBlocks = new AtomicLong(ServerFactoryManager.DEFAULT_REKEY_BYTES_LIMIT / 16);
+
+ // SSH_MSG_IGNORE stream padding
+ protected int ignorePacketDataLength = FactoryManager.DEFAULT_IGNORE_MESSAGE_SIZE;
+ protected long ignorePacketsFrequency = FactoryManager.DEFAULT_IGNORE_MESSAGE_FREQUENCY;
+ protected int ignorePacketsVariance = FactoryManager.DEFAULT_IGNORE_MESSAGE_VARIANCE;
+
+ protected final AtomicLong maxRekeyBlocks = new AtomicLong(FactoryManager.DEFAULT_REKEY_BYTES_LIMIT / 16);
+ protected final AtomicLong ignorePacketsCount = new AtomicLong(FactoryManager.DEFAULT_IGNORE_MESSAGE_FREQUENCY);
/**
* The factory manager used to retrieve factories of Ciphers, Macs and other objects
@@ -217,14 +224,14 @@ public abstract class AbstractSession extends AbstractKexFactoryManager implemen
this.factoryManager = factoryManager;
this.ioSession = ioSession;
- maxRekeyBytes = PropertyResolverUtils.getLongProperty(this, ServerFactoryManager.REKEY_BYTES_LIMIT, maxRekeyBytes);
- maxRekeyInterval = PropertyResolverUtils.getLongProperty(this, ServerFactoryManager.REKEY_TIME_LIMIT, maxRekeyInterval);
- maxRekyPackets = PropertyResolverUtils.getLongProperty(this, ServerFactoryManager.REKEY_PACKETS_LIMIT, maxRekyPackets);
+ 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();
ClassLoader loader = getClass().getClassLoader();
sessionListenerProxy = EventListenerUtils.proxyWrapper(SessionListener.class, loader, sessionListeners);
channelListenerProxy = EventListenerUtils.proxyWrapper(ChannelListener.class, loader, channelListeners);
- random = ValidateUtils.checkNotNull(factoryManager.getRandomFactory(), "No random factory").create();
// Delegate the task of further notifications to the session
addSessionListener(factoryManager.getSessionListenerProxy());
@@ -378,6 +385,27 @@ public abstract class AbstractSession extends AbstractKexFactoryManager implemen
}
}
+ /**
+ * Refresh whatever internal configuration is not {@code final}
+ */
+ protected void refreshConfiguration() {
+ synchronized (lock) {
+ // re-keying configuration
+ maxRekeyBytes = PropertyResolverUtils.getLongProperty(this, FactoryManager.REKEY_BYTES_LIMIT, maxRekeyBytes);
+ maxRekeyInterval = PropertyResolverUtils.getLongProperty(this, FactoryManager.REKEY_TIME_LIMIT, maxRekeyInterval);
+ maxRekyPackets = PropertyResolverUtils.getLongProperty(this, FactoryManager.REKEY_PACKETS_LIMIT, maxRekyPackets);
+
+ // intermittent SSH_MSG_IGNORE stream padding
+ ignorePacketDataLength = PropertyResolverUtils.getIntProperty(this, FactoryManager.IGNORE_MESSAGE_SIZE, FactoryManager.DEFAULT_IGNORE_MESSAGE_SIZE);
+ ignorePacketsFrequency = PropertyResolverUtils.getLongProperty(this, FactoryManager.IGNORE_MESSAGE_FREQUENCY, FactoryManager.DEFAULT_IGNORE_MESSAGE_FREQUENCY);
+ ignorePacketsVariance = PropertyResolverUtils.getIntProperty(this, FactoryManager.IGNORE_MESSAGE_VARIANCE, FactoryManager.DEFAULT_IGNORE_MESSAGE_VARIANCE);
+ if (ignorePacketsVariance >= ignorePacketsFrequency) {
+ ignorePacketsVariance = 0;
+ }
+
+ ignorePacketsCount.set(calculateNextIgnorePacketCount(random, ignorePacketsFrequency, ignorePacketsVariance));
+ }
+ }
/**
* Abstract method for processing incoming decoded packets.
@@ -518,9 +546,17 @@ public abstract class AbstractSession extends AbstractKexFactoryManager implemen
}
protected void handleIgnore(Buffer buffer) throws Exception {
+ handleIgnore(buffer.getBytes(), buffer);
+ }
+
+ protected void handleIgnore(byte[] data, Buffer buffer) throws Exception {
if (log.isDebugEnabled()) {
log.debug("handleIgnore({}) SSH_MSG_IGNORE", this);
}
+
+ if (log.isTraceEnabled()) {
+ log.trace("handleIgnore({}) data: {}", this, BufferUtils.printHex(data));
+ }
}
protected void handleUnimplemented(Buffer buffer) throws Exception {
@@ -833,15 +869,71 @@ public abstract class AbstractSession extends AbstractKexFactoryManager implemen
}
protected IoWriteFuture doWritePacket(Buffer buffer) throws IOException {
+ Buffer ignoreBuf = null;
+ int ignoreDataLen = resolveIgnoreBufferDataLength();
+ if (ignoreDataLen > 0) {
+ ignoreBuf = createBuffer(SshConstants.SSH_MSG_IGNORE, ignoreDataLen + Byte.SIZE);
+ ignoreBuf.putInt(ignoreDataLen);
+
+ int wpos = ignoreBuf.wpos();
+ synchronized (lock) {
+ random.fill(ignoreBuf.array(), wpos, ignoreDataLen);
+ }
+ ignoreBuf.wpos(wpos + ignoreDataLen);
+
+ if (log.isDebugEnabled()) {
+ log.debug("doWritePacket({}) append SSH_MSG_IGNORE message", this);
+ }
+ }
+
// Synchronize all write requests as needed by the encoding algorithm
// and also queue the write request in this synchronized block to ensure
// packets are sent in the correct order
synchronized (encodeLock) {
+ if (ignoreBuf != null) {
+ encode(ignoreBuf);
+ ioSession.write(ignoreBuf);
+ }
+
encode(buffer);
return ioSession.write(buffer);
}
}
+ protected int resolveIgnoreBufferDataLength() {
+ if ((ignorePacketDataLength <= 0) || (ignorePacketsFrequency <= 0L) || (ignorePacketsVariance < 0)) {
+ return 0;
+ }
+
+ long count = ignorePacketsCount.decrementAndGet();
+ if (count > 0L) {
+ return 0;
+ }
+
+ synchronized (lock) {
+ ignorePacketsCount.set(calculateNextIgnorePacketCount(random, ignorePacketsFrequency, ignorePacketsVariance));
+ return ignorePacketDataLength + random.random(ignorePacketDataLength);
+ }
+ }
+
+ protected long calculateNextIgnorePacketCount(Random r, long freq, int variance) {
+ if ((freq <= 0L) || (variance < 0)) {
+ return -1L;
+ }
+
+ if (variance == 0) {
+ return freq;
+ }
+
+ int extra = r.random((variance < 0) ? (0 - variance) : variance);
+ long count = (variance < 0) ? (freq - extra) : (freq + extra);
+ if (log.isTraceEnabled()) {
+ log.trace("calculateNextIgnorePacketCount({}) count={}", this, count);
+ }
+
+ return count;
+ }
+
/**
* Send a global request and wait for the response.
* This must only be used when sending a SSH_MSG_GLOBAL_REQUEST with a result expected,
@@ -892,7 +984,7 @@ public abstract class AbstractSession extends AbstractKexFactoryManager implemen
len += outMac.getBlockSize();
}
- return prepareBuffer(cmd, new ByteArrayBuffer(new byte[Math.max(len, ByteArrayBuffer.DEFAULT_SIZE)], false));
+ return prepareBuffer(cmd, new ByteArrayBuffer(new byte[len + Byte.SIZE], false));
}
@Override
@@ -974,7 +1066,7 @@ public abstract class AbstractSession extends AbstractKexFactoryManager implemen
outBytesCount.addAndGet(len);
// Make buffer ready to be read
buffer.rpos(off);
- } catch (SshException e) {
+ } catch (IOException e) {
throw e;
} catch (Exception e) {
throw new SshException(e);
@@ -1417,7 +1509,7 @@ public abstract class AbstractSession extends AbstractKexFactoryManager implemen
// select the lowest cipher size
int avgCipherBlockSize = Math.min(inBlockSize, outBlockSize);
long recommendedByteRekeyBlocks = 1L << Math.min((avgCipherBlockSize * Byte.SIZE) / 4, 63); // in case (block-size / 4) > 63
- maxRekeyBlocks.set(PropertyResolverUtils.getLongProperty(this, ServerFactoryManager.REKEY_BLOCKS_LIMIT, recommendedByteRekeyBlocks));
+ maxRekeyBlocks.set(PropertyResolverUtils.getLongProperty(this, FactoryManager.REKEY_BLOCKS_LIMIT, recommendedByteRekeyBlocks));
if (log.isDebugEnabled()) {
log.debug("receiveNewKeys({}) inCipher={}, outCipher={}, recommended blocks limit={}, actual={}",
this, inCipher, outCipher, recommendedByteRekeyBlocks, maxRekeyBlocks);
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a535e450/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
index 2e63121..2c2ff25 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
@@ -91,53 +91,6 @@ public interface ServerFactoryManager extends FactoryManager, ServerAuthenticati
long DEFAULT_COMMAND_EXIT_TIMEOUT = TimeUnit.SECONDS.toMillis(5L);
/**
- * Key re-exchange will be automatically performed after the session
- * has sent or received the given amount of bytes. If non-positive,
- * then disabled. The default value is {@link #DEFAULT_REKEY_BYTES_LIMIT}
- */
- String REKEY_BYTES_LIMIT = "rekey-bytes-limit";
-
- /**
- * Default value for {@link #REKEY_BYTES_LIMIT} if no override
- * @see <A HREF="https://tools.ietf.org/html/rfc4253#page-23">RFC4253 section 9</A>
- */
- long DEFAULT_REKEY_BYTES_LIMIT = 1024L * 1024L * 1024L; // 1GB
-
- /**
- * Key re-exchange will be automatically performed after the specified
- * amount of time has elapsed since the last key exchange - in milliseconds.
- * If non-positive then disabled. The default value is {@link #DEFAULT_REKEY_TIME_LIMIT}
- */
- String REKEY_TIME_LIMIT = "rekey-time-limit";
-
- /**
- * Default value for {@link #REKEY_TIME_LIMIT} if none specified
- * @see <A HREF="https://tools.ietf.org/html/rfc4253#page-23">RFC4253 section 9</A>
- */
- long DEFAULT_REKEY_TIME_LIMIT = 60L * 60L * 1000L; // 1 hour
-
- /**
- * Key re-exchange will be automatically performed after the specified
- * number of packets has been exchanged - positive 64-bit value. If
- * non-positive then disabled. The default is {@link #DEFAULT_REKEY_PACKETS_LIMIT}
- */
- String REKEY_PACKETS_LIMIT = "rekey-packets-limit";
-
- /**
- * Default value for {@link #REKEY_PACKETS_LIMIT} if none specified
- * @see <A HREF="https://tools.ietf.org/html/rfc4344#page-3">RFC4344 section 3.1</A>
- */
- long DEFAULT_REKEY_PACKETS_LIMIT = 1L << 31;
-
- /**
- * Key re-exchange will be automatically performed after the specified
- * number of cipher blocks has been processed - positive 64-bit value. If
- * non-positive then disabled. The default is calculated according to
- * <A HREF="https://tools.ietf.org/html/rfc4344#page-3">RFC4344 section 3.2</A>
- */
- String REKEY_BLOCKS_LIMIT = "rekey-blocks-limit";
-
- /**
* A URL pointing to the moduli file.
* If not specified, the default internal file will be used.
*/
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/a535e450/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java b/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
index 210fde3..886f08f 100644
--- a/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/common/session/AbstractSessionTest.java
@@ -19,10 +19,21 @@
package org.apache.sshd.common.session;
import java.io.IOException;
+import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.sshd.common.FactoryManager;
+import org.apache.sshd.common.PropertyResolverUtils;
+import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.channel.IoWriteFutureImpl;
+import org.apache.sshd.common.future.CloseFuture;
+import org.apache.sshd.common.io.IoService;
+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.util.GenericUtils;
import org.apache.sshd.common.util.buffer.Buffer;
@@ -43,11 +54,21 @@ public class AbstractSessionTest extends BaseTestSupport {
private MySession session;
+ public AbstractSessionTest() {
+ super();
+ }
+
@Before
public void setUp() throws Exception {
session = new MySession();
}
+ public void tearDown() throws Exception {
+ if (session != null) {
+ session.close();
+ }
+ }
+
@Test
public void testReadIdentSimple() {
Buffer buf = new ByteArrayBuffer("SSH-2.0-software\r\n".getBytes(StandardCharsets.UTF_8));
@@ -111,9 +132,130 @@ public class AbstractSessionTest extends BaseTestSupport {
fail("Unexpected success: " + ident);
}
+ @Test // see SSHD-619
+ public void testMsgIgnorePadding() throws Exception {
+ final long frequency = Byte.SIZE;
+ PropertyResolverUtils.updateProperty(session, FactoryManager.IGNORE_MESSAGE_SIZE, Short.SIZE);
+ PropertyResolverUtils.updateProperty(session, FactoryManager.IGNORE_MESSAGE_FREQUENCY, frequency);
+ PropertyResolverUtils.updateProperty(session, FactoryManager.IGNORE_MESSAGE_VARIANCE, 0);
+ session.refreshConfiguration();
+
+ Buffer msg = session.createBuffer(SshConstants.SSH_MSG_DEBUG, Long.SIZE);
+ msg.putBoolean(true); // display ?
+ msg.putString(getCurrentTestName()); // message
+ msg.putString(""); // language
+
+ MyIoSession ioSession = (MyIoSession) session.getIoSession();
+ Queue<Buffer> queue = ioSession.getOutgoingMessages();
+ int numIgnores = 0;
+ for (int cycle = 0; cycle < Byte.SIZE; cycle++) {
+ for (long index = 0; index <= frequency; index++) {
+ session.writePacket(msg);
+
+ Buffer data = queue.remove();
+ if (data != msg) {
+ int cmd = data.getUByte();
+ assertEquals("Mismatched buffer command at cycle " + cycle + "[" + index + "]", SshConstants.SSH_MSG_IGNORE, cmd);
+
+ int len = data.getInt();
+ assertTrue("Mismatched random padding data length at cycle " + cycle + "[" + index + "]: " + len, len >= Short.SIZE);
+ numIgnores++;
+ }
+ }
+ }
+
+ assertEquals("Mismatched number of ignore messages", Byte.SIZE, numIgnores);
+ }
+
+ public static class MyIoSession implements IoSession {
+ private final Queue<Buffer> outgoing = new LinkedBlockingQueue<>();
+ private final AtomicBoolean open = new AtomicBoolean(true);
+
+ public MyIoSession() {
+ super();
+ }
+
+ public Queue<Buffer> getOutgoingMessages() {
+ return outgoing;
+ }
+
+ @Override
+ public boolean isClosed() {
+ return !isOpen();
+ }
+
+ @Override
+ public boolean isClosing() {
+ return !isOpen();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return open.get();
+ }
+
+ @Override
+ public void close() throws IOException {
+ close(true);
+ }
+
+ @Override
+ public long getId() {
+ return 0;
+ }
+
+ @Override
+ public Object getAttribute(Object key) {
+ return null;
+ }
+
+ @Override
+ public Object setAttribute(Object key, Object value) {
+ return null;
+ }
+
+ @Override
+ public SocketAddress getRemoteAddress() {
+ return null;
+ }
+
+ @Override
+ public SocketAddress getLocalAddress() {
+ return null;
+ }
+
+ @Override
+ public IoWriteFuture write(Buffer buffer) {
+ if (!isOpen()) {
+ throw new IllegalStateException("Not open");
+ }
+ if (!outgoing.offer(buffer)) {
+ throw new IllegalStateException("Failed to offer outgoing buffer");
+ }
+
+ IoWriteFutureImpl future = new IoWriteFutureImpl(buffer);
+ future.setValue(Boolean.TRUE);
+ return future;
+ }
+
+ @Override
+ public CloseFuture close(boolean immediately) {
+ if (open.getAndSet(false)) {
+ outgoing.clear();
+ }
+
+ return null;
+ }
+
+ @Override
+ public IoService getService() {
+ return null;
+ }
+ }
+
public static class MySession extends AbstractSession {
public MySession() {
- super(true, org.apache.sshd.util.test.Utils.setupTestServer(AbstractSessionTest.class), null);
+ super(true, org.apache.sshd.util.test.Utils.setupTestServer(AbstractSessionTest.class), new MyIoSession());
}
@Override
@@ -131,6 +273,11 @@ public class AbstractSessionTest extends BaseTestSupport {
}
@Override
+ protected void encode(Buffer buffer) throws IOException {
+ // ignored
+ }
+
+ @Override
protected byte[] sendKexInit() throws IOException {
return GenericUtils.EMPTY_BYTE_ARRAY;
}
[2/2] mina-sshd git commit: Added '-r' flag support to SFTP 'rm'
command
Posted by lg...@apache.org.
Added '-r' flag support to SFTP 'rm' command
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/926b16be
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/926b16be
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/926b16be
Branch: refs/heads/master
Commit: 926b16be1cbf8e0cd68459817abc0417dd5ba87d
Parents: a535e45
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Mon Jan 4 14:45:53 2016 +0200
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Mon Jan 4 14:45:53 2016 +0200
----------------------------------------------------------------------
.../sshd/client/subsystem/sftp/SftpCommand.java | 76 +++++++++++++++++---
1 file changed, 68 insertions(+), 8 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/926b16be/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
index 59b7622..4cc7d6b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpCommand.java
@@ -36,6 +36,7 @@ import java.util.logging.Level;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.client.subsystem.sftp.SftpClient.Attributes;
+import org.apache.sshd.client.subsystem.sftp.SftpClient.DirEntry;
import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatExtensionInfo;
import org.apache.sshd.client.subsystem.sftp.extensions.openssh.OpenSSHStatPathExtension;
import org.apache.sshd.common.NamedResource;
@@ -297,7 +298,7 @@ public class SftpCommand implements Channel {
@Override
public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified", args);
+ ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
String newPath = resolveRemotePath(args);
SftpClient sftp = getClient();
@@ -314,7 +315,7 @@ public class SftpCommand implements Channel {
@Override
public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified", args);
+ ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
String path = resolveRemotePath(args);
SftpClient sftp = getClient();
@@ -359,13 +360,70 @@ public class SftpCommand implements Channel {
@Override
public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified", args);
+ String[] comps = GenericUtils.split(args, ' ');
+ int numArgs = GenericUtils.length(comps);
+ ValidateUtils.checkTrue(numArgs >= 1, "No arguments");
+ ValidateUtils.checkTrue(numArgs <= 2, "Too many arguments: %s", args);
+
+ String remotePath = comps[0];
+ boolean recursive = false;
+ boolean verbose = false;
+ if (remotePath.charAt(0) == '-') {
+ ValidateUtils.checkTrue(remotePath.length() > 1, "Missing flags specification: %s", args);
+ ValidateUtils.checkTrue(numArgs == 2, "Missing remote directory: %s", args);
+
+ for (int index = 1; index < remotePath.length(); index++) {
+ char ch = remotePath.charAt(index);
+ switch(ch) {
+ case 'r' :
+ recursive = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown flag (" + String.valueOf(ch) + ")");
+ }
+ }
+ remotePath = comps[1];
+ }
- String path = resolveRemotePath(args);
+ String path = resolveRemotePath(remotePath);
SftpClient sftp = getClient();
- sftp.remove(path);
+ if (recursive) {
+ Attributes attrs = sftp.stat(path);
+ ValidateUtils.checkTrue(attrs.isDirectory(), "Remote path not a directory: %s", args);
+ removeRecursive(sftp, path, attrs, stdout, verbose);
+ } else {
+ sftp.remove(path);
+ if (verbose) {
+ stdout.append('\t').append("Removed ").println(path);
+ }
+ }
+
return false;
}
+
+ private void removeRecursive(SftpClient sftp, String path, Attributes attrs, PrintStream stdout, boolean verbose) throws IOException {
+ if (attrs.isDirectory()) {
+ for (DirEntry entry : sftp.readDir(path)) {
+ String name = entry.getFilename();
+ if (".".equals(name) || "..".equals(name)) {
+ continue;
+ }
+
+ removeRecursive(sftp, path + "/" + name, entry.getAttributes(), stdout, verbose);
+ }
+
+ sftp.rmdir(path);
+ } else {
+ sftp.remove(path);
+ }
+
+ if (verbose) {
+ stdout.append('\t').append("Removed ").println(path);
+ }
+ }
}
private class RmdirCommandExecutor implements CommandExecutor {
@@ -376,7 +434,7 @@ public class SftpCommand implements Channel {
@Override
public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
- ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified", args);
+ ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
String path = resolveRemotePath(args);
SftpClient sftp = getClient();
@@ -413,13 +471,15 @@ public class SftpCommand implements Channel {
@Override
public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
String[] comps = GenericUtils.split(args, ' ');
- ValidateUtils.checkTrue(GenericUtils.length(comps) == 1, "Invalid number of arguments: %s", args);
+ int numArgs = GenericUtils.length(comps);
+ ValidateUtils.checkTrue(numArgs <= 1, "Invalid number of arguments: %s", args);
SftpClient sftp = getClient();
OpenSSHStatPathExtension ext = sftp.getExtension(OpenSSHStatPathExtension.class);
ValidateUtils.checkTrue(ext.isSupported(), "Extension not supported by server: %s", ext.getName());
- OpenSSHStatExtensionInfo info = ext.stat(resolveRemotePath(GenericUtils.trimToEmpty(comps[0])));
+ String remPath = resolveRemotePath((numArgs >= 1) ? GenericUtils.trimToEmpty(comps[0]) : GenericUtils.trimToEmpty(args));
+ OpenSSHStatExtensionInfo info = ext.stat(remPath);
Field[] fields = info.getClass().getFields();
for (Field f : fields) {
String name = f.getName();