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 2015/11/17 09:46:24 UTC
mina-sshd git commit: [SSHD-591] PUTTY shell session does not echo
commands and their results
Repository: mina-sshd
Updated Branches:
refs/heads/master 97b73947d -> 1912eddd1
[SSHD-591] PUTTY shell session does not echo commands and their results
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/1912eddd
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/1912eddd
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/1912eddd
Branch: refs/heads/master
Commit: 1912eddd12391f2a69063fca46d12b1b6ad886e5
Parents: 97b7394
Author: Lyor Goldstein <lg...@vmware.com>
Authored: Tue Nov 17 10:46:12 2015 +0200
Committer: Lyor Goldstein <lg...@vmware.com>
Committed: Tue Nov 17 10:46:12 2015 +0200
----------------------------------------------------------------------
.../sshd/client/session/ClientSessionImpl.java | 2 +-
.../sshd/common/session/AbstractSession.java | 3 --
.../org/apache/sshd/common/session/Session.java | 6 +++
.../apache/sshd/server/StandardEnvironment.java | 6 +--
.../sshd/server/channel/ChannelSession.java | 2 +-
.../server/channel/PuttyRequestHandler.java | 54 ++++++++++++++++++--
.../sshd/server/session/ServerSessionImpl.java | 6 ++-
.../apache/sshd/server/shell/InvertedShell.java | 3 +-
.../sshd/server/shell/InvertedShellWrapper.java | 5 +-
.../apache/sshd/server/shell/ProcessShell.java | 24 ++++++++-
.../sshd/server/shell/TtyFilterInputStream.java | 2 +-
.../server/shell/InvertedShellWrapperTest.java | 11 ++++
.../sshd/util/test/BogusInvertedShell.java | 7 +++
13 files changed, 111 insertions(+), 20 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSessionImpl.java
----------------------------------------------------------------------
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 06c0a6e..0f4df8f 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
@@ -610,7 +610,7 @@ public class ClientSessionImpl extends AbstractSession implements ClientSession
return false;
}
log.debug("Server version string: {}", serverVersion);
- if (!(serverVersion.startsWith("SSH-2.0-") || serverVersion.startsWith("SSH-1.99-"))) {
+ if (!(serverVersion.startsWith(DEFAULT_SSH_VERSION_PREFIX) || serverVersion.startsWith("SSH-1.99-"))) {
throw new SshException(SshConstants.SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
"Unsupported protocol version: " + serverVersion);
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/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 11ca6f8..21e1963 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
@@ -85,9 +85,6 @@ import org.apache.sshd.common.util.closeable.AbstractInnerCloseable;
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public abstract class AbstractSession extends AbstractInnerCloseable implements Session {
-
- public static final String DEFAULT_SSH_VERSION_PREFIX = "SSH-2.0-";
-
/**
* Name of the property where this session is stored in the attributes of the
* underlying MINA session. See {@link #getSession(IoSession, boolean)}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java b/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java
index 3f04862..f16949a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/session/Session.java
@@ -48,6 +48,12 @@ public interface Session
UsernameHolder {
/**
+ * Default prefix expected for the client / server identification string
+ */
+ String DEFAULT_SSH_VERSION_PREFIX = "SSH-2.0-";
+
+
+ /**
* Timeout status.
*/
enum TimeoutStatus {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/main/java/org/apache/sshd/server/StandardEnvironment.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/StandardEnvironment.java b/sshd-core/src/main/java/org/apache/sshd/server/StandardEnvironment.java
index 011b52a..7a5b1c5 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/StandardEnvironment.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/StandardEnvironment.java
@@ -115,15 +115,15 @@ public class StandardEnvironment extends AbstractLoggingBean implements Environm
}
/**
- * adds a variable to the environment. This method is called <code>set</code>
+ * Adds a variable to the environment. This method is called <code>set</code>
* according to the name of the appropriate posix command <code>set</code>
*
- * @param key environment variable name
+ * @param key environment variable name - never {@code null}/empty
* @param value environment variable value
*/
public void set(String key, String value) {
// TODO: listening for property changes would be nice too.
- getEnv().put(key, value);
+ getEnv().put(ValidateUtils.checkNotNullAndNotEmpty(key, "Empty environment variable name"), value);
}
/**
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
index fd4ff86..4b6e69b 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/ChannelSession.java
@@ -590,7 +590,7 @@ public class ChannelSession extends AbstractServerChannel {
getEnvironment().set(name, value);
}
- protected StandardEnvironment getEnvironment() {
+ public StandardEnvironment getEnvironment() {
return env;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/main/java/org/apache/sshd/server/channel/PuttyRequestHandler.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/channel/PuttyRequestHandler.java b/sshd-core/src/main/java/org/apache/sshd/server/channel/PuttyRequestHandler.java
index 022b949..388a871 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/channel/PuttyRequestHandler.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/channel/PuttyRequestHandler.java
@@ -18,8 +18,15 @@
*/
package org.apache.sshd.server.channel;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+
import org.apache.sshd.common.channel.AbstractChannelRequestHandler;
import org.apache.sshd.common.channel.Channel;
+import org.apache.sshd.common.channel.PtyMode;
+import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.buffer.Buffer;
@@ -28,17 +35,21 @@ import org.apache.sshd.common.util.buffer.Buffer;
* <A HREF="http://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixF.html">Appendix F: SSH-2 names specified for PuTTY</A>
*/
public class PuttyRequestHandler extends AbstractChannelRequestHandler {
+ /**
+ * Suffix of all PUTTY related channel requests
+ */
public static final String REQUEST_SUFFIX = "@putty.projects.tartarus.org";
+ public static final Set<PtyMode> PUTTY_OPTIONS =
+ Collections.unmodifiableSet(EnumSet.of(PtyMode.ECHO, PtyMode.ICRNL, PtyMode.ONLCR));
+
public PuttyRequestHandler() {
super();
}
@Override
public Result process(Channel channel, String request, boolean wantReply, Buffer buffer) throws Exception {
- // make sure proper suffix
- if ((GenericUtils.length(request) <= REQUEST_SUFFIX.length())
- || (!request.endsWith(REQUEST_SUFFIX))) {
+ if (isPuttyRequest(request)) {
return Result.Unsupported;
}
@@ -64,4 +75,41 @@ public class PuttyRequestHandler extends AbstractChannelRequestHandler {
return Result.ReplyFailure;
}
+
+ /**
+ * @param request The channel request value - ignored if {@code null}/empty
+ * @return {@code true} if the request ends in {@link #REQUEST_SUFFIX}
+ */
+ public static boolean isPuttyRequest(String request) {
+ return (GenericUtils.length(request) > REQUEST_SUFFIX.length()) && request.endsWith(REQUEST_SUFFIX);
+ }
+
+ /**
+ * @param session The current {@link Session} - ignored if {@code null}
+ * @return {@code true} if it is a PUTTY session
+ * @see Session#getClientVersion()
+ * @see #isPuttyClient(String)
+ */
+ public static boolean isPuttyClient(Session session) {
+ return isPuttyClient((session == null) ? null : session.getClientVersion());
+ }
+
+ /**
+ * @param clientVersion The client identification string - ignored if {@code null}/empty
+ * @return {@code true} if the identification starts with the {@link Session#DEFAULT_SSH_VERSION_PREFIX}
+ * and it contains the "putty" string (case insensitive)
+ */
+ public static boolean isPuttyClient(String clientVersion) {
+ return (GenericUtils.length(clientVersion) > Session.DEFAULT_SSH_VERSION_PREFIX.length())
+ && clientVersion.toLowerCase().contains("putty");
+ }
+
+ public static Map<PtyMode, Integer> resolveShellTtyOptions(Map<PtyMode, Integer> modes) {
+ Map<PtyMode, Integer> resolved = PtyMode.createEnabledOptions(PUTTY_OPTIONS);
+ if (GenericUtils.size(modes) > 0) {
+ resolved.putAll(modes); // TODO consider adding only non-overriding options
+ }
+
+ return resolved;
+ }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSessionImpl.java
----------------------------------------------------------------------
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 b524923..aa92a8b 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
@@ -185,7 +185,11 @@ public class ServerSessionImpl extends AbstractSession implements ServerSession
if (GenericUtils.isEmpty(clientVersion)) {
return false;
}
- log.debug("Client version string: {}", clientVersion);
+
+ if (log.isDebugEnabled()) {
+ log.debug("readIdentification({}) client version string: {}", this, clientVersion);
+ }
+
if (!clientVersion.startsWith(DEFAULT_SSH_VERSION_PREFIX)) {
String msg = "Unsupported protocol version: " + clientVersion;
ioSession.write(new ByteArrayBuffer((msg + "\n").getBytes(StandardCharsets.UTF_8))).addListener(new SshFutureListener<IoWriteFuture>() {
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java
index ac3e8a7..3d7de3c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShell.java
@@ -22,6 +22,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import org.apache.sshd.server.CommandLifecycle;
+import org.apache.sshd.server.SessionAware;
/**
* This shell have inverted streams, such as the one obtained when launching a
@@ -31,7 +32,7 @@ import org.apache.sshd.server.CommandLifecycle;
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public interface InvertedShell extends CommandLifecycle {
+public interface InvertedShell extends CommandLifecycle, SessionAware {
/**
* Returns the output stream used to feed the shell.
* This method is called after the shell has been started.
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
index 6c55025..43155ff 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/InvertedShellWrapper.java
@@ -139,10 +139,7 @@ public class InvertedShellWrapper extends AbstractLoggingBean implements Command
public void setSession(ServerSession session) {
pumpSleepTime = PropertyResolverUtils.getLongProperty(session, PUMP_SLEEP_TIME, DEFAULT_PUMP_SLEEP_TIME);
ValidateUtils.checkTrue(pumpSleepTime > 0L, "Invalid " + PUMP_SLEEP_TIME + ": %d", pumpSleepTime);
-
- if (shell instanceof SessionAware) {
- ((SessionAware) shell).setSession(session);
- }
+ shell.setSession(session);
}
@Override
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
index fa69429..dbe47a7 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/ProcessShell.java
@@ -34,15 +34,19 @@ import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.channel.PuttyRequestHandler;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.session.ServerSessionHolder;
/**
* Bridges the I/O streams between the SSH command and the process that executes it
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public class ProcessShell extends AbstractLoggingBean implements InvertedShell {
+public class ProcessShell extends AbstractLoggingBean implements InvertedShell, ServerSessionHolder {
private final List<String> command;
private String cmdValue;
+ private ServerSession session;
private Process process;
private TtyFilterOutputStream in;
private TtyFilterInputStream out;
@@ -55,6 +59,7 @@ public class ProcessShell extends AbstractLoggingBean implements InvertedShell {
public ProcessShell(String ... command) {
this(GenericUtils.isEmpty(command) ? Collections.<String>emptyList() : Arrays.asList(command));
}
+
public ProcessShell(Collection<String> command) {
// we copy the original list so as not to change it
this.command = new ArrayList<String>(ValidateUtils.checkNotNullAndNotEmpty(command, "No process shell command(s)"));
@@ -62,6 +67,17 @@ public class ProcessShell extends AbstractLoggingBean implements InvertedShell {
}
@Override
+ public ServerSession getServerSession() {
+ return session;
+ }
+
+ @Override
+ public void setSession(ServerSession session) {
+ this.session = ValidateUtils.checkNotNull(session, "No server session");
+ ValidateUtils.checkTrue(process == null, "Session set after process started");
+ }
+
+ @Override
public void start(Environment env) throws IOException {
Map<String, String> varsMap = resolveShellEnvironment(env.getEnv());
for (int i = 0; i < command.size(); i++) {
@@ -101,7 +117,11 @@ public class ProcessShell extends AbstractLoggingBean implements InvertedShell {
// for some reason these modes provide best results BOTH with Linux SSH client and PUTTY
protected Map<PtyMode, Integer> resolveShellTtyOptions(Map<PtyMode, Integer> modes) {
- return modes;
+ if (PuttyRequestHandler.isPuttyClient(getServerSession())) {
+ return PuttyRequestHandler.resolveShellTtyOptions(modes);
+ } else {
+ return modes;
+ }
}
@Override
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterInputStream.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterInputStream.java b/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterInputStream.java
index 840eca6..e17cbf0 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterInputStream.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/shell/TtyFilterInputStream.java
@@ -128,8 +128,8 @@ public class TtyFilterInputStream extends FilterInputStream {
buf.putBuffer(org);
return buf;
}
-
}
+
protected int readRawInput() throws IOException {
if (buffer.available() > 0) {
return buffer.getUByte();
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java b/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java
index 6632965..1746d38 100644
--- a/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/server/shell/InvertedShellWrapperTest.java
@@ -27,6 +27,7 @@ import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.util.test.BaseTestSupport;
import org.apache.sshd.util.test.BogusEnvironment;
import org.apache.sshd.util.test.BogusExitCallback;
@@ -85,6 +86,11 @@ public class InvertedShellWrapperTest extends BaseTestSupport {
}
@Override
+ public void setSession(ServerSession session) {
+ bogusShell.setSession(session);
+ }
+
+ @Override
public boolean isAlive() {
return bogusShell.isAlive();
}
@@ -177,6 +183,11 @@ public class InvertedShellWrapperTest extends BaseTestSupport {
}
@Override
+ public void setSession(ServerSession session) {
+ // ignored
+ }
+
+ @Override
public boolean isAlive() {
return false;
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1912eddd/sshd-core/src/test/java/org/apache/sshd/util/test/BogusInvertedShell.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/BogusInvertedShell.java b/sshd-core/src/test/java/org/apache/sshd/util/test/BogusInvertedShell.java
index 6b2630e..3954bca 100644
--- a/sshd-core/src/test/java/org/apache/sshd/util/test/BogusInvertedShell.java
+++ b/sshd-core/src/test/java/org/apache/sshd/util/test/BogusInvertedShell.java
@@ -26,6 +26,7 @@ import java.util.Map;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.shell.InvertedShell;
public class BogusInvertedShell implements InvertedShell {
@@ -35,6 +36,7 @@ public class BogusInvertedShell implements InvertedShell {
private final InputStream err;
// for test assertions
+ private ServerSession session;
private boolean started = false;
private boolean alive = true;
private Map<String, String> env = null;
@@ -46,6 +48,11 @@ public class BogusInvertedShell implements InvertedShell {
}
@Override
+ public void setSession(ServerSession session) {
+ this.session = session;
+ }
+
+ @Override
public void start(Environment env) throws IOException {
this.started = true;
this.env = Collections.unmodifiableMap(env.getEnv());