org.apache.sshd.client.channel.PtyCapableChannelSession Maven / Gradle / Ivy
/*
* 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.IOException;
import java.util.Collections;
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.session.Session;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
/**
* Serves as the base channel session for executing remote commands - including
* a full shell. Note: all the configuration changes via the various
* {@code setXXX} methods must be made before the channel is actually
* open. If they are invoked afterwards then they have no effect (silently
* ignored).
* A typical code snippet would be:
*
* try (client = SshClient.setUpDefaultClient()) {
* client.start();
*
* try (ClientSession s = client.connect(getCurrentTestName(), "localhost", port).verify(7L, TimeUnit.SECONDS).getSession()) {
* s.addPasswordIdentity(getCurrentTestName());
* s.auth().verify(5L, TimeUnit.SECONDS);
*
* try (ChannelExec shell = s.createExecChannel("my super duper command")) {
* shell.setEnv("var1", "val1");
* shell.setEnv("var2", "val2");
* ...etc...
*
* shell.setPtyType(...);
* shell.setPtyLines(...);
* ...etc...
*
* shell.open().verify(5L, TimeUnit.SECONDS);
* shell.waitFor(ClientChannel.CLOSED, TimeUnit.SECONDS.toMillis(17L)); // can use zero for infinite wait
*
* Integer status = shell.getExitStatus();
* if (status.intValue() != 0) {
* ...error...
* }
* }
* } finally {
* client.stop();
* }
* }
*
*
* @author Apache MINA SSHD Project
*/
public class PtyCapableChannelSession extends ChannelSession implements PtyChannelConfigurationMutator {
private boolean agentForwarding;
private boolean usePty;
private final Map env = new LinkedHashMap<>();
private final PtyChannelConfiguration config;
public PtyCapableChannelSession(boolean usePty, PtyChannelConfigurationHolder configHolder, Map env) {
this.usePty = usePty;
this.config = PtyChannelConfigurationMutator.copyConfiguration(
configHolder, new PtyChannelConfiguration());
this.config.setPtyType(resolvePtyType(this.config));
if (GenericUtils.isNotEmpty(env)) {
for (Map.Entry 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.isNotEmpty(ptyType)) {
return ptyType;
}
return DUMMY_PTY_TYPE;
}
public void setupSensibleDefaultPty() {
try {
PtyChannelConfigurationMutator.setupSensitiveDefaultPtyConfiguration(this);
} catch (Throwable t) {
if (log.isDebugEnabled()) {
log.debug("setupSensibleDefaultPty({}) Failed ({}) to setup: {}",
this, t.getClass().getSimpleName(), t.getMessage());
}
if (log.isTraceEnabled()) {
log.trace("setupSensibleDefaultPty(" + this + ") failure details", t);
}
}
}
public boolean isAgentForwarding() {
return agentForwarding;
}
public void setAgentForwarding(boolean agentForwarding) {
this.agentForwarding = agentForwarding;
}
public boolean isUsePty() {
return usePty;
}
public void setUsePty(boolean usePty) {
this.usePty = usePty;
}
@Override
public String getPtyType() {
return config.getPtyType();
}
@Override
public void setPtyType(String ptyType) {
config.setPtyType(ptyType);
}
@Override
public int getPtyColumns() {
return config.getPtyColumns();
}
@Override
public void setPtyColumns(int ptyColumns) {
config.setPtyColumns(ptyColumns);
}
@Override
public int getPtyLines() {
return config.getPtyLines();
}
@Override
public void setPtyLines(int ptyLines) {
config.setPtyLines(ptyLines);
}
@Override
public int getPtyWidth() {
return config.getPtyWidth();
}
@Override
public void setPtyWidth(int ptyWidth) {
config.setPtyWidth(ptyWidth);
}
@Override
public int getPtyHeight() {
return config.getPtyHeight();
}
@Override
public void setPtyHeight(int ptyHeight) {
config.setPtyHeight(ptyHeight);
}
@Override
public Map getPtyModes() {
return config.getPtyModes();
}
@Override
public void setPtyModes(Map ptyModes) {
config.setPtyModes((ptyModes == null) ? Collections.emptyMap() : ptyModes);
}
/**
* @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 removed.
* @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, getPtyHeight(), getPtyWidth());
}
public void sendWindowChange(int columns, int lines, int height, int width) throws IOException {
if (log.isDebugEnabled()) {
log.debug("sendWindowChange({}) cols={}, lines={}, height={}, width={}",
this, columns, lines, height, 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(getPtyColumns());
buffer.putInt(getPtyLines());
buffer.putInt(getPtyHeight());
buffer.putInt(getPtyWidth());
writePacket(buffer);
}
protected void doOpenPty() throws IOException {
Session session = getSession();
boolean debugEnabled = log.isDebugEnabled();
if (agentForwarding) {
if (debugEnabled) {
log.debug("doOpenPty({}) Send agent forwarding request", this);
}
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);
buffer.putBoolean(false); // want-reply
writePacket(buffer);
}
if (usePty) {
if (debugEnabled) {
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(getPtyType());
buffer.putInt(getPtyColumns());
buffer.putInt(getPtyLines());
buffer.putInt(getPtyHeight());
buffer.putInt(getPtyWidth());
Map 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);
}
if (GenericUtils.size(env) > 0) {
if (debugEnabled) {
log.debug("doOpenPty({}) Send SSH_MSG_CHANNEL_REQUEST env: {}", this, env);
}
// Cannot use forEach because of the IOException being thrown by writePacket
for (Map.Entry entry : env.entrySet()) {
String key = entry.getKey();
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(str);
writePacket(buffer);
}
}
}
}