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 2019/02/21 18:50:48 UTC
[mina-sshd] 03/05: [SSHD-897] Exposed API to provide PTY and/or
environment options when opening a client command or shell channel
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 0393ebe048f2e7bd56dd0ae6c29b40dca65ab755
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Wed Feb 20 10:22:22 2019 +0200
[SSHD-897] Exposed API to provide PTY and/or environment options when opening a client command or shell channel
---
CHANGES.md | 7 +
.../common/channel/PtyChannelConfiguration.java | 111 +++++++++++++
.../channel/PtyChannelConfigurationHolder.java | 60 +++++++
.../channel/PtyChannelConfigurationMutator.java | 82 ++++++++++
.../apache/sshd/common/channel/SttySupport.java | 2 +-
.../apache/sshd/client/channel/ChannelExec.java | 6 +-
.../apache/sshd/client/channel/ChannelShell.java | 6 +-
.../client/channel/PtyCapableChannelSession.java | 181 ++++++++++++---------
.../sshd/client/session/AbstractClientSession.java | 15 +-
.../apache/sshd/client/session/ClientSession.java | 67 ++++++--
.../java/org/apache/sshd/client/ClientTest.java | 2 +-
11 files changed, 436 insertions(+), 103 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 01570c0..9e82712 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -20,6 +20,13 @@ current sesssion - client/server proposals and what has been negotiated.
* The `SignalListener` accepts a `Channel` argument indicating the channel instance through which the signal was received.
+* When creating a client shell or command channel one can provide optional PTY and/or environment values in order
+to override the internal default ones.
+
+ * In this context, the `PtyCapableChannelSession#setEnv` method has been modified to accept ANY object.
+ When the environment values are sent to the server, the object's `toString()` will be used. Furthermore,
+ if one provides a `null` value, the previous registered value (if any) is **removed**.
+
## Minor code helpers
* The `Session` object provides a `isServerSession` method that can be used to distinguish between
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyChannelConfiguration.java b/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyChannelConfiguration.java
new file mode 100644
index 0000000..141012c
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyChannelConfiguration.java
@@ -0,0 +1,111 @@
+/*
+ * 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.common.channel;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class PtyChannelConfiguration implements PtyChannelConfigurationMutator {
+ private String ptyType;
+ private int ptyColumns = DEFAULT_COLUMNS_COUNT;
+ private int ptyLines = DEFAULT_ROWS_COUNT;
+ private int ptyWidth = DEFAULT_WIDTH;
+ private int ptyHeight = DEFAULT_HEIGHT;
+ private Map<PtyMode, Integer> ptyModes = new EnumMap<>(PtyMode.class);
+
+ public PtyChannelConfiguration() {
+ ptyModes.putAll(DEFAULT_PTY_MODES);
+ }
+
+ @Override
+ public String getPtyType() {
+ return ptyType;
+ }
+
+ @Override
+ public void setPtyType(String ptyType) {
+ this.ptyType = ptyType;
+ }
+
+ @Override
+ public int getPtyColumns() {
+ return ptyColumns;
+ }
+
+ @Override
+ public void setPtyColumns(int ptyColumns) {
+ this.ptyColumns = ptyColumns;
+ }
+
+ @Override
+ public int getPtyLines() {
+ return ptyLines;
+ }
+
+ @Override
+ public void setPtyLines(int ptyLines) {
+ this.ptyLines = ptyLines;
+ }
+
+ @Override
+ public int getPtyWidth() {
+ return ptyWidth;
+ }
+
+ @Override
+ public void setPtyWidth(int ptyWidth) {
+ this.ptyWidth = ptyWidth;
+ }
+
+ @Override
+ public int getPtyHeight() {
+ return ptyHeight;
+ }
+
+ @Override
+ public void setPtyHeight(int ptyHeight) {
+ this.ptyHeight = ptyHeight;
+ }
+
+ @Override
+ public Map<PtyMode, Integer> getPtyModes() {
+ return ptyModes;
+ }
+
+ @Override
+ public void setPtyModes(Map<PtyMode, Integer> ptyModes) {
+ this.ptyModes = ptyModes;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName()
+ + "[type=" + getPtyType()
+ + ", lines=" + getPtyLines()
+ + ", columns=" + getPtyColumns()
+ + ", height=" + getPtyHeight()
+ + ", width=" + getPtyWidth()
+ + ", modes=" + getPtyModes()
+ + "]";
+ }
+}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyChannelConfigurationHolder.java b/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyChannelConfigurationHolder.java
new file mode 100644
index 0000000..6ba629b
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyChannelConfigurationHolder.java
@@ -0,0 +1,60 @@
+/*
+ * 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.common.channel;
+
+import java.util.Map;
+
+import org.apache.sshd.common.util.MapEntryUtils.EnumMapBuilder;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface PtyChannelConfigurationHolder {
+ int DEFAULT_COLUMNS_COUNT = 80;
+ int DEFAULT_ROWS_COUNT = 24;
+ int DEFAULT_WIDTH = 640;
+ int DEFAULT_HEIGHT = 480;
+
+ String DUMMY_PTY_TYPE = "dummy";
+ String WINDOWS_PTY_TYPE = "windows";
+
+ Map<PtyMode, Integer> DEFAULT_PTY_MODES =
+ EnumMapBuilder.<PtyMode, Integer>builder(PtyMode.class)
+ .put(PtyMode.ISIG, 1)
+ .put(PtyMode.ICANON, 1)
+ .put(PtyMode.ECHO, 1)
+ .put(PtyMode.ECHOE, 1)
+ .put(PtyMode.ECHOK, 1)
+ .put(PtyMode.ECHONL, 0)
+ .put(PtyMode.NOFLSH, 0)
+ .immutable();
+
+ String getPtyType();
+
+ int getPtyColumns();
+
+ int getPtyLines();
+
+ int getPtyWidth();
+
+ int getPtyHeight();
+
+ Map<PtyMode, Integer> getPtyModes();
+}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyChannelConfigurationMutator.java b/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyChannelConfigurationMutator.java
new file mode 100644
index 0000000..bd3cfc9
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/channel/PtyChannelConfigurationMutator.java
@@ -0,0 +1,82 @@
+/*
+ * 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.common.channel;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.sshd.common.util.OsUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public interface PtyChannelConfigurationMutator extends PtyChannelConfigurationHolder {
+ void setPtyType(String ptyType);
+
+ void setPtyColumns(int ptyColumns);
+
+ void setPtyLines(int ptyLines);
+
+ void setPtyWidth(int ptyWidth);
+
+ void setPtyHeight(int ptyHeight);
+
+ void setPtyModes(Map<PtyMode, Integer> ptyModes);
+
+ static <M extends PtyChannelConfigurationMutator> M copyConfiguration(PtyChannelConfigurationHolder src, M dst) {
+ if ((src == null) || (dst == null)) {
+ return dst;
+ }
+
+ dst.setPtyColumns(src.getPtyColumns());
+ dst.setPtyHeight(src.getPtyHeight());
+ dst.setPtyLines(src.getPtyLines());
+ dst.setPtyModes(src.getPtyModes());
+ dst.setPtyType(src.getPtyType());
+ dst.setPtyWidth(src.getPtyWidth());
+ return dst;
+ }
+
+ /**
+ * Uses O/S detection to initialize some default PTY related values
+ *
+ * @param <M> Generic {@link PtyChannelConfigurationMutator} instance
+ * @param mutator The mutator to update - ignored if {@code null}
+ * @return The updated mutator
+ * @throws IOException If failed to access some O/S related configuration
+ * @throws InterruptedException If interrupted during access of O/S related configuration
+ */
+ static <M extends PtyChannelConfigurationMutator> M setupSensitiveDefaultPtyConfiguration(M mutator)
+ throws IOException, InterruptedException {
+ if (mutator == null) {
+ return null;
+ }
+
+ if (OsUtils.isUNIX()) {
+ mutator.setPtyModes(SttySupport.getUnixPtyModes());
+ mutator.setPtyColumns(SttySupport.getTerminalWidth());
+ mutator.setPtyLines(SttySupport.getTerminalHeight());
+ } else {
+ mutator.setPtyType(WINDOWS_PTY_TYPE);
+ }
+
+ return mutator;
+ }
+}
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/channel/SttySupport.java b/sshd-common/src/main/java/org/apache/sshd/common/channel/SttySupport.java
index bd02674..d996dee 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/channel/SttySupport.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/channel/SttySupport.java
@@ -67,7 +67,7 @@ public final class SttySupport {
if (str.charAt(0) == 'v') {
str = str.substring(1);
int v = findChar(stty, str);
- if (v < 0 && "reprint".equals(str)) {
+ if ((v < 0) && "reprint".equals(str)) {
v = findChar(stty, "rprnt");
}
if (v >= 0) {
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelExec.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelExec.java
index b5dfa14..4363c0d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelExec.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelExec.java
@@ -20,9 +20,11 @@ package org.apache.sshd.client.channel;
import java.io.IOException;
import java.util.Date;
+import java.util.Map;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.channel.Channel;
+import org.apache.sshd.common.channel.PtyChannelConfigurationHolder;
import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
@@ -42,8 +44,8 @@ public class ChannelExec extends PtyCapableChannelSession {
private final String command;
- public ChannelExec(String command) {
- super(false);
+ public ChannelExec(String command, PtyChannelConfigurationHolder configHolder, Map<String, ?> env) {
+ super(false, configHolder, env);
this.command = ValidateUtils.checkNotNullAndNotEmpty(command, "Command may not be null/empty");
}
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelShell.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelShell.java
index 32000b8..bf3940c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelShell.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/ChannelShell.java
@@ -20,9 +20,11 @@ package org.apache.sshd.client.channel;
import java.io.IOException;
import java.util.Date;
+import java.util.Map;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.channel.Channel;
+import org.apache.sshd.common.channel.PtyChannelConfigurationHolder;
import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.util.buffer.Buffer;
@@ -39,8 +41,8 @@ public class ChannelShell extends PtyCapableChannelSession {
public static final String REQUEST_SHELL_REPLY = "channel-shell-want-reply";
public static final boolean DEFAULT_REQUEST_SHELL_REPLY = false;
- public ChannelShell() {
- super(true);
+ public ChannelShell(PtyChannelConfigurationHolder configHolder, Map<String, ?> env) {
+ super(true, configHolder, env);
}
@Override
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java b/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java
index e62c44d..c42eae3 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/channel/PtyCapableChannelSession.java
@@ -20,18 +20,19 @@ package org.apache.sshd.client.channel;
import java.io.IOException;
import java.util.Collections;
-import java.util.EnumMap;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Objects;
import org.apache.sshd.agent.SshAgentFactory;
import org.apache.sshd.common.SshConstants;
+import org.apache.sshd.common.channel.PtyChannelConfiguration;
+import org.apache.sshd.common.channel.PtyChannelConfigurationHolder;
+import org.apache.sshd.common.channel.PtyChannelConfigurationMutator;
import org.apache.sshd.common.channel.PtyMode;
-import org.apache.sshd.common.channel.SttySupport;
import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.util.GenericUtils;
-import org.apache.sshd.common.util.MapEntryUtils.EnumMapBuilder;
-import org.apache.sshd.common.util.OsUtils;
+import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -75,51 +76,41 @@ import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
-public class PtyCapableChannelSession extends ChannelSession {
- public static final int DEFAULT_COLUMNS_COUNT = 80;
- public static final int DEFAULT_ROWS_COUNT = 24;
- public static final int DEFAULT_WIDTH = 640;
- public static final int DEFAULT_HEIGHT = 480;
- public static final Map<PtyMode, Integer> DEFAULT_PTY_MODES =
- EnumMapBuilder.<PtyMode, Integer>builder(PtyMode.class)
- .put(PtyMode.ISIG, 1)
- .put(PtyMode.ICANON, 1)
- .put(PtyMode.ECHO, 1)
- .put(PtyMode.ECHOE, 1)
- .put(PtyMode.ECHOK, 1)
- .put(PtyMode.ECHONL, 0)
- .put(PtyMode.NOFLSH, 0)
- .immutable();
-
+public class PtyCapableChannelSession extends ChannelSession implements PtyChannelConfigurationMutator {
private boolean agentForwarding;
private boolean usePty;
- private String ptyType;
- private int ptyColumns = DEFAULT_COLUMNS_COUNT;
- private int ptyLines = DEFAULT_ROWS_COUNT;
- private int ptyWidth = DEFAULT_WIDTH;
- private int ptyHeight = DEFAULT_HEIGHT;
- private Map<PtyMode, Integer> ptyModes = new EnumMap<>(PtyMode.class);
- private final Map<String, String> env = new LinkedHashMap<>();
-
- public PtyCapableChannelSession(boolean usePty) {
+ private final Map<String, Object> env = new LinkedHashMap<>();
+ private final PtyChannelConfiguration config;
+
+ public PtyCapableChannelSession(boolean usePty, PtyChannelConfigurationHolder configHolder, Map<String, ?> env) {
this.usePty = usePty;
+ this.config = PtyChannelConfigurationMutator.copyConfiguration(
+ configHolder, new PtyChannelConfiguration());
+ this.config.setPtyType(resolvePtyType(this.config));
+ if (GenericUtils.isNotEmpty(env)) {
+ for (Map.Entry<String, ?> ee : env.entrySet()) {
+ setEnv(ee.getKey(), ee.getValue());
+ }
+ }
+ }
+
+ protected String resolvePtyType(PtyChannelConfigurationHolder configHolder) {
+ String ptyType = configHolder.getPtyType();
+ if (GenericUtils.isNotEmpty(ptyType)) {
+ return ptyType;
+ }
+
ptyType = System.getenv("TERM");
- if (GenericUtils.isEmpty(ptyType)) {
- ptyType = "dummy";
+ if (GenericUtils.isNotEmpty(ptyType)) {
+ return ptyType;
}
- ptyModes.putAll(DEFAULT_PTY_MODES);
+ return DUMMY_PTY_TYPE;
}
public void setupSensibleDefaultPty() {
try {
- if (OsUtils.isUNIX()) {
- ptyModes = SttySupport.getUnixPtyModes();
- ptyColumns = SttySupport.getTerminalWidth();
- ptyLines = SttySupport.getTerminalHeight();
- } else {
- ptyType = "windows";
- }
+ PtyChannelConfigurationMutator.setupSensitiveDefaultPtyConfiguration(this);
} catch (Throwable t) {
if (log.isDebugEnabled()) {
log.debug("setupSensibleDefaultPty({}) Failed ({}) to setup: {}",
@@ -147,60 +138,84 @@ public class PtyCapableChannelSession extends ChannelSession {
this.usePty = usePty;
}
+ @Override
public String getPtyType() {
- return ptyType;
+ return config.getPtyType();
}
+ @Override
public void setPtyType(String ptyType) {
- this.ptyType = ptyType;
+ config.setPtyType(ptyType);
}
+ @Override
public int getPtyColumns() {
- return ptyColumns;
+ return config.getPtyColumns();
}
+ @Override
public void setPtyColumns(int ptyColumns) {
- this.ptyColumns = ptyColumns;
+ config.setPtyColumns(ptyColumns);
}
+ @Override
public int getPtyLines() {
- return ptyLines;
+ return config.getPtyLines();
}
+ @Override
public void setPtyLines(int ptyLines) {
- this.ptyLines = ptyLines;
+ config.setPtyLines(ptyLines);
}
+ @Override
public int getPtyWidth() {
- return ptyWidth;
+ return config.getPtyWidth();
}
+ @Override
public void setPtyWidth(int ptyWidth) {
- this.ptyWidth = ptyWidth;
+ config.setPtyWidth(ptyWidth);
}
+ @Override
public int getPtyHeight() {
- return ptyHeight;
+ return config.getPtyHeight();
}
+ @Override
public void setPtyHeight(int ptyHeight) {
- this.ptyHeight = ptyHeight;
+ config.setPtyHeight(ptyHeight);
}
+ @Override
public Map<PtyMode, Integer> getPtyModes() {
- return ptyModes;
+ return config.getPtyModes();
}
+ @Override
public void setPtyModes(Map<PtyMode, Integer> ptyModes) {
- this.ptyModes = (ptyModes == null) ? Collections.emptyMap() : ptyModes;
+ config.setPtyModes((ptyModes == null) ? Collections.emptyMap() : ptyModes);
}
- public void setEnv(String key, String value) {
- env.put(key, value);
+ /**
+ * @param key The (never {@code null}) key (Note: may be empty...)
+ * @param value The value to set - if {@code null} then the pre-existing
+ * value for the key (if any) is <U>removed</U>.
+ * @return The replaced/removed previous value - {@code null} if no previous
+ * value set for the key.
+ */
+ public Object setEnv(String key, Object value) {
+ ValidateUtils.checkNotNull(key, "No key provided");
+ if (value == null) {
+ return env.remove(key);
+ } else {
+ return env.put(key, value);
+ }
}
public void sendWindowChange(int columns, int lines) throws IOException {
- sendWindowChange(columns, lines, ptyHeight, ptyWidth);
+ sendWindowChange(columns, lines, getPtyHeight(), getPtyWidth());
}
public void sendWindowChange(int columns, int lines, int height, int width) throws IOException {
@@ -209,20 +224,20 @@ public class PtyCapableChannelSession extends ChannelSession {
this, columns, lines, height, width);
}
- ptyColumns = columns;
- ptyLines = lines;
- ptyHeight = height;
- ptyWidth = width;
+ setPtyColumns(columns);
+ setPtyLines(lines);
+ setPtyHeight(height);
+ setPtyWidth(width);
Session session = getSession();
Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST, Long.SIZE);
buffer.putInt(getRecipient());
buffer.putString("window-change");
buffer.putBoolean(false); // want-reply
- buffer.putInt(ptyColumns);
- buffer.putInt(ptyLines);
- buffer.putInt(ptyHeight);
- buffer.putInt(ptyWidth);
+ buffer.putInt(getPtyColumns());
+ buffer.putInt(getPtyLines());
+ buffer.putInt(getPtyHeight());
+ buffer.putInt(getPtyWidth());
writePacket(buffer);
}
@@ -234,7 +249,8 @@ public class PtyCapableChannelSession extends ChannelSession {
log.debug("doOpenPty({}) Send agent forwarding request", this);
}
- String channelType = session.getStringProperty(SshAgentFactory.PROXY_AUTH_CHANNEL_TYPE, SshAgentFactory.DEFAULT_PROXY_AUTH_CHANNEL_TYPE);
+ String channelType = session.getStringProperty(
+ SshAgentFactory.PROXY_AUTH_CHANNEL_TYPE, SshAgentFactory.DEFAULT_PROXY_AUTH_CHANNEL_TYPE);
Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST, Long.SIZE);
buffer.putInt(getRecipient());
buffer.putString(channelType);
@@ -244,25 +260,28 @@ public class PtyCapableChannelSession extends ChannelSession {
if (usePty) {
if (debugEnabled) {
- log.debug("doOpenPty({}) Send SSH_MSG_CHANNEL_REQUEST pty-req: type={}, cols={}, lines={}, height={}, width={}, modes={}",
- this, ptyType, ptyColumns, ptyLines, ptyHeight, ptyWidth, ptyModes);
+ log.debug("doOpenPty({}) Send SSH_MSG_CHANNEL_REQUEST pty-req: {}", this, config);
}
Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST, Byte.MAX_VALUE);
buffer.putInt(getRecipient());
buffer.putString("pty-req");
buffer.putBoolean(false); // want-reply
- buffer.putString(ptyType);
- buffer.putInt(ptyColumns);
- buffer.putInt(ptyLines);
- buffer.putInt(ptyHeight);
- buffer.putInt(ptyWidth);
-
- Buffer modes = new ByteArrayBuffer(GenericUtils.size(ptyModes) * (1 + Integer.BYTES) + Long.SIZE, false);
- ptyModes.forEach((mode, value) -> {
- modes.putByte((byte) mode.toInt());
- modes.putInt(value.longValue());
- });
+ buffer.putString(getPtyType());
+ buffer.putInt(getPtyColumns());
+ buffer.putInt(getPtyLines());
+ buffer.putInt(getPtyHeight());
+ buffer.putInt(getPtyWidth());
+
+ Map<PtyMode, Integer> ptyModes = getPtyModes();
+ int numModes = GenericUtils.size(ptyModes);
+ Buffer modes = new ByteArrayBuffer(numModes * (1 + Integer.BYTES) + Long.SIZE, false);
+ if (numModes > 0) {
+ ptyModes.forEach((mode, value) -> {
+ modes.putByte((byte) mode.toInt());
+ modes.putInt(value.longValue());
+ });
+ }
modes.putByte(PtyMode.TTY_OP_END);
buffer.putBytes(modes.getCompactData());
writePacket(buffer);
@@ -274,15 +293,17 @@ public class PtyCapableChannelSession extends ChannelSession {
}
// Cannot use forEach because of the IOException being thrown by writePacket
- for (Map.Entry<String, String> entry : env.entrySet()) {
+ for (Map.Entry<String, ?> entry : env.entrySet()) {
String key = entry.getKey();
- String value = entry.getValue();
- Buffer buffer = session.createBuffer(SshConstants.SSH_MSG_CHANNEL_REQUEST, key.length() + value.length() + Integer.SIZE);
+ Object value = entry.getValue();
+ String str = Objects.toString(value);
+ Buffer buffer = session.createBuffer(
+ SshConstants.SSH_MSG_CHANNEL_REQUEST, key.length() + GenericUtils.length(str) + Integer.SIZE);
buffer.putInt(getRecipient());
buffer.putString("env");
buffer.putBoolean(false); // want-reply
buffer.putString(key);
- buffer.putString(value);
+ buffer.putString(str);
writePacket(buffer);
}
}
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 e197077..20610c4 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
@@ -48,6 +48,7 @@ import org.apache.sshd.common.RuntimeSshException;
import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.channel.Channel;
+import org.apache.sshd.common.channel.PtyChannelConfigurationHolder;
import org.apache.sshd.common.cipher.BuiltinCiphers;
import org.apache.sshd.common.cipher.CipherNone;
import org.apache.sshd.common.config.keys.KeyUtils;
@@ -283,12 +284,14 @@ public abstract class AbstractClientSession extends AbstractSession implements C
}
@Override
- public ChannelExec createExecChannel(String command) throws IOException {
- ChannelExec channel = new ChannelExec(command);
+ public ChannelExec createExecChannel(
+ String command, PtyChannelConfigurationHolder ptyConfig, Map<String, ?> env)
+ throws IOException {
+ ChannelExec channel = new ChannelExec(command, ptyConfig, env);
ConnectionService service = getConnectionService();
int id = service.registerChannel(channel);
if (log.isDebugEnabled()) {
- log.debug("createExecChannel({})[{}] created id={}", this, command, id);
+ log.debug("createExecChannel({})[{}] created id={} - PTY={}", this, command, id, ptyConfig);
}
return channel;
}
@@ -388,16 +391,16 @@ public abstract class AbstractClientSession extends AbstractSession implements C
}
@Override
- public ChannelShell createShellChannel() throws IOException {
+ public ChannelShell createShellChannel(PtyChannelConfigurationHolder ptyConfig, Map<String, ?> env) throws IOException {
if ((inCipher instanceof CipherNone) || (outCipher instanceof CipherNone)) {
throw new IllegalStateException("Interactive channels are not supported with none cipher");
}
- ChannelShell channel = new ChannelShell();
+ ChannelShell channel = new ChannelShell(ptyConfig, env);
ConnectionService service = getConnectionService();
int id = service.registerChannel(channel);
if (log.isDebugEnabled()) {
- log.debug("createShellChannel({}) created id={}", this, id);
+ log.debug("createShellChannel({}) created id={} - PTY={}", this, id, ptyConfig);
}
return channel;
}
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 b134ba0..950227c 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
@@ -48,6 +48,7 @@ import org.apache.sshd.client.future.AuthFuture;
import org.apache.sshd.client.session.forward.DynamicPortForwardingTracker;
import org.apache.sshd.client.session.forward.ExplicitPortForwardingTracker;
import org.apache.sshd.common.AttributeRepository;
+import org.apache.sshd.common.channel.PtyChannelConfigurationHolder;
import org.apache.sshd.common.forward.PortForwardingManager;
import org.apache.sshd.common.future.KeyExchangeFuture;
import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
@@ -148,21 +149,54 @@ public interface ClientSession
ClientChannel createChannel(String type, String subType) throws IOException;
/**
- * Create a channel to start a shell.
+ * Create a channel to start a shell using default PTY settings and environment.
*
* @return The created {@link ChannelShell}
* @throws IOException If failed to create the requested channel
*/
- ChannelShell createShellChannel() throws IOException;
+ default ChannelShell createShellChannel() throws IOException {
+ return createShellChannel(null, Collections.emptyMap());
+ }
+
+ /**
+ * Create a channel to start a shell using specific PTY settings and/or environment.
+ *
+ * @param ptyConfig The PTY configuration to use - if {@code null} then
+ * internal defaults are used
+ * @param env Extra environment configuration to be transmitted to the server - ignored
+ * if {@code null}/empty.
+ * @return The created {@link ChannelShell}
+ * @throws IOException If failed to create the requested channel
+ */
+ ChannelShell createShellChannel(
+ PtyChannelConfigurationHolder ptyConfig, Map<String, ?> env)
+ throws IOException;
+
+ /**
+ * Create a channel to execute a command using default PTY settings and environment.
+ *
+ * @param command The command to execute
+ * @return The created {@link ChannelExec}
+ * @throws IOException If failed to create the requested channel
+ */
+ default ChannelExec createExecChannel(String command) throws IOException {
+ return createExecChannel(command, null, Collections.emptyMap());
+ }
/**
- * Create a channel to execute a command.
+ * Create a channel to execute a command using specific PTY settings and/or environment.
*
* @param command The command to execute
+ * @param ptyConfig The PTY configuration to use - if {@code null} then
+ * internal defaults are used
+ * @param env Extra environment configuration to be transmitted to the server - ignored
+ * if {@code null}/empty.
* @return The created {@link ChannelExec}
* @throws IOException If failed to create the requested channel
*/
- ChannelExec createExecChannel(String command) throws IOException;
+ ChannelExec createExecChannel(
+ String command, PtyChannelConfigurationHolder ptyConfig, Map<String, ?> env)
+ throws IOException;
/**
* Execute a command that requires no input and returns its output
@@ -236,7 +270,9 @@ public interface ClientSession
* @throws IOException If failed to execute the command or got a non-zero exit status
* @see ClientChannel#validateCommandExitStatusCode(String, Integer) validateCommandExitStatusCode
*/
- default void executeRemoteCommand(String command, OutputStream stdout, OutputStream stderr, Charset charset) throws IOException {
+ default void executeRemoteCommand(
+ String command, OutputStream stdout, OutputStream stderr, Charset charset)
+ throws IOException {
if (charset == null) {
charset = StandardCharsets.US_ASCII;
}
@@ -276,7 +312,9 @@ public interface ClientSession
* @return The created {@link ChannelDirectTcpip}
* @throws IOException If failed to create the requested channel
*/
- ChannelDirectTcpip createDirectTcpipChannel(SshdSocketAddress local, SshdSocketAddress remote) throws IOException;
+ ChannelDirectTcpip createDirectTcpipChannel(
+ SshdSocketAddress local, SshdSocketAddress remote)
+ throws IOException;
/**
* Starts a local port forwarding and returns a tracker that stops the
@@ -290,8 +328,11 @@ public interface ClientSession
* @throws IOException If failed to set up the requested forwarding
* @see #startLocalPortForwarding(SshdSocketAddress, SshdSocketAddress)
*/
- default ExplicitPortForwardingTracker createLocalPortForwardingTracker(SshdSocketAddress local, SshdSocketAddress remote) throws IOException {
- return new ExplicitPortForwardingTracker(this, true, local, remote, startLocalPortForwarding(local, remote));
+ default ExplicitPortForwardingTracker createLocalPortForwardingTracker(
+ SshdSocketAddress local, SshdSocketAddress remote)
+ throws IOException {
+ return new ExplicitPortForwardingTracker(
+ this, true, local, remote, startLocalPortForwarding(local, remote));
}
/**
@@ -306,8 +347,11 @@ public interface ClientSession
* @throws IOException If failed to set up the requested forwarding
* @see #startRemotePortForwarding(SshdSocketAddress, SshdSocketAddress)
*/
- default ExplicitPortForwardingTracker createRemotePortForwardingTracker(SshdSocketAddress remote, SshdSocketAddress local) throws IOException {
- return new ExplicitPortForwardingTracker(this, false, local, remote, startRemotePortForwarding(remote, local));
+ default ExplicitPortForwardingTracker createRemotePortForwardingTracker(
+ SshdSocketAddress remote, SshdSocketAddress local)
+ throws IOException {
+ return new ExplicitPortForwardingTracker(
+ this, false, local, remote, startRemotePortForwarding(remote, local));
}
/**
@@ -397,6 +441,7 @@ public interface ClientSession
static Iterator<String> passwordIteratorOf(ClientSession session) {
return (session == null)
? Collections.<String>emptyIterator()
- : PasswordIdentityProvider.iteratorOf(session.getRegisteredIdentities(), session.getPasswordIdentityProvider());
+ : PasswordIdentityProvider.iteratorOf(
+ session.getRegisteredIdentities(), session.getPasswordIdentityProvider());
}
}
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
index 11ef70e..8ee4e9a 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientTest.java
@@ -805,7 +805,7 @@ public class ClientTest extends BaseTestSupport {
client.start();
try (ClientSession session = createTestClientSession();
- ChannelShell channel = new ChannelShell();
+ ChannelShell channel = new ChannelShell(null, Collections.emptyMap());
ByteArrayOutputStream sent = new ByteArrayOutputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream()) {