Repository: mina-sshd Updated Branches: refs/heads/master ae8d1c995 -> c64e73acb
[SSHD-692] Cannot repeatedly call ChannelSession#executeRemoteCommand Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/c64e73ac Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/c64e73ac Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/c64e73ac Branch: refs/heads/master Commit: c64e73acbfb94ea7e3e0c474150b621ad16f80c0 Parents: ae8d1c9 Author: Lyor Goldstein <[email protected]> Authored: Sun Aug 21 20:46:23 2016 +0300 Committer: Lyor Goldstein <[email protected]> Committed: Sun Aug 21 20:46:23 2016 +0300 ---------------------------------------------------------------------- .../sshd/client/session/ClientSession.java | 14 ++- .../sshd/client/channel/ChannelExecMain.java | 91 ++++++++++++++++ .../sshd/client/channel/ChannelExecTest.java | 109 +++++++++++++++++++ 3 files changed, 213 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c64e73ac/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java index ff88df1..dbd2b14 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java @@ -49,6 +49,7 @@ import org.apache.sshd.client.subsystem.sftp.SftpClientCreator; import org.apache.sshd.common.forward.PortForwardingManager; import org.apache.sshd.common.future.KeyExchangeFuture; import org.apache.sshd.common.session.Session; +import org.apache.sshd.common.util.closeable.CloseableUtils; import org.apache.sshd.common.util.io.NoCloseOutputStream; import org.apache.sshd.common.util.io.NullOutputStream; import org.apache.sshd.common.util.net.SshdSocketAddress; @@ -94,6 +95,8 @@ public interface ClientSession Set<ClientChannelEvent> REMOTE_COMMAND_WAIT_EVENTS = Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.CLOSED, ClientChannelEvent.EXIT_STATUS)); + Set<ClientChannelEvent> REMOTE_COMMAND_END_EVENTS = + Collections.unmodifiableSet(EnumSet.of(ClientChannelEvent.CLOSED)); /** * Returns the original address (after having been translated through host @@ -224,7 +227,16 @@ public interface ClientSession if ((exitStatus != null) && (exitStatus.intValue() != 0)) { throw new RemoteException("Remote command failed (" + exitStatus + "): " + command, new ServerException(exitStatus.toString())); } - byte[] response = channelOut.toByteArray(); + + if (!waitMask.contains(ClientChannelEvent.CLOSED)) { + long maxWait = CloseableUtils.getMaxCloseWaitTime(this); + waitMask = channel.waitFor(REMOTE_COMMAND_END_EVENTS, maxWait); + if (waitMask.contains(ClientChannelEvent.TIMEOUT)) { + throw new SocketTimeoutException("Failed to receive command channel close in time: " + command); + } + } + + byte[] response = channelOut.toByteArray(); return new String(response, charset); } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c64e73ac/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java b/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java new file mode 100644 index 0000000..495459a --- /dev/null +++ b/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecMain.java @@ -0,0 +1,91 @@ +/* + * 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.client.channel; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintStream; + +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.util.GenericUtils; +import org.apache.sshd.common.util.io.NoCloseInputStream; +import org.apache.sshd.util.test.BaseTestSupport; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class ChannelExecMain extends BaseTestSupport { + public static void doExecuteCommands( + BufferedReader stdin, PrintStream stdout, PrintStream stderr, ClientSession session) throws Exception { + while (true) { + stdout.print("> "); + + String command = stdin.readLine(); + if ("q".equalsIgnoreCase(command) || "quit".equalsIgnoreCase(command)) { + break; + } + if (GenericUtils.isEmpty(command)) { + continue; + } + + while (true) { + try { + String response = session.executeRemoteCommand(command); + String[] lines = GenericUtils.split(response, '\n'); + for (String l : lines) { + stdout.append('\t').println(l); + } + } catch (Exception e) { + stderr.append(e.getClass().getSimpleName()).append(": ").println(e.getMessage()); + } + + stdout.append("Execute ").append(command).print(" again [y]/n "); + String ans = stdin.readLine(); + if ((GenericUtils.length(ans) > 0) && (Character.toLowerCase(ans.charAt(0)) != 'y')) { + break; + } + } + } + } + + public static void main(String[] args) throws Exception { + PrintStream stdout = System.out; + PrintStream stderr = System.err; + try (BufferedReader stdin = new BufferedReader(new InputStreamReader(new NoCloseInputStream(System.in)))) { + ClientSession session = SshClient.setupClientSession("-P", stdin, stdout, stderr, args); + if (session == null) { + System.err.println("usage: channelExec [-i identity] [-l login] [-P port] [-o option=value]" + + " [-w password] [-c cipherlist] [-m maclist] [-C] hostname/user@host"); + System.exit(-1); + return; + } + + try (SshClient client = (SshClient) session.getFactoryManager()) { + try (ClientSession clientSession = session) { + doExecuteCommands(stdin, stdout, stderr, session); + } finally { + client.stop(); + } + } + } + } + +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c64e73ac/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecTest.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecTest.java b/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecTest.java new file mode 100644 index 0000000..5b227fc --- /dev/null +++ b/sshd-core/src/test/java/org/apache/sshd/client/channel/ChannelExecTest.java @@ -0,0 +1,109 @@ +/* + * 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.client.channel; + +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; + +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.server.Command; +import org.apache.sshd.server.CommandFactory; +import org.apache.sshd.server.SshServer; +import org.apache.sshd.util.test.BaseTestSupport; +import org.apache.sshd.util.test.CommandExecutionHelper; +import org.apache.sshd.util.test.Utils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class ChannelExecTest extends BaseTestSupport { + private static SshServer sshd; + private static int port; + private static SshClient client; + + public ChannelExecTest() { + super(); + } + + @BeforeClass + public static void setupClientAndServer() throws Exception { + sshd = Utils.setupTestServer(ChannelExecTest.class); + sshd.setCommandFactory(new CommandFactory() { + @Override + public Command createCommand(String command) { + return new CommandExecutionHelper() { + @Override + protected boolean handleCommandLine(String command) throws Exception { + OutputStream stdout = getOut(); + stdout.write(command.getBytes(StandardCharsets.US_ASCII)); + stdout.flush(); + return false; + } + }; + } + }); + sshd.start(); + port = sshd.getPort(); + + client = Utils.setupTestClient(ChannelExecTest.class); + client.start(); + } + + @AfterClass + public static void tearDownClientAndServer() throws Exception { + if (sshd != null) { + try { + sshd.stop(true); + } finally { + sshd = null; + } + } + + if (client != null) { + try { + client.stop(); + } finally { + client = null; + } + } + } + + @Test // see SSHD-692 + public void testMultipleRemoteCommandExecutions() throws Exception { + try (ClientSession session = client.connect(getCurrentTestName(), TEST_LOCALHOST, port).verify(7L, TimeUnit.SECONDS).getSession()) { + session.addPasswordIdentity(getCurrentTestName()); + session.auth().verify(5L, TimeUnit.SECONDS); + + for (int index = 1; index <= Byte.SIZE; index++) { + String expected = getCurrentTestName() + "[" + index + "]"; + String actual = session.executeRemoteCommand(expected + "\n"); + assertEquals("Mismatched reply", expected, actual); + } + } + } +}
