/* * Copyright (C)2009 - SSHJ Contributors * * Licensed 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 net.schmizz.sshj.connection.channel.direct; import net.schmizz.sshj.common.*; import net.schmizz.sshj.connection.Connection; import net.schmizz.sshj.connection.ConnectionException; import net.schmizz.sshj.connection.channel.ChannelInputStream; import net.schmizz.sshj.transport.TransportException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.Collections; import java.util.Map; import java.util.concurrent.TimeUnit; /** {@link Session} implementation. */ public class SessionChannel extends AbstractDirectChannel implements Session, Session.Command, Session.Shell, Session.Subsystem { private final ChannelInputStream err = new ChannelInputStream(this, trans, lwin); private volatile Integer exitStatus; private volatile Signal exitSignal; private volatile Boolean wasCoreDumped; private volatile String exitErrMsg; private volatile Boolean canDoFlowControl; private boolean usedUp; public SessionChannel(Connection conn) { super(conn, "session"); } public SessionChannel(Connection conn, Charset remoteCharset) { super(conn, "session", remoteCharset); } @Override public void allocateDefaultPTY() throws ConnectionException, TransportException { allocatePTY("vt100", 80, 24, 0, 0, Collections.<PTYMode, Integer>emptyMap()); } @Override public void allocatePTY(String term, int cols, int rows, int width, int height, Map<PTYMode, Integer> modes) throws ConnectionException, TransportException { sendChannelRequest( "pty-req", true, new Buffer.PlainBuffer() .putString(term) .putUInt32(cols) .putUInt32(rows) .putUInt32(width) .putUInt32(height) .putBytes(PTYMode.encode(modes)) ).await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS); } @Override public Boolean canDoFlowControl() { return canDoFlowControl; } @Override public void changeWindowDimensions(int cols, int rows, int width, int height) throws TransportException { sendChannelRequest( "window-change", false, new Buffer.PlainBuffer() .putUInt32(cols) .putUInt32(rows) .putUInt32(width) .putUInt32(height) ); } @Override public Command exec(String command) throws ConnectionException, TransportException { checkReuse(); log.debug("Will request to exec `{}`", command); sendChannelRequest("exec", true, new Buffer.PlainBuffer().putString(command, getRemoteCharset())) .await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS); usedUp = true; return this; } @Override public InputStream getErrorStream() { return err; } @Override public String getExitErrorMessage() { return exitErrMsg; } @Override public Signal getExitSignal() { return exitSignal; } @Override public Integer getExitStatus() { return exitStatus; } @Override public void handleRequest(String req, SSHPacket buf) throws ConnectionException, TransportException { try { if ("xon-xoff".equals(req)) canDoFlowControl = buf.readBoolean(); else if ("exit-status".equals(req)) exitStatus = buf.readUInt32AsInt(); else if ("exit-signal".equals(req)) { exitSignal = Signal.fromString(buf.readString()); wasCoreDumped = buf.readBoolean(); // core dumped exitErrMsg = buf.readString(); sendClose(); } else super.handleRequest(req, buf); } catch (Buffer.BufferException be) { throw new ConnectionException(be); } } @Override public void reqX11Forwarding(String authProto, String authCookie, int screen) throws ConnectionException, TransportException { sendChannelRequest( "x11-req", true, new Buffer.PlainBuffer() .putBoolean(false) .putString(authProto) .putString(authCookie) .putUInt32(screen) ).await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS); } @Override public void setEnvVar(String name, String value) throws ConnectionException, TransportException { sendChannelRequest("env", true, new Buffer.PlainBuffer().putString(name).putString(value)) .await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS); } @Override public void signal(Signal sig) throws TransportException { sendChannelRequest("signal", false, new Buffer.PlainBuffer().putString(sig.toString())); } @Override public Shell startShell() throws ConnectionException, TransportException { checkReuse(); sendChannelRequest("shell", true, null).await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS); usedUp = true; return this; } @Override public Subsystem startSubsystem(String name) throws ConnectionException, TransportException { checkReuse(); log.info("Will request `{}` subsystem", name); sendChannelRequest("subsystem", true, new Buffer.PlainBuffer().putString(name)) .await(conn.getTimeoutMs(), TimeUnit.MILLISECONDS); usedUp = true; return this; } @Override public Boolean getExitWasCoreDumped() { return wasCoreDumped; } @Override protected void closeAllStreams() { IOUtils.closeQuietly(err); super.closeAllStreams(); } @Override protected void eofInputStreams() { err.eof(); // also close the stderr stream super.eofInputStreams(); } @Override protected void gotExtendedData(SSHPacket buf) throws ConnectionException, TransportException { try { final int dataTypeCode = buf.readUInt32AsInt(); if (dataTypeCode == 1) receiveInto(err, buf); else throw new ConnectionException(DisconnectReason.PROTOCOL_ERROR, "Bad extended data type = " + dataTypeCode); } catch (Buffer.BufferException be) { throw new ConnectionException(be); } } @Override public void notifyError(SSHException error) { err.notifyError(error); super.notifyError(error); } private void checkReuse() { if (usedUp) throw new SSHRuntimeException("This session channel is all used up"); } }