Channel.java
/*
* Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials provided with
* the distribution.
*
* 3. The names of the authors may not be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JCRAFT, INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jcraft.jsch;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Vector;
public abstract class Channel {
static final int SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91;
static final int SSH_MSG_CHANNEL_OPEN_FAILURE = 92;
static final int SSH_MSG_CHANNEL_WINDOW_ADJUST = 93;
static final int SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1;
static final int SSH_OPEN_CONNECT_FAILED = 2;
static final int SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3;
static final int SSH_OPEN_RESOURCE_SHORTAGE = 4;
static int index = 0;
private static Vector<Channel> pool = new Vector<>();
static Channel getChannel(String type, Session session) {
Channel ret = null;
if (type.equals("session")) {
ret = new ChannelSession();
}
if (type.equals("shell")) {
ret = new ChannelShell();
}
if (type.equals("exec")) {
ret = new ChannelExec();
}
if (type.equals("x11")) {
ret = new ChannelX11();
}
if (type.equals("auth-agent@openssh.com")) {
ret = new ChannelAgentForwarding();
}
if (type.equals("direct-tcpip")) {
ret = new ChannelDirectTCPIP();
}
if (type.equals("forwarded-tcpip")) {
ret = new ChannelForwardedTCPIP();
}
if (type.equals("sftp")) {
ChannelSftp sftp = new ChannelSftp();
boolean useWriteFlushWorkaround =
session.getConfig("use_sftp_write_flush_workaround").equals("yes");
sftp.setUseWriteFlushWorkaround(useWriteFlushWorkaround);
ret = sftp;
}
if (type.equals("subsystem")) {
ret = new ChannelSubsystem();
}
if (type.equals("direct-streamlocal@openssh.com")) {
ret = new ChannelDirectStreamLocal();
}
if (ret == null) {
return null;
}
ret.setSession(session);
return ret;
}
static Channel getChannel(int id, Session session) {
synchronized (pool) {
for (int i = 0; i < pool.size(); i++) {
Channel c = pool.elementAt(i);
if (c.id == id && c.session == session)
return c;
}
}
return null;
}
static void del(Channel c) {
synchronized (pool) {
pool.removeElement(c);
}
}
int id;
volatile int recipient = -1;
protected byte[] type = Util.str2byte("foo");
volatile int lwsize_max = 0x100000;
volatile int lwsize = lwsize_max; // local initial window size
volatile int lmpsize = 0x4000; // local maximum packet size
volatile long rwsize = 0; // remote initial window size
volatile int rmpsize = 0; // remote maximum packet size
IO io = null;
Thread thread = null;
volatile boolean eof_local = false;
volatile boolean eof_remote = false;
volatile boolean close = false;
volatile boolean connected = false;
volatile boolean open_confirmation = false;
volatile int exitstatus = -1;
volatile int reply = 0;
volatile int connectTimeout = 0;
protected Session session;
int notifyme = 0;
Channel() {
synchronized (pool) {
id = index++;
pool.addElement(this);
}
}
synchronized void setRecipient(int foo) {
this.recipient = foo;
if (notifyme > 0)
notifyAll();
}
int getRecipient() {
return recipient;
}
void init() throws JSchException {}
public void connect() throws JSchException {
connect(0);
}
public void connect(int connectTimeout) throws JSchException {
this.connectTimeout = connectTimeout;
try {
sendChannelOpen();
start();
} catch (Exception e) {
connected = false;
disconnect();
if (e instanceof JSchException)
throw (JSchException) e;
throw new JSchException(e.toString(), e);
}
}
public void setXForwarding(boolean foo) {}
public void start() throws JSchException {}
public boolean isEOF() {
return eof_remote;
}
void getData(Buffer buf) {
setRecipient(buf.getInt());
setRemoteWindowSize(buf.getUInt());
setRemotePacketSize(buf.getInt());
}
public void setInputStream(InputStream in) {
io.setInputStream(in, false);
}
public void setInputStream(InputStream in, boolean dontclose) {
io.setInputStream(in, dontclose);
}
public void setOutputStream(OutputStream out) {
io.setOutputStream(out, false);
}
public void setOutputStream(OutputStream out, boolean dontclose) {
io.setOutputStream(out, dontclose);
}
public void setExtOutputStream(OutputStream out) {
io.setExtOutputStream(out, false);
}
public void setExtOutputStream(OutputStream out, boolean dontclose) {
io.setExtOutputStream(out, dontclose);
}
public InputStream getInputStream() throws IOException {
Session _session = this.session;
if (_session != null && isConnected() && _session.getLogger().isEnabled(Logger.WARN)) {
_session.getLogger().log(Logger.WARN, "getInputStream() should be called before connect()");
}
int max_input_buffer_size = 32 * 1024;
try {
max_input_buffer_size = Integer.parseInt(getSession().getConfig("max_input_buffer_size"));
} catch (Exception e) {
}
PipedInputStream in = new MyPipedInputStream(32 * 1024, // this value should be customizable.
max_input_buffer_size);
boolean resizable = 32 * 1024 < max_input_buffer_size;
io.setOutputStream(new PassiveOutputStream(in, resizable), false);
return in;
}
public InputStream getExtInputStream() throws IOException {
Session _session = this.session;
if (_session != null && isConnected() && _session.getLogger().isEnabled(Logger.WARN)) {
_session.getLogger().log(Logger.WARN,
"getExtInputStream() should be called before connect()");
}
int max_input_buffer_size = 32 * 1024;
try {
max_input_buffer_size = Integer.parseInt(getSession().getConfig("max_input_buffer_size"));
} catch (Exception e) {
}
PipedInputStream in = new MyPipedInputStream(32 * 1024, // this value should be customizable.
max_input_buffer_size);
boolean resizable = 32 * 1024 < max_input_buffer_size;
io.setExtOutputStream(new PassiveOutputStream(in, resizable), false);
return in;
}
public OutputStream getOutputStream() throws IOException {
final Channel channel = this;
OutputStream out = new OutputStream() {
private int dataLen = 0;
private Buffer buffer = null;
private Packet packet = null;
private boolean closed = false;
private synchronized void init() throws IOException {
buffer = new Buffer(rmpsize);
packet = new Packet(buffer);
byte[] _buf = buffer.buffer;
try {
if (_buf.length - (14 + 0) - getSession().getBufferMargin() <= 0) {
buffer = null;
packet = null;
throw new IOException("failed to initialize the channel.");
}
} catch (JSchException e) {
throw new IOException("failed to initialize the channel.", e);
}
}
byte[] b = new byte[1];
@Override
public void write(int w) throws IOException {
b[0] = (byte) w;
write(b, 0, 1);
}
@Override
public void write(byte[] buf, int s, int l) throws IOException {
if (packet == null) {
init();
}
if (closed) {
throw new IOException("Already closed");
}
byte[] _buf = buffer.buffer;
int _bufl = _buf.length;
try {
while (l > 0) {
int _l = l;
int buffer_margin = getSession().getBufferMargin();
if (l > _bufl - (14 + dataLen) - buffer_margin) {
_l = _bufl - (14 + dataLen) - buffer_margin;
}
if (_l <= 0) {
flush();
continue;
}
System.arraycopy(buf, s, _buf, 14 + dataLen, _l);
dataLen += _l;
s += _l;
l -= _l;
}
} catch (JSchException e) {
throw new IOException(e.toString(), e);
}
}
@Override
public void flush() throws IOException {
if (closed) {
throw new IOException("Already closed");
}
if (dataLen == 0)
return;
packet.reset();
buffer.putByte((byte) Session.SSH_MSG_CHANNEL_DATA);
buffer.putInt(recipient);
buffer.putInt(dataLen);
buffer.skip(dataLen);
try {
int foo = dataLen;
dataLen = 0;
synchronized (channel) {
if (!channel.close)
getSession().write(packet, channel, foo);
}
} catch (Exception e) {
close();
throw new IOException(e.toString(), e);
}
}
@Override
public void close() throws IOException {
if (packet == null) {
try {
init();
} catch (IOException e) {
// close should be finished silently.
return;
}
}
if (closed) {
return;
}
if (dataLen > 0) {
flush();
}
channel.eof();
closed = true;
}
};
return out;
}
static class MyPipedInputStream extends PipedInputStream {
private int BUFFER_SIZE = 1024;
private int max_buffer_size = BUFFER_SIZE;
MyPipedInputStream() throws IOException {
super();
}
MyPipedInputStream(int size) throws IOException {
super();
buffer = new byte[size];
BUFFER_SIZE = size;
max_buffer_size = size;
}
MyPipedInputStream(int size, int max_buffer_size) throws IOException {
this(size);
this.max_buffer_size = max_buffer_size;
}
MyPipedInputStream(PipedOutputStream out) throws IOException {
super(out);
}
MyPipedInputStream(PipedOutputStream out, int size) throws IOException {
super(out);
buffer = new byte[size];
BUFFER_SIZE = size;
}
/*
* TODO: We should have our own Piped[I/O]Stream implementation. Before accepting data, JDK's
* PipedInputStream will check the existence of reader thread, and if it is not alive, the
* stream will be closed. That behavior may cause the problem if multiple threads make access to
* it.
*/
public synchronized void updateReadSide() throws IOException {
if (available() != 0) { // not empty
return;
}
in = 0;
out = 0;
buffer[in++] = 0;
read();
}
private int freeSpace() {
int size = 0;
if (out < in) {
size = buffer.length - in;
} else if (in < out) {
if (in == -1)
size = buffer.length;
else
size = out - in;
}
return size;
}
synchronized void checkSpace(int len) throws IOException {
int size = freeSpace();
if (size < len) {
int datasize = buffer.length - size;
int foo = buffer.length;
while ((foo - datasize) < len) {
foo *= 2;
}
if (foo > max_buffer_size) {
foo = max_buffer_size;
}
if ((foo - datasize) < len)
return;
byte[] tmp = new byte[foo];
if (out < in) {
System.arraycopy(buffer, 0, tmp, 0, buffer.length);
} else if (in < out) {
if (in == -1) {
} else {
System.arraycopy(buffer, 0, tmp, 0, in);
System.arraycopy(buffer, out, tmp, tmp.length - (buffer.length - out),
(buffer.length - out));
out = tmp.length - (buffer.length - out);
}
} else if (in == out) {
System.arraycopy(buffer, 0, tmp, 0, buffer.length);
in = buffer.length;
}
buffer = tmp;
} else if (buffer.length == size && size > BUFFER_SIZE) {
int i = size / 2;
if (i < BUFFER_SIZE)
i = BUFFER_SIZE;
byte[] tmp = new byte[i];
buffer = tmp;
}
}
}
void setLocalWindowSizeMax(int foo) {
this.lwsize_max = foo;
}
void setLocalWindowSize(int foo) {
this.lwsize = foo;
}
void setLocalPacketSize(int foo) {
this.lmpsize = foo;
}
synchronized void setRemoteWindowSize(long foo) {
this.rwsize = foo;
}
synchronized void addRemoteWindowSize(long foo) {
this.rwsize += foo;
if (notifyme > 0)
notifyAll();
}
void setRemotePacketSize(int foo) {
this.rmpsize = foo;
}
abstract void run();
void write(byte[] foo) throws IOException {
write(foo, 0, foo.length);
}
void write(byte[] foo, int s, int l) throws IOException {
try {
io.put(foo, s, l);
} catch (NullPointerException e) {
}
}
void write_ext(byte[] foo, int s, int l) throws IOException {
try {
io.put_ext(foo, s, l);
} catch (NullPointerException e) {
}
}
void eof_remote() {
eof_remote = true;
try {
io.out_close();
} catch (NullPointerException e) {
}
}
void eof() {
if (eof_local)
return;
eof_local = true;
int i = getRecipient();
if (i == -1)
return;
try {
Buffer buf = new Buffer(100);
Packet packet = new Packet(buf);
packet.reset();
buf.putByte((byte) Session.SSH_MSG_CHANNEL_EOF);
buf.putInt(i);
synchronized (this) {
if (!close)
getSession().write(packet);
}
} catch (Exception e) {
// System.err.println("Channel.eof");
// e.printStackTrace();
}
/*
* if(!isConnected()){ disconnect(); }
*/
}
/*
* http://www1.ietf.org/internet-drafts/draft-ietf-secsh-connect-24.txt
*
* 5.3 Closing a Channel When a party will no longer send more data to a channel, it SHOULD send
* SSH_MSG_CHANNEL_EOF.
*
* byte SSH_MSG_CHANNEL_EOF uint32 recipient_channel
*
* No explicit response is sent to this message. However, the application may send EOF to whatever
* is at the other end of the channel. Note that the channel remains open after this message, and
* more data may still be sent in the other direction. This message does not consume window space
* and can be sent even if no window space is available.
*
* When either party wishes to terminate the channel, it sends SSH_MSG_CHANNEL_CLOSE. Upon
* receiving this message, a party MUST send back a SSH_MSG_CHANNEL_CLOSE unless it has already
* sent this message for the channel. The channel is considered closed for a party when it has
* both sent and received SSH_MSG_CHANNEL_CLOSE, and the party may then reuse the channel number.
* A party MAY send SSH_MSG_CHANNEL_CLOSE without having sent or received SSH_MSG_CHANNEL_EOF.
*
* byte SSH_MSG_CHANNEL_CLOSE uint32 recipient_channel
*
* This message does not consume window space and can be sent even if no window space is
* available.
*
* It is recommended that any data sent before this message is delivered to the actual
* destination, if possible.
*/
void close() {
if (close)
return;
close = true;
eof_local = eof_remote = true;
int i = getRecipient();
if (i == -1)
return;
try {
Buffer buf = new Buffer(100);
Packet packet = new Packet(buf);
packet.reset();
buf.putByte((byte) Session.SSH_MSG_CHANNEL_CLOSE);
buf.putInt(i);
synchronized (this) {
getSession().write(packet);
}
} catch (Exception e) {
// e.printStackTrace();
}
}
public boolean isClosed() {
return close;
}
static void disconnect(Session session) {
Channel[] channels = null;
int count = 0;
synchronized (pool) {
channels = new Channel[pool.size()];
for (int i = 0; i < pool.size(); i++) {
try {
Channel c = pool.elementAt(i);
if (c.session == session) {
channels[count++] = c;
}
} catch (Exception e) {
}
}
}
for (int i = 0; i < count; i++) {
channels[i].disconnect();
}
}
public void disconnect() {
// System.err.println(this+":disconnect "+io+" "+connected);
// Thread.dumpStack();
try {
synchronized (this) {
if (!connected) {
return;
}
connected = false;
}
close();
eof_remote = eof_local = true;
thread = null;
try {
if (io != null) {
io.close();
}
} catch (Exception e) {
// e.printStackTrace();
}
// io=null;
} finally {
Channel.del(this);
}
}
public boolean isConnected() {
Session _session = this.session;
if (_session != null) {
return _session.isConnected() && connected;
}
return false;
}
public void sendSignal(String signal) throws Exception {
RequestSignal request = new RequestSignal();
request.setSignal(signal);
request.request(getSession(), this);
}
// public String toString(){
// return "Channel: type="+new
// String(type)+",id="+id+",recipient="+recipient+",window_size="+window_size+",packet_size="+packet_size;
// }
/*
* class OutputThread extends Thread{ Channel c; OutputThread(Channel c){ this.c=c;} public void
* run(){c.output_thread();} }
*/
static class PassiveInputStream extends MyPipedInputStream {
PipedOutputStream os;
PassiveInputStream(PipedOutputStream out, int size) throws IOException {
super(out, size);
this.os = out;
}
PassiveInputStream(PipedOutputStream out) throws IOException {
super(out);
this.os = out;
}
@Override
public void close() throws IOException {
if (this.os != null) {
this.os.close();
}
this.os = null;
}
}
static class PassiveOutputStream extends PipedOutputStream {
private MyPipedInputStream _sink = null;
PassiveOutputStream(PipedInputStream in, boolean resizable_buffer) throws IOException {
super(in);
if (resizable_buffer && (in instanceof MyPipedInputStream)) {
this._sink = (MyPipedInputStream) in;
}
}
@Override
public void write(int b) throws IOException {
if (_sink != null) {
_sink.checkSpace(1);
}
super.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (_sink != null) {
_sink.checkSpace(len);
}
super.write(b, off, len);
}
}
void setExitStatus(int status) {
exitstatus = status;
}
public int getExitStatus() {
return exitstatus;
}
void setSession(Session session) {
this.session = session;
}
public Session getSession() throws JSchException {
Session _session = session;
if (_session == null) {
throw new JSchException("session is not available");
}
return _session;
}
public int getId() {
return id;
}
protected void sendOpenConfirmation() throws Exception {
Buffer buf = new Buffer(200);
Packet packet = new Packet(buf);
packet.reset();
buf.putByte((byte) SSH_MSG_CHANNEL_OPEN_CONFIRMATION);
buf.putInt(getRecipient());
buf.putInt(id);
buf.putInt(lwsize);
buf.putInt(lmpsize);
getSession().write(packet);
}
protected void sendOpenFailure(int reasoncode) {
try {
Buffer buf = new Buffer(200);
Packet packet = new Packet(buf);
packet.reset();
buf.putByte((byte) SSH_MSG_CHANNEL_OPEN_FAILURE);
buf.putInt(getRecipient());
buf.putInt(reasoncode);
buf.putString(Util.str2byte("open failed"));
buf.putString(Util.empty);
getSession().write(packet);
} catch (Exception e) {
}
}
protected Packet genChannelOpenPacket() {
Buffer buf = new Buffer(200);
Packet packet = new Packet(buf);
// byte SSH_MSG_CHANNEL_OPEN(90)
// string channel type //
// uint32 sender channel // 0
// uint32 initial window size // 0x100000(65536)
// uint32 maxmum packet size // 0x4000(16384)
packet.reset();
buf.putByte((byte) 90);
buf.putString(this.type);
buf.putInt(this.id);
buf.putInt(this.lwsize);
buf.putInt(this.lmpsize);
return packet;
}
protected void sendChannelOpen() throws Exception {
Session _session = getSession();
if (!_session.isConnected()) {
throw new JSchException("session is down");
}
Packet packet = genChannelOpenPacket();
_session.write(packet);
int retry = 2000;
long start = System.currentTimeMillis();
long timeout = connectTimeout;
if (timeout != 0L)
retry = 1;
synchronized (this) {
while (this.getRecipient() == -1 && _session.isConnected() && retry > 0) {
if (timeout > 0L) {
if ((System.currentTimeMillis() - start) > timeout) {
retry = 0;
continue;
}
}
try {
long t = timeout == 0L ? 10L : timeout;
this.notifyme = 1;
wait(t);
} catch (InterruptedException e) {
} finally {
this.notifyme = 0;
}
retry--;
}
}
if (!_session.isConnected()) {
throw new JSchException("session is down");
}
if (this.getRecipient() == -1) { // timeout
throw new JSchException("channel is not opened.");
}
if (this.open_confirmation == false) { // SSH_MSG_CHANNEL_OPEN_FAILURE
throw new JSchException("channel is not opened.");
}
connected = true;
}
}