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 &quot;putty&quot; 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());