From 3359a7f6d20f4d799140e304f646491863735028 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Thu, 11 Apr 2013 21:01:32 -0700 Subject: Fix line endings --- .../java/com/trilead/ssh2/channel/Channel.java | 414 +-- .../trilead/ssh2/channel/ChannelInputStream.java | 172 +- .../com/trilead/ssh2/channel/ChannelManager.java | 3512 ++++++++++---------- .../trilead/ssh2/channel/ChannelOutputStream.java | 142 +- .../trilead/ssh2/channel/IChannelWorkerThread.java | 26 +- .../trilead/ssh2/channel/LocalAcceptThread.java | 270 +- .../trilead/ssh2/channel/RemoteAcceptThread.java | 206 +- .../trilead/ssh2/channel/RemoteForwardingData.java | 34 +- .../ssh2/channel/RemoteX11AcceptThread.java | 480 +-- .../com/trilead/ssh2/channel/StreamForwarder.java | 226 +- .../com/trilead/ssh2/channel/X11ServerData.java | 32 +- 11 files changed, 2757 insertions(+), 2757 deletions(-) (limited to 'lib/src/main/java/com/trilead/ssh2/channel') diff --git a/lib/src/main/java/com/trilead/ssh2/channel/Channel.java b/lib/src/main/java/com/trilead/ssh2/channel/Channel.java index 4b19a48..8365f12 100644 --- a/lib/src/main/java/com/trilead/ssh2/channel/Channel.java +++ b/lib/src/main/java/com/trilead/ssh2/channel/Channel.java @@ -1,207 +1,207 @@ - -package com.trilead.ssh2.channel; - -/** - * Channel. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: Channel.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class Channel -{ - /* - * OK. Here is an important part of the JVM Specification: - * (http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#22214) - * - * Any association between locks and variables is purely conventional. - * Locking any lock conceptually flushes all variables from a thread's - * working memory, and unlocking any lock forces the writing out to main - * memory of all variables that the thread has assigned. That a lock may be - * associated with a particular object or a class is purely a convention. - * (...) - * - * If a thread uses a particular shared variable only after locking a - * particular lock and before the corresponding unlocking of that same lock, - * then the thread will read the shared value of that variable from main - * memory after the lock operation, if necessary, and will copy back to main - * memory the value most recently assigned to that variable before the - * unlock operation. - * - * This, in conjunction with the mutual exclusion rules for locks, suffices - * to guarantee that values are correctly transmitted from one thread to - * another through shared variables. - * - * ====> Always keep that in mind when modifying the Channel/ChannelManger - * code. - * - */ - - static final int STATE_OPENING = 1; - static final int STATE_OPEN = 2; - static final int STATE_CLOSED = 4; - - static final int CHANNEL_BUFFER_SIZE = 30000; - - /* - * To achieve correctness, the following rules have to be respected when - * accessing this object: - */ - - // These fields can always be read - final ChannelManager cm; - final ChannelOutputStream stdinStream; - final ChannelInputStream stdoutStream; - final ChannelInputStream stderrStream; - - // These two fields will only be written while the Channel is in state - // STATE_OPENING. - // The code makes sure that the two fields are written out when the state is - // changing to STATE_OPEN. - // Therefore, if you know that the Channel is in state STATE_OPEN, then you - // can read these two fields without synchronizing on the Channel. However, make - // sure that you get the latest values (e.g., flush caches by synchronizing on any - // object). However, to be on the safe side, you can lock the channel. - - int localID = -1; - int remoteID = -1; - - /* - * Make sure that we never send a data/EOF/WindowChange msg after a CLOSE - * msg. - * - * This is a little bit complicated, but we have to do it in that way, since - * we cannot keep a lock on the Channel during the send operation (this - * would block sometimes the receiver thread, and, in extreme cases, can - * lead to a deadlock on both sides of the connection (senders are blocked - * since the receive buffers on the other side are full, and receiver - * threads wait for the senders to finish). It all depends on the - * implementation on the other side. But we cannot make any assumptions, we - * have to assume the worst case. Confused? Just believe me. - */ - - /* - * If you send a message on a channel, then you have to aquire the - * "channelSendLock" and check the "closeMessageSent" flag (this variable - * may only be accessed while holding the "channelSendLock" !!! - * - * BTW: NEVER EVER SEND MESSAGES FROM THE RECEIVE THREAD - see explanation - * above. - */ - - final Object channelSendLock = new Object(); - boolean closeMessageSent = false; - - /* - * Stop memory fragmentation by allocating this often used buffer. - * May only be used while holding the channelSendLock - */ - - final byte[] msgWindowAdjust = new byte[9]; - - // If you access (read or write) any of the following fields, then you have - // to synchronize on the channel. - - int state = STATE_OPENING; - - boolean closeMessageRecv = false; - - /* This is a stupid implementation. At the moment we can only wait - * for one pending request per channel. - */ - int successCounter = 0; - int failedCounter = 0; - - int localWindow = 0; /* locally, we use a small window, < 2^31 */ - long remoteWindow = 0; /* long for readable 2^32 - 1 window support */ - - int localMaxPacketSize = -1; - int remoteMaxPacketSize = -1; - - final byte[] stdoutBuffer = new byte[CHANNEL_BUFFER_SIZE]; - final byte[] stderrBuffer = new byte[CHANNEL_BUFFER_SIZE]; - - int stdoutReadpos = 0; - int stdoutWritepos = 0; - int stderrReadpos = 0; - int stderrWritepos = 0; - - boolean EOF = false; - - Integer exit_status; - - String exit_signal; - - // we keep the x11 cookie so that this channel can be closed when this - // specific x11 forwarding gets stopped - - String hexX11FakeCookie; - - // reasonClosed is special, since we sometimes need to access it - // while holding the channelSendLock. - // We protect it with a private short term lock. - - private final Object reasonClosedLock = new Object(); - private String reasonClosed = null; - - public Channel(ChannelManager cm) - { - this.cm = cm; - - this.localWindow = CHANNEL_BUFFER_SIZE; - this.localMaxPacketSize = 35000 - 1024; // leave enough slack - - this.stdinStream = new ChannelOutputStream(this); - this.stdoutStream = new ChannelInputStream(this, false); - this.stderrStream = new ChannelInputStream(this, true); - } - - /* Methods to allow access from classes outside of this package */ - - public ChannelInputStream getStderrStream() - { - return stderrStream; - } - - public ChannelOutputStream getStdinStream() - { - return stdinStream; - } - - public ChannelInputStream getStdoutStream() - { - return stdoutStream; - } - - public String getExitSignal() - { - synchronized (this) - { - return exit_signal; - } - } - - public Integer getExitStatus() - { - synchronized (this) - { - return exit_status; - } - } - - public String getReasonClosed() - { - synchronized (reasonClosedLock) - { - return reasonClosed; - } - } - - public void setReasonClosed(String reasonClosed) - { - synchronized (reasonClosedLock) - { - if (this.reasonClosed == null) - this.reasonClosed = reasonClosed; - } - } -} + +package com.trilead.ssh2.channel; + +/** + * Channel. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: Channel.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class Channel +{ + /* + * OK. Here is an important part of the JVM Specification: + * (http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#22214) + * + * Any association between locks and variables is purely conventional. + * Locking any lock conceptually flushes all variables from a thread's + * working memory, and unlocking any lock forces the writing out to main + * memory of all variables that the thread has assigned. That a lock may be + * associated with a particular object or a class is purely a convention. + * (...) + * + * If a thread uses a particular shared variable only after locking a + * particular lock and before the corresponding unlocking of that same lock, + * then the thread will read the shared value of that variable from main + * memory after the lock operation, if necessary, and will copy back to main + * memory the value most recently assigned to that variable before the + * unlock operation. + * + * This, in conjunction with the mutual exclusion rules for locks, suffices + * to guarantee that values are correctly transmitted from one thread to + * another through shared variables. + * + * ====> Always keep that in mind when modifying the Channel/ChannelManger + * code. + * + */ + + static final int STATE_OPENING = 1; + static final int STATE_OPEN = 2; + static final int STATE_CLOSED = 4; + + static final int CHANNEL_BUFFER_SIZE = 30000; + + /* + * To achieve correctness, the following rules have to be respected when + * accessing this object: + */ + + // These fields can always be read + final ChannelManager cm; + final ChannelOutputStream stdinStream; + final ChannelInputStream stdoutStream; + final ChannelInputStream stderrStream; + + // These two fields will only be written while the Channel is in state + // STATE_OPENING. + // The code makes sure that the two fields are written out when the state is + // changing to STATE_OPEN. + // Therefore, if you know that the Channel is in state STATE_OPEN, then you + // can read these two fields without synchronizing on the Channel. However, make + // sure that you get the latest values (e.g., flush caches by synchronizing on any + // object). However, to be on the safe side, you can lock the channel. + + int localID = -1; + int remoteID = -1; + + /* + * Make sure that we never send a data/EOF/WindowChange msg after a CLOSE + * msg. + * + * This is a little bit complicated, but we have to do it in that way, since + * we cannot keep a lock on the Channel during the send operation (this + * would block sometimes the receiver thread, and, in extreme cases, can + * lead to a deadlock on both sides of the connection (senders are blocked + * since the receive buffers on the other side are full, and receiver + * threads wait for the senders to finish). It all depends on the + * implementation on the other side. But we cannot make any assumptions, we + * have to assume the worst case. Confused? Just believe me. + */ + + /* + * If you send a message on a channel, then you have to aquire the + * "channelSendLock" and check the "closeMessageSent" flag (this variable + * may only be accessed while holding the "channelSendLock" !!! + * + * BTW: NEVER EVER SEND MESSAGES FROM THE RECEIVE THREAD - see explanation + * above. + */ + + final Object channelSendLock = new Object(); + boolean closeMessageSent = false; + + /* + * Stop memory fragmentation by allocating this often used buffer. + * May only be used while holding the channelSendLock + */ + + final byte[] msgWindowAdjust = new byte[9]; + + // If you access (read or write) any of the following fields, then you have + // to synchronize on the channel. + + int state = STATE_OPENING; + + boolean closeMessageRecv = false; + + /* This is a stupid implementation. At the moment we can only wait + * for one pending request per channel. + */ + int successCounter = 0; + int failedCounter = 0; + + int localWindow = 0; /* locally, we use a small window, < 2^31 */ + long remoteWindow = 0; /* long for readable 2^32 - 1 window support */ + + int localMaxPacketSize = -1; + int remoteMaxPacketSize = -1; + + final byte[] stdoutBuffer = new byte[CHANNEL_BUFFER_SIZE]; + final byte[] stderrBuffer = new byte[CHANNEL_BUFFER_SIZE]; + + int stdoutReadpos = 0; + int stdoutWritepos = 0; + int stderrReadpos = 0; + int stderrWritepos = 0; + + boolean EOF = false; + + Integer exit_status; + + String exit_signal; + + // we keep the x11 cookie so that this channel can be closed when this + // specific x11 forwarding gets stopped + + String hexX11FakeCookie; + + // reasonClosed is special, since we sometimes need to access it + // while holding the channelSendLock. + // We protect it with a private short term lock. + + private final Object reasonClosedLock = new Object(); + private String reasonClosed = null; + + public Channel(ChannelManager cm) + { + this.cm = cm; + + this.localWindow = CHANNEL_BUFFER_SIZE; + this.localMaxPacketSize = 35000 - 1024; // leave enough slack + + this.stdinStream = new ChannelOutputStream(this); + this.stdoutStream = new ChannelInputStream(this, false); + this.stderrStream = new ChannelInputStream(this, true); + } + + /* Methods to allow access from classes outside of this package */ + + public ChannelInputStream getStderrStream() + { + return stderrStream; + } + + public ChannelOutputStream getStdinStream() + { + return stdinStream; + } + + public ChannelInputStream getStdoutStream() + { + return stdoutStream; + } + + public String getExitSignal() + { + synchronized (this) + { + return exit_signal; + } + } + + public Integer getExitStatus() + { + synchronized (this) + { + return exit_status; + } + } + + public String getReasonClosed() + { + synchronized (reasonClosedLock) + { + return reasonClosed; + } + } + + public void setReasonClosed(String reasonClosed) + { + synchronized (reasonClosedLock) + { + if (this.reasonClosed == null) + this.reasonClosed = reasonClosed; + } + } +} diff --git a/lib/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java b/lib/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java index a6d936f..f88522c 100644 --- a/lib/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java +++ b/lib/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java @@ -1,86 +1,86 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.io.InputStream; - -/** - * ChannelInputStream. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ChannelInputStream.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public final class ChannelInputStream extends InputStream -{ - Channel c; - - boolean isClosed = false; - boolean isEOF = false; - boolean extendedFlag = false; - - ChannelInputStream(Channel c, boolean isExtended) - { - this.c = c; - this.extendedFlag = isExtended; - } - - public int available() throws IOException - { - if (isEOF) - return 0; - - int avail = c.cm.getAvailable(c, extendedFlag); - - /* We must not return -1 on EOF */ - - return (avail > 0) ? avail : 0; - } - - public void close() throws IOException - { - isClosed = true; - } - - public int read(byte[] b, int off, int len) throws IOException - { - if (b == null) - throw new NullPointerException(); - - if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) - throw new IndexOutOfBoundsException(); - - if (len == 0) - return 0; - - if (isEOF) - return -1; - - int ret = c.cm.getChannelData(c, extendedFlag, b, off, len); - - if (ret == -1) - { - isEOF = true; - } - - return ret; - } - - public int read(byte[] b) throws IOException - { - return read(b, 0, b.length); - } - - public int read() throws IOException - { - /* Yes, this stream is pure and unbuffered, a single byte read() is slow */ - - final byte b[] = new byte[1]; - - int ret = read(b, 0, 1); - - if (ret != 1) - return -1; - - return b[0] & 0xff; - } -} + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; + +/** + * ChannelInputStream. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ChannelInputStream.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public final class ChannelInputStream extends InputStream +{ + Channel c; + + boolean isClosed = false; + boolean isEOF = false; + boolean extendedFlag = false; + + ChannelInputStream(Channel c, boolean isExtended) + { + this.c = c; + this.extendedFlag = isExtended; + } + + public int available() throws IOException + { + if (isEOF) + return 0; + + int avail = c.cm.getAvailable(c, extendedFlag); + + /* We must not return -1 on EOF */ + + return (avail > 0) ? avail : 0; + } + + public void close() throws IOException + { + isClosed = true; + } + + public int read(byte[] b, int off, int len) throws IOException + { + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return 0; + + if (isEOF) + return -1; + + int ret = c.cm.getChannelData(c, extendedFlag, b, off, len); + + if (ret == -1) + { + isEOF = true; + } + + return ret; + } + + public int read(byte[] b) throws IOException + { + return read(b, 0, b.length); + } + + public int read() throws IOException + { + /* Yes, this stream is pure and unbuffered, a single byte read() is slow */ + + final byte b[] = new byte[1]; + + int ret = read(b, 0, 1); + + if (ret != 1) + return -1; + + return b[0] & 0xff; + } +} diff --git a/lib/src/main/java/com/trilead/ssh2/channel/ChannelManager.java b/lib/src/main/java/com/trilead/ssh2/channel/ChannelManager.java index 432aef5..88beffd 100644 --- a/lib/src/main/java/com/trilead/ssh2/channel/ChannelManager.java +++ b/lib/src/main/java/com/trilead/ssh2/channel/ChannelManager.java @@ -1,1756 +1,1756 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Vector; - -import com.trilead.ssh2.AuthAgentCallback; -import com.trilead.ssh2.ChannelCondition; -import com.trilead.ssh2.log.Logger; -import com.trilead.ssh2.packets.PacketChannelAuthAgentReq; -import com.trilead.ssh2.packets.PacketChannelOpenConfirmation; -import com.trilead.ssh2.packets.PacketChannelOpenFailure; -import com.trilead.ssh2.packets.PacketChannelTrileadPing; -import com.trilead.ssh2.packets.PacketGlobalCancelForwardRequest; -import com.trilead.ssh2.packets.PacketGlobalForwardRequest; -import com.trilead.ssh2.packets.PacketGlobalTrileadPing; -import com.trilead.ssh2.packets.PacketOpenDirectTCPIPChannel; -import com.trilead.ssh2.packets.PacketOpenSessionChannel; -import com.trilead.ssh2.packets.PacketSessionExecCommand; -import com.trilead.ssh2.packets.PacketSessionPtyRequest; -import com.trilead.ssh2.packets.PacketSessionPtyResize; -import com.trilead.ssh2.packets.PacketSessionStartShell; -import com.trilead.ssh2.packets.PacketSessionSubsystemRequest; -import com.trilead.ssh2.packets.PacketSessionX11Request; -import com.trilead.ssh2.packets.Packets; -import com.trilead.ssh2.packets.TypesReader; -import com.trilead.ssh2.transport.MessageHandler; -import com.trilead.ssh2.transport.TransportManager; - -/** - * ChannelManager. Please read the comments in Channel.java. - *

- * Besides the crypto part, this is the core of the library. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ChannelManager.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $ - */ -public class ChannelManager implements MessageHandler -{ - private static final Logger log = Logger.getLogger(ChannelManager.class); - - private HashMap x11_magic_cookies = new HashMap(); - - private TransportManager tm; - - private Vector channels = new Vector(); - private int nextLocalChannel = 100; - private boolean shutdown = false; - private int globalSuccessCounter = 0; - private int globalFailedCounter = 0; - - private HashMap remoteForwardings = new HashMap(); - - private AuthAgentCallback authAgent; - - private Vector listenerThreads = new Vector(); - - private boolean listenerThreadsAllowed = true; - - public ChannelManager(TransportManager tm) - { - this.tm = tm; - tm.registerMessageHandler(this, 80, 100); - } - - private Channel getChannel(int id) - { - synchronized (channels) - { - for (int i = 0; i < channels.size(); i++) - { - Channel c = channels.elementAt(i); - if (c.localID == id) - return c; - } - } - return null; - } - - private void removeChannel(int id) - { - synchronized (channels) - { - for (int i = 0; i < channels.size(); i++) - { - Channel c = channels.elementAt(i); - if (c.localID == id) - { - channels.removeElementAt(i); - break; - } - } - } - } - - private int addChannel(Channel c) - { - synchronized (channels) - { - channels.addElement(c); - return nextLocalChannel++; - } - } - - private void waitUntilChannelOpen(Channel c) throws IOException - { - synchronized (c) - { - while (c.state == Channel.STATE_OPENING) - { - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - } - } - - if (c.state != Channel.STATE_OPEN) - { - removeChannel(c.localID); - - String detail = c.getReasonClosed(); - - if (detail == null) - detail = "state: " + c.state; - - throw new IOException("Could not open channel (" + detail + ")"); - } - } - } - - private final boolean waitForGlobalRequestResult() throws IOException - { - synchronized (channels) - { - while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) - { - if (shutdown) - { - throw new IOException("The connection is being shutdown"); - } - - try - { - channels.wait(); - } - catch (InterruptedException ignore) - { - } - } - - if ((globalFailedCounter == 0) && (globalSuccessCounter == 1)) - return true; - - if ((globalFailedCounter == 1) && (globalSuccessCounter == 0)) - return false; - - throw new IOException("Illegal state. The server sent " + globalSuccessCounter - + " SSH_MSG_REQUEST_SUCCESS and " + globalFailedCounter + " SSH_MSG_REQUEST_FAILURE messages."); - } - } - - private final boolean waitForChannelRequestResult(Channel c) throws IOException - { - synchronized (c) - { - while ((c.successCounter == 0) && (c.failedCounter == 0)) - { - if (c.state != Channel.STATE_OPEN) - { - String detail = c.getReasonClosed(); - - if (detail == null) - detail = "state: " + c.state; - - throw new IOException("This SSH2 channel is not open (" + detail + ")"); - } - - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - } - } - - if ((c.failedCounter == 0) && (c.successCounter == 1)) - return true; - - if ((c.failedCounter == 1) && (c.successCounter == 0)) - return false; - - throw new IOException("Illegal state. The server sent " + c.successCounter - + " SSH_MSG_CHANNEL_SUCCESS and " + c.failedCounter + " SSH_MSG_CHANNEL_FAILURE messages."); - } - } - - public void registerX11Cookie(String hexFakeCookie, X11ServerData data) - { - synchronized (x11_magic_cookies) - { - x11_magic_cookies.put(hexFakeCookie, data); - } - } - - public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) - { - if (hexFakeCookie == null) - throw new IllegalStateException("hexFakeCookie may not be null"); - - synchronized (x11_magic_cookies) - { - x11_magic_cookies.remove(hexFakeCookie); - } - - if (killChannels == false) - return; - - if (log.isEnabled()) - log.log(50, "Closing all X11 channels for the given fake cookie"); - - Vector channel_copy; - - synchronized (channels) - { - channel_copy = (Vector) channels.clone(); - } - - for (int i = 0; i < channel_copy.size(); i++) - { - Channel c = channel_copy.elementAt(i); - - synchronized (c) - { - if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) - continue; - } - - try - { - closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); - } - catch (IOException e) - { - } - } - } - - public X11ServerData checkX11Cookie(String hexFakeCookie) - { - synchronized (x11_magic_cookies) - { - if (hexFakeCookie != null) - return x11_magic_cookies.get(hexFakeCookie); - } - return null; - } - - public void closeAllChannels() - { - if (log.isEnabled()) - log.log(50, "Closing all channels"); - - Vector channel_copy; - - synchronized (channels) - { - channel_copy = (Vector) channels.clone(); - } - - for (int i = 0; i < channel_copy.size(); i++) - { - Channel c = channel_copy.elementAt(i); - try - { - closeChannel(c, "Closing all channels", true); - } - catch (IOException e) - { - } - } - } - - public void closeChannel(Channel c, String reason, boolean force) throws IOException - { - byte msg[] = new byte[5]; - - synchronized (c) - { - if (force) - { - c.state = Channel.STATE_CLOSED; - c.EOF = true; - } - - c.setReasonClosed(reason); - - msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE; - msg[1] = (byte) (c.remoteID >> 24); - msg[2] = (byte) (c.remoteID >> 16); - msg[3] = (byte) (c.remoteID >> 8); - msg[4] = (byte) (c.remoteID); - - c.notifyAll(); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - return; - tm.sendMessage(msg); - c.closeMessageSent = true; - } - - if (log.isEnabled()) - log.log(50, "Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")"); - } - - public void sendEOF(Channel c) throws IOException - { - byte[] msg = new byte[5]; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - return; - - msg[0] = Packets.SSH_MSG_CHANNEL_EOF; - msg[1] = (byte) (c.remoteID >> 24); - msg[2] = (byte) (c.remoteID >> 16); - msg[3] = (byte) (c.remoteID >> 8); - msg[4] = (byte) (c.remoteID); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - return; - tm.sendMessage(msg); - } - - if (log.isEnabled()) - log.log(50, "Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")"); - } - - public void sendOpenConfirmation(Channel c) throws IOException - { - PacketChannelOpenConfirmation pcoc = null; - - synchronized (c) - { - if (c.state != Channel.STATE_OPENING) - return; - - c.state = Channel.STATE_OPEN; - - pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - return; - tm.sendMessage(pcoc.getPayload()); - } - } - - public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException - { - while (len > 0) - { - int thislen = 0; - byte[] msg; - - synchronized (c) - { - while (true) - { - if (c.state == Channel.STATE_CLOSED) - throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); - - if (c.state != Channel.STATE_OPEN) - throw new IOException("SSH channel in strange state. (" + c.state + ")"); - - if (c.remoteWindow != 0) - break; - - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - } - } - - /* len > 0, no sign extension can happen when comparing */ - - thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; - - int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9); - - /* The worst case scenario =) a true bottleneck */ - - if (estimatedMaxDataLen <= 0) - { - estimatedMaxDataLen = 1; - } - - if (thislen > estimatedMaxDataLen) - thislen = estimatedMaxDataLen; - - c.remoteWindow -= thislen; - - msg = new byte[1 + 8 + thislen]; - - msg[0] = Packets.SSH_MSG_CHANNEL_DATA; - msg[1] = (byte) (c.remoteID >> 24); - msg[2] = (byte) (c.remoteID >> 16); - msg[3] = (byte) (c.remoteID >> 8); - msg[4] = (byte) (c.remoteID); - msg[5] = (byte) (thislen >> 24); - msg[6] = (byte) (thislen >> 16); - msg[7] = (byte) (thislen >> 8); - msg[8] = (byte) (thislen); - - System.arraycopy(buffer, pos, msg, 9, thislen); - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent == true) - throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); - - tm.sendMessage(msg); - } - - pos += thislen; - len -= thislen; - } - } - - public int requestGlobalForward(String bindAddress, int bindPort, String targetAddress, int targetPort) - throws IOException - { - RemoteForwardingData rfd = new RemoteForwardingData(); - - rfd.bindAddress = bindAddress; - rfd.bindPort = bindPort; - rfd.targetAddress = targetAddress; - rfd.targetPort = targetPort; - - synchronized (remoteForwardings) - { - Integer key = Integer.valueOf(bindPort); - - if (remoteForwardings.get(key) != null) - { - throw new IOException("There is already a forwarding for remote port " + bindPort); - } - - remoteForwardings.put(key, rfd); - } - - synchronized (channels) - { - globalSuccessCounter = globalFailedCounter = 0; - } - - PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort); - tm.sendMessage(pgf.getPayload()); - - if (log.isEnabled()) - log.log(50, "Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")"); - - try - { - if (waitForGlobalRequestResult() == false) - throw new IOException("The server denied the request (did you enable port forwarding?)"); - } - catch (IOException e) - { - synchronized (remoteForwardings) - { - remoteForwardings.remove(rfd); - } - throw e; - } - - return bindPort; - } - - public void requestCancelGlobalForward(int bindPort) throws IOException - { - RemoteForwardingData rfd = null; - - synchronized (remoteForwardings) - { - rfd = remoteForwardings.get(Integer.valueOf(bindPort)); - - if (rfd == null) - throw new IOException("Sorry, there is no known remote forwarding for remote port " + bindPort); - } - - synchronized (channels) - { - globalSuccessCounter = globalFailedCounter = 0; - } - - PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, - rfd.bindPort); - tm.sendMessage(pgcf.getPayload()); - - if (log.isEnabled()) - log.log(50, "Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); - - try - { - if (waitForGlobalRequestResult() == false) - throw new IOException("The server denied the request."); - } - finally - { - synchronized (remoteForwardings) - { - /* Only now we are sure that no more forwarded connections will arrive */ - remoteForwardings.remove(rfd); - } - } - - } - - /** - * @param agent - * @throws IOException - */ - public boolean requestChannelAgentForwarding(Channel c, AuthAgentCallback authAgent) throws IOException { - synchronized (this) - { - if (this.authAgent != null) - throw new IllegalStateException("Auth agent already exists"); - - this.authAgent = authAgent; - } - - synchronized (channels) - { - globalSuccessCounter = globalFailedCounter = 0; - } - - if (log.isEnabled()) - log.log(50, "Requesting agent forwarding"); - - PacketChannelAuthAgentReq aar = new PacketChannelAuthAgentReq(c.remoteID); - tm.sendMessage(aar.getPayload()); - - if (waitForChannelRequestResult(c) == false) { - authAgent = null; - return false; - } - - return true; - } - - public void registerThread(IChannelWorkerThread thr) throws IOException - { - synchronized (listenerThreads) - { - if (listenerThreadsAllowed == false) - throw new IOException("Too late, this connection is closed."); - listenerThreads.addElement(thr); - } - } - - public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address, - int originator_port) throws IOException - { - Channel c = new Channel(this); - - synchronized (c) - { - c.localID = addChannel(c); - // end of synchronized block forces writing out to main memory - } - - PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow, - c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port); - - tm.sendMessage(dtc.getPayload()); - - waitUntilChannelOpen(c); - - return c; - } - - public Channel openSessionChannel() throws IOException - { - Channel c = new Channel(this); - - synchronized (c) - { - c.localID = addChannel(c); - // end of synchronized block forces the writing out to main memory - } - - if (log.isEnabled()) - log.log(50, "Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")"); - - PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize); - tm.sendMessage(smo.getPayload()); - - waitUntilChannelOpen(c); - - return c; - } - - public void requestGlobalTrileadPing() throws IOException - { - synchronized (channels) - { - globalSuccessCounter = globalFailedCounter = 0; - } - - PacketGlobalTrileadPing pgtp = new PacketGlobalTrileadPing(); - - tm.sendMessage(pgtp.getPayload()); - - if (log.isEnabled()) - log.log(50, "Sending SSH_MSG_GLOBAL_REQUEST 'trilead-ping'."); - - try - { - if (waitForGlobalRequestResult() == true) - throw new IOException("Your server is alive - but buggy. " - + "It replied with SSH_MSG_REQUEST_SUCCESS when it actually should not."); - - } - catch (IOException e) - { - throw (IOException) new IOException("The ping request failed.").initCause(e); - } - } - - public void requestChannelTrileadPing(Channel c) throws IOException - { - PacketChannelTrileadPing pctp; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")"); - - pctp = new PacketChannelTrileadPing(c.remoteID); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(pctp.getPayload()); - } - - try - { - if (waitForChannelRequestResult(c) == true) - throw new IOException("Your server is alive - but buggy. " - + "It replied with SSH_MSG_SESSION_SUCCESS when it actually should not."); - - } - catch (IOException e) - { - throw (IOException) new IOException("The ping request failed.").initCause(e); - } - } - - public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters, - int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException - { - PacketSessionPtyRequest spr; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); - - spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters, - term_width_pixels, term_height_pixels, terminal_modes); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(spr.getPayload()); - } - - try - { - if (waitForChannelRequestResult(c) == false) - throw new IOException("The server denied the request."); - } - catch (IOException e) - { - throw (IOException) new IOException("PTY request failed").initCause(e); - } - } - - - public void resizePTY(Channel c, int term_width_characters, int term_height_characters, - int term_width_pixels, int term_height_pixels) throws IOException { - PacketSessionPtyResize spr; - - synchronized (c) { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request PTY on this channel (" - + c.getReasonClosed() + ")"); - - spr = new PacketSessionPtyResize(c.remoteID, term_width_characters, term_height_characters, - term_width_pixels, term_height_pixels); - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) { - if (c.closeMessageSent) - throw new IOException("Cannot request PTY on this channel (" - + c.getReasonClosed() + ")"); - tm.sendMessage(spr.getPayload()); - } - } - - - public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol, - String x11AuthenticationCookie, int x11ScreenNumber) throws IOException - { - PacketSessionX11Request psr; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); - - psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol, - x11AuthenticationCookie, x11ScreenNumber); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(psr.getPayload()); - } - - if (log.isEnabled()) - log.log(50, "Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")"); - - try - { - if (waitForChannelRequestResult(c) == false) - throw new IOException("The server denied the request."); - } - catch (IOException e) - { - throw (IOException) new IOException("The X11 request failed.").initCause(e); - } - } - - public void requestSubSystem(Channel c, String subSystemName) throws IOException - { - PacketSessionSubsystemRequest ssr; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); - - ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(ssr.getPayload()); - } - - try - { - if (waitForChannelRequestResult(c) == false) - throw new IOException("The server denied the request."); - } - catch (IOException e) - { - throw (IOException) new IOException("The subsystem request failed.").initCause(e); - } - } - - public void requestExecCommand(Channel c, String cmd) throws IOException - { - PacketSessionExecCommand sm; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); - - sm = new PacketSessionExecCommand(c.remoteID, true, cmd); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(sm.getPayload()); - } - - if (log.isEnabled()) - log.log(50, "Executing command (channel " + c.localID + ", '" + cmd + "')"); - - try - { - if (waitForChannelRequestResult(c) == false) - throw new IOException("The server denied the request."); - } - catch (IOException e) - { - throw (IOException) new IOException("The execute request failed.").initCause(e); - } - } - - public void requestShell(Channel c) throws IOException - { - PacketSessionStartShell sm; - - synchronized (c) - { - if (c.state != Channel.STATE_OPEN) - throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); - - sm = new PacketSessionStartShell(c.remoteID, true); - - c.successCounter = c.failedCounter = 0; - } - - synchronized (c.channelSendLock) - { - if (c.closeMessageSent) - throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); - tm.sendMessage(sm.getPayload()); - } - - try - { - if (waitForChannelRequestResult(c) == false) - throw new IOException("The server denied the request."); - } - catch (IOException e) - { - throw (IOException) new IOException("The shell request failed.").initCause(e); - } - } - - public void msgChannelExtendedData(byte[] msg, int msglen) throws IOException - { - if (msglen <= 13) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id); - - if (dataType != Packets.SSH_EXTENDED_DATA_STDERR) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")"); - - if (len != (msglen - 13)) - throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13) - + ", got " + len + ")"); - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")"); - - synchronized (c) - { - if (c.state == Channel.STATE_CLOSED) - return; // ignore - - if (c.state != Channel.STATE_OPEN) - throw new IOException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state (" - + c.state + ")"); - - if (c.localWindow < len) - throw new IOException("Remote sent too much data, does not fit into window."); - - c.localWindow -= len; - - System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len); - c.stderrWritepos += len; - - c.notifyAll(); - } - } - - /** - * Wait until for a condition. - * - * @param c - * Channel - * @param timeout - * in ms, 0 means no timeout. - * @param condition_mask - * minimum event mask - * @return all current events - * - */ - public int waitForCondition(Channel c, long timeout, int condition_mask) - { - long end_time = 0; - boolean end_time_set = false; - - synchronized (c) - { - while (true) - { - int current_cond = 0; - - int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; - int stderrAvail = c.stderrWritepos - c.stderrReadpos; - - if (stdoutAvail > 0) - current_cond = current_cond | ChannelCondition.STDOUT_DATA; - - if (stderrAvail > 0) - current_cond = current_cond | ChannelCondition.STDERR_DATA; - - if (c.EOF) - current_cond = current_cond | ChannelCondition.EOF; - - if (c.getExitStatus() != null) - current_cond = current_cond | ChannelCondition.EXIT_STATUS; - - if (c.getExitSignal() != null) - current_cond = current_cond | ChannelCondition.EXIT_SIGNAL; - - if (c.state == Channel.STATE_CLOSED) - return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF; - - if ((current_cond & condition_mask) != 0) - return current_cond; - - if (timeout > 0) - { - if (!end_time_set) - { - end_time = System.currentTimeMillis() + timeout; - end_time_set = true; - } - else - { - timeout = end_time - System.currentTimeMillis(); - - if (timeout <= 0) - return current_cond | ChannelCondition.TIMEOUT; - } - } - - try - { - if (timeout > 0) - c.wait(timeout); - else - c.wait(); - } - catch (InterruptedException e) - { - } - } - } - } - - public int getAvailable(Channel c, boolean extended) throws IOException - { - synchronized (c) - { - int avail; - - if (extended) - avail = c.stderrWritepos - c.stderrReadpos; - else - avail = c.stdoutWritepos - c.stdoutReadpos; - - return ((avail > 0) ? avail : (c.EOF ? -1 : 0)); - } - } - - public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException - { - int copylen = 0; - int increment = 0; - int remoteID = 0; - int localID = 0; - - synchronized (c) - { - int stdoutAvail = 0; - int stderrAvail = 0; - - while (true) - { - /* - * Data available? We have to return remaining data even if the - * channel is already closed. - */ - - stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; - stderrAvail = c.stderrWritepos - c.stderrReadpos; - - if ((!extended) && (stdoutAvail != 0)) - break; - - if ((extended) && (stderrAvail != 0)) - break; - - /* Do not wait if more data will never arrive (EOF or CLOSED) */ - - if ((c.EOF) || (c.state != Channel.STATE_OPEN)) - return -1; - - try - { - c.wait(); - } - catch (InterruptedException ignore) - { - } - } - - /* OK, there is some data. Return it. */ - - if (!extended) - { - copylen = (stdoutAvail > len) ? len : stdoutAvail; - System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); - c.stdoutReadpos += copylen; - - if (c.stdoutReadpos != c.stdoutWritepos) - - System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos - - c.stdoutReadpos); - - c.stdoutWritepos -= c.stdoutReadpos; - c.stdoutReadpos = 0; - } - else - { - copylen = (stderrAvail > len) ? len : stderrAvail; - System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen); - c.stderrReadpos += copylen; - - if (c.stderrReadpos != c.stderrWritepos) - - System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos - - c.stderrReadpos); - - c.stderrWritepos -= c.stderrReadpos; - c.stderrReadpos = 0; - } - - if (c.state != Channel.STATE_OPEN) - return copylen; - - if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) - { - int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, Channel.CHANNEL_BUFFER_SIZE - - c.stderrWritepos); - - increment = minFreeSpace - c.localWindow; - c.localWindow = minFreeSpace; - } - - remoteID = c.remoteID; /* read while holding the lock */ - localID = c.localID; /* read while holding the lock */ - } - - /* - * If a consumer reads stdout and stdin in parallel, we may end up with - * sending two msgWindowAdjust messages. Luckily, it - * does not matter in which order they arrive at the server. - */ - - if (increment > 0) - { - if (log.isEnabled()) - log.log(80, "Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); - - synchronized (c.channelSendLock) - { - byte[] msg = c.msgWindowAdjust; - - msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST; - msg[1] = (byte) (remoteID >> 24); - msg[2] = (byte) (remoteID >> 16); - msg[3] = (byte) (remoteID >> 8); - msg[4] = (byte) (remoteID); - msg[5] = (byte) (increment >> 24); - msg[6] = (byte) (increment >> 16); - msg[7] = (byte) (increment >> 8); - msg[8] = (byte) (increment); - - if (c.closeMessageSent == false) - tm.sendMessage(msg); - } - } - - return copylen; - } - - public void msgChannelData(byte[] msg, int msglen) throws IOException - { - if (msglen <= 9) - throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id); - - if (len != (msglen - 9)) - throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got " - + len + ")"); - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); - - synchronized (c) - { - if (c.state == Channel.STATE_CLOSED) - return; // ignore - - if (c.state != Channel.STATE_OPEN) - throw new IOException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")"); - - if (c.localWindow < len) - throw new IOException("Remote sent too much data, does not fit into window."); - - c.localWindow -= len; - - System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len); - c.stdoutWritepos += len; - - c.notifyAll(); - } - } - - public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException - { - if (msglen != 9) - throw new IOException("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id); - - synchronized (c) - { - final long huge = 0xFFFFffffL; /* 2^32 - 1 */ - - c.remoteWindow += (windowChange & huge); /* avoid sign extension */ - - /* TODO - is this a good heuristic? */ - - if ((c.remoteWindow > huge)) - c.remoteWindow = huge; - - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); - } - - public void msgChannelOpen(byte[] msg, int msglen) throws IOException - { - TypesReader tr = new TypesReader(msg, 0, msglen); - - tr.readByte(); // skip packet type - String channelType = tr.readString(); - int remoteID = tr.readUINT32(); /* sender channel */ - int remoteWindow = tr.readUINT32(); /* initial window size */ - int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */ - - if ("x11".equals(channelType)) - { - synchronized (x11_magic_cookies) - { - /* If we did not request X11 forwarding, then simply ignore this bogus request. */ - - if (x11_magic_cookies.size() == 0) - { - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, - Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", ""); - - tm.sendAsynchronousMessage(pcof.getPayload()); - - if (log.isEnabled()) - log.log(20, "Unexpected X11 request, denying it!"); - - return; - } - } - - String remoteOriginatorAddress = tr.readString(); - int remoteOriginatorPort = tr.readUINT32(); - - Channel c = new Channel(this); - - synchronized (c) - { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - } - - /* - * The open confirmation message will be sent from another thread - */ - - RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort); - rxat.setDaemon(true); - rxat.start(); - - return; - } - - if ("forwarded-tcpip".equals(channelType)) - { - String remoteConnectedAddress = tr.readString(); /* address that was connected */ - int remoteConnectedPort = tr.readUINT32(); /* port that was connected */ - String remoteOriginatorAddress = tr.readString(); /* originator IP address */ - int remoteOriginatorPort = tr.readUINT32(); /* originator port */ - - RemoteForwardingData rfd = null; - - synchronized (remoteForwardings) - { - rfd = remoteForwardings.get(Integer.valueOf(remoteConnectedPort)); - } - - if (rfd == null) - { - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, - Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, - "No thanks, unknown port in forwarded-tcpip request", ""); - - /* Always try to be polite. */ - - tm.sendAsynchronousMessage(pcof.getPayload()); - - if (log.isEnabled()) - log.log(20, "Unexpected forwarded-tcpip request, denying it!"); - - return; - } - - Channel c = new Channel(this); - - synchronized (c) - { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - } - - /* - * The open confirmation message will be sent from another thread. - */ - - RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort, - remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); - - rat.setDaemon(true); - rat.start(); - - return; - } - - if ("auth-agent@openssh.com".equals(channelType)) { - Channel c = new Channel(this); - - synchronized (c) - { - c.remoteID = remoteID; - c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ - c.remoteMaxPacketSize = remoteMaxPacketSize; - c.localID = addChannel(c); - } - - AuthAgentForwardThread aat = new AuthAgentForwardThread(c, authAgent); - - aat.setDaemon(true); - aat.start(); - - return; - } - - /* Tell the server that we have no idea what it is talking about */ - - PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, - "Unknown channel type", ""); - - tm.sendAsynchronousMessage(pcof.getPayload()); - - if (log.isEnabled()) - log.log(20, "The peer tried to open an unsupported channel type (" + channelType + ")"); - } - - public void msgChannelRequest(byte[] msg, int msglen) throws IOException - { - TypesReader tr = new TypesReader(msg, 0, msglen); - - tr.readByte(); // skip packet type - int id = tr.readUINT32(); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); - - String type = tr.readString("US-ASCII"); - boolean wantReply = tr.readBoolean(); - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); - - if (type.equals("exit-status")) - { - if (wantReply != false) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); - - int exit_status = tr.readUINT32(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - synchronized (c) - { - c.exit_status = Integer.valueOf(exit_status); - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); - - return; - } - - if (type.equals("exit-signal")) - { - if (wantReply != false) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); - - String signame = tr.readString("US-ASCII"); - tr.readBoolean(); - tr.readString(); - tr.readString(); - - if (tr.remain() != 0) - throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); - - synchronized (c) - { - c.exit_signal = signame; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); - - return; - } - - /* We simply ignore unknown channel requests, however, if the server wants a reply, - * then we signal that we have no idea what it is about. - */ - - if (wantReply) - { - byte[] reply = new byte[5]; - - reply[0] = Packets.SSH_MSG_CHANNEL_FAILURE; - reply[1] = (byte) (c.remoteID >> 24); - reply[2] = (byte) (c.remoteID >> 16); - reply[3] = (byte) (c.remoteID >> 8); - reply[4] = (byte) (c.remoteID); - - tm.sendAsynchronousMessage(reply); - } - - if (log.isEnabled()) - log.log(50, "Channel request '" + type + "' is not known, ignoring it"); - } - - public void msgChannelEOF(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id); - - synchronized (c) - { - c.EOF = true; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_EOF (channel " + id + ")"); - } - - public void msgChannelClose(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id); - - synchronized (c) - { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("Close requested by remote"); - c.closeMessageRecv = true; - - removeChannel(c.localID); - - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); - } - - public void msgChannelSuccess(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); - - synchronized (c) - { - c.successCounter++; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); - } - - public void msgChannelFailure(byte[] msg, int msglen) throws IOException - { - if (msglen != 5) - throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")"); - - int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); - - synchronized (c) - { - c.failedCounter++; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); - } - - public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException - { - PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen); - - Channel c = getChannel(sm.recipientChannelID); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " - + sm.recipientChannelID); - - synchronized (c) - { - if (c.state != Channel.STATE_OPENING) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " - + sm.recipientChannelID); - - c.remoteID = sm.senderChannelID; - c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */ - c.remoteMaxPacketSize = sm.maxPacketSize; - c.state = Channel.STATE_OPEN; - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: " - + sm.senderChannelID + ")"); - } - - public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException - { - if (msglen < 5) - throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")"); - - TypesReader tr = new TypesReader(msg, 0, msglen); - - tr.readByte(); // skip packet type - int id = tr.readUINT32(); /* sender channel */ - - Channel c = getChannel(id); - - if (c == null) - throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); - - int reasonCode = tr.readUINT32(); - String description = tr.readString("UTF-8"); - - String reasonCodeSymbolicName = null; - - switch (reasonCode) - { - case 1: - reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED"; - break; - case 2: - reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED"; - break; - case 3: - reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE"; - break; - case 4: - reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE"; - break; - default: - reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")"; - } - - StringBuffer descriptionBuffer = new StringBuffer(); - descriptionBuffer.append(description); - - for (int i = 0; i < descriptionBuffer.length(); i++) - { - char cc = descriptionBuffer.charAt(i); - - if ((cc >= 32) && (cc <= 126)) - continue; - descriptionBuffer.setCharAt(i, '\uFFFD'); - } - - synchronized (c) - { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("The server refused to open the channel (" + reasonCodeSymbolicName + ", '" - + descriptionBuffer.toString() + "')"); - c.notifyAll(); - } - - if (log.isEnabled()) - log.log(50, "Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); - } - - public void msgGlobalRequest(byte[] msg, int msglen) throws IOException - { - /* Currently we do not support any kind of global request */ - - TypesReader tr = new TypesReader(msg, 0, msglen); - - tr.readByte(); // skip packet type - String requestName = tr.readString(); - boolean wantReply = tr.readBoolean(); - - if (wantReply) - { - byte[] reply_failure = new byte[1]; - reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; - - tm.sendAsynchronousMessage(reply_failure); - } - - /* We do not clean up the requestName String - that is OK for debug */ - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); - } - - public void msgGlobalSuccess() throws IOException - { - synchronized (channels) - { - globalSuccessCounter++; - channels.notifyAll(); - } - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_REQUEST_SUCCESS"); - } - - public void msgGlobalFailure() throws IOException - { - synchronized (channels) - { - globalFailedCounter++; - channels.notifyAll(); - } - - if (log.isEnabled()) - log.log(80, "Got SSH_MSG_REQUEST_FAILURE"); - } - - public void handleMessage(byte[] msg, int msglen) throws IOException - { - if (msg == null) - { - if (log.isEnabled()) - log.log(50, "HandleMessage: got shutdown"); - - synchronized (listenerThreads) - { - for (int i = 0; i < listenerThreads.size(); i++) - { - IChannelWorkerThread lat = listenerThreads.elementAt(i); - lat.stopWorking(); - } - listenerThreadsAllowed = false; - } - - synchronized (channels) - { - shutdown = true; - - for (int i = 0; i < channels.size(); i++) - { - Channel c = channels.elementAt(i); - synchronized (c) - { - c.EOF = true; - c.state = Channel.STATE_CLOSED; - c.setReasonClosed("The connection is being shutdown"); - c.closeMessageRecv = true; /* - * You never know, perhaps - * we are waiting for a - * pending close message - * from the server... - */ - c.notifyAll(); - } - } - /* Works with J2ME */ - channels.setSize(0); - channels.trimToSize(); - channels.notifyAll(); /* Notify global response waiters */ - return; - } - } - - switch (msg[0]) - { - case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: - msgChannelOpenConfirmation(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST: - msgChannelWindowAdjust(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_DATA: - msgChannelData(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA: - msgChannelExtendedData(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_REQUEST: - msgChannelRequest(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_EOF: - msgChannelEOF(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_OPEN: - msgChannelOpen(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_CLOSE: - msgChannelClose(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_SUCCESS: - msgChannelSuccess(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_FAILURE: - msgChannelFailure(msg, msglen); - break; - case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE: - msgChannelOpenFailure(msg, msglen); - break; - case Packets.SSH_MSG_GLOBAL_REQUEST: - msgGlobalRequest(msg, msglen); - break; - case Packets.SSH_MSG_REQUEST_SUCCESS: - msgGlobalSuccess(); - break; - case Packets.SSH_MSG_REQUEST_FAILURE: - msgGlobalFailure(); - break; - default: - throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff)); - } - } -} + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Vector; + +import com.trilead.ssh2.AuthAgentCallback; +import com.trilead.ssh2.ChannelCondition; +import com.trilead.ssh2.log.Logger; +import com.trilead.ssh2.packets.PacketChannelAuthAgentReq; +import com.trilead.ssh2.packets.PacketChannelOpenConfirmation; +import com.trilead.ssh2.packets.PacketChannelOpenFailure; +import com.trilead.ssh2.packets.PacketChannelTrileadPing; +import com.trilead.ssh2.packets.PacketGlobalCancelForwardRequest; +import com.trilead.ssh2.packets.PacketGlobalForwardRequest; +import com.trilead.ssh2.packets.PacketGlobalTrileadPing; +import com.trilead.ssh2.packets.PacketOpenDirectTCPIPChannel; +import com.trilead.ssh2.packets.PacketOpenSessionChannel; +import com.trilead.ssh2.packets.PacketSessionExecCommand; +import com.trilead.ssh2.packets.PacketSessionPtyRequest; +import com.trilead.ssh2.packets.PacketSessionPtyResize; +import com.trilead.ssh2.packets.PacketSessionStartShell; +import com.trilead.ssh2.packets.PacketSessionSubsystemRequest; +import com.trilead.ssh2.packets.PacketSessionX11Request; +import com.trilead.ssh2.packets.Packets; +import com.trilead.ssh2.packets.TypesReader; +import com.trilead.ssh2.transport.MessageHandler; +import com.trilead.ssh2.transport.TransportManager; + +/** + * ChannelManager. Please read the comments in Channel.java. + *

+ * Besides the crypto part, this is the core of the library. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ChannelManager.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $ + */ +public class ChannelManager implements MessageHandler +{ + private static final Logger log = Logger.getLogger(ChannelManager.class); + + private HashMap x11_magic_cookies = new HashMap(); + + private TransportManager tm; + + private Vector channels = new Vector(); + private int nextLocalChannel = 100; + private boolean shutdown = false; + private int globalSuccessCounter = 0; + private int globalFailedCounter = 0; + + private HashMap remoteForwardings = new HashMap(); + + private AuthAgentCallback authAgent; + + private Vector listenerThreads = new Vector(); + + private boolean listenerThreadsAllowed = true; + + public ChannelManager(TransportManager tm) + { + this.tm = tm; + tm.registerMessageHandler(this, 80, 100); + } + + private Channel getChannel(int id) + { + synchronized (channels) + { + for (int i = 0; i < channels.size(); i++) + { + Channel c = channels.elementAt(i); + if (c.localID == id) + return c; + } + } + return null; + } + + private void removeChannel(int id) + { + synchronized (channels) + { + for (int i = 0; i < channels.size(); i++) + { + Channel c = channels.elementAt(i); + if (c.localID == id) + { + channels.removeElementAt(i); + break; + } + } + } + } + + private int addChannel(Channel c) + { + synchronized (channels) + { + channels.addElement(c); + return nextLocalChannel++; + } + } + + private void waitUntilChannelOpen(Channel c) throws IOException + { + synchronized (c) + { + while (c.state == Channel.STATE_OPENING) + { + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + } + } + + if (c.state != Channel.STATE_OPEN) + { + removeChannel(c.localID); + + String detail = c.getReasonClosed(); + + if (detail == null) + detail = "state: " + c.state; + + throw new IOException("Could not open channel (" + detail + ")"); + } + } + } + + private final boolean waitForGlobalRequestResult() throws IOException + { + synchronized (channels) + { + while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) + { + if (shutdown) + { + throw new IOException("The connection is being shutdown"); + } + + try + { + channels.wait(); + } + catch (InterruptedException ignore) + { + } + } + + if ((globalFailedCounter == 0) && (globalSuccessCounter == 1)) + return true; + + if ((globalFailedCounter == 1) && (globalSuccessCounter == 0)) + return false; + + throw new IOException("Illegal state. The server sent " + globalSuccessCounter + + " SSH_MSG_REQUEST_SUCCESS and " + globalFailedCounter + " SSH_MSG_REQUEST_FAILURE messages."); + } + } + + private final boolean waitForChannelRequestResult(Channel c) throws IOException + { + synchronized (c) + { + while ((c.successCounter == 0) && (c.failedCounter == 0)) + { + if (c.state != Channel.STATE_OPEN) + { + String detail = c.getReasonClosed(); + + if (detail == null) + detail = "state: " + c.state; + + throw new IOException("This SSH2 channel is not open (" + detail + ")"); + } + + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + } + } + + if ((c.failedCounter == 0) && (c.successCounter == 1)) + return true; + + if ((c.failedCounter == 1) && (c.successCounter == 0)) + return false; + + throw new IOException("Illegal state. The server sent " + c.successCounter + + " SSH_MSG_CHANNEL_SUCCESS and " + c.failedCounter + " SSH_MSG_CHANNEL_FAILURE messages."); + } + } + + public void registerX11Cookie(String hexFakeCookie, X11ServerData data) + { + synchronized (x11_magic_cookies) + { + x11_magic_cookies.put(hexFakeCookie, data); + } + } + + public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) + { + if (hexFakeCookie == null) + throw new IllegalStateException("hexFakeCookie may not be null"); + + synchronized (x11_magic_cookies) + { + x11_magic_cookies.remove(hexFakeCookie); + } + + if (killChannels == false) + return; + + if (log.isEnabled()) + log.log(50, "Closing all X11 channels for the given fake cookie"); + + Vector channel_copy; + + synchronized (channels) + { + channel_copy = (Vector) channels.clone(); + } + + for (int i = 0; i < channel_copy.size(); i++) + { + Channel c = channel_copy.elementAt(i); + + synchronized (c) + { + if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) + continue; + } + + try + { + closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); + } + catch (IOException e) + { + } + } + } + + public X11ServerData checkX11Cookie(String hexFakeCookie) + { + synchronized (x11_magic_cookies) + { + if (hexFakeCookie != null) + return x11_magic_cookies.get(hexFakeCookie); + } + return null; + } + + public void closeAllChannels() + { + if (log.isEnabled()) + log.log(50, "Closing all channels"); + + Vector channel_copy; + + synchronized (channels) + { + channel_copy = (Vector) channels.clone(); + } + + for (int i = 0; i < channel_copy.size(); i++) + { + Channel c = channel_copy.elementAt(i); + try + { + closeChannel(c, "Closing all channels", true); + } + catch (IOException e) + { + } + } + } + + public void closeChannel(Channel c, String reason, boolean force) throws IOException + { + byte msg[] = new byte[5]; + + synchronized (c) + { + if (force) + { + c.state = Channel.STATE_CLOSED; + c.EOF = true; + } + + c.setReasonClosed(reason); + + msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE; + msg[1] = (byte) (c.remoteID >> 24); + msg[2] = (byte) (c.remoteID >> 16); + msg[3] = (byte) (c.remoteID >> 8); + msg[4] = (byte) (c.remoteID); + + c.notifyAll(); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + return; + tm.sendMessage(msg); + c.closeMessageSent = true; + } + + if (log.isEnabled()) + log.log(50, "Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")"); + } + + public void sendEOF(Channel c) throws IOException + { + byte[] msg = new byte[5]; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + return; + + msg[0] = Packets.SSH_MSG_CHANNEL_EOF; + msg[1] = (byte) (c.remoteID >> 24); + msg[2] = (byte) (c.remoteID >> 16); + msg[3] = (byte) (c.remoteID >> 8); + msg[4] = (byte) (c.remoteID); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + return; + tm.sendMessage(msg); + } + + if (log.isEnabled()) + log.log(50, "Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")"); + } + + public void sendOpenConfirmation(Channel c) throws IOException + { + PacketChannelOpenConfirmation pcoc = null; + + synchronized (c) + { + if (c.state != Channel.STATE_OPENING) + return; + + c.state = Channel.STATE_OPEN; + + pcoc = new PacketChannelOpenConfirmation(c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + return; + tm.sendMessage(pcoc.getPayload()); + } + } + + public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException + { + while (len > 0) + { + int thislen = 0; + byte[] msg; + + synchronized (c) + { + while (true) + { + if (c.state == Channel.STATE_CLOSED) + throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); + + if (c.state != Channel.STATE_OPEN) + throw new IOException("SSH channel in strange state. (" + c.state + ")"); + + if (c.remoteWindow != 0) + break; + + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + } + } + + /* len > 0, no sign extension can happen when comparing */ + + thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; + + int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9); + + /* The worst case scenario =) a true bottleneck */ + + if (estimatedMaxDataLen <= 0) + { + estimatedMaxDataLen = 1; + } + + if (thislen > estimatedMaxDataLen) + thislen = estimatedMaxDataLen; + + c.remoteWindow -= thislen; + + msg = new byte[1 + 8 + thislen]; + + msg[0] = Packets.SSH_MSG_CHANNEL_DATA; + msg[1] = (byte) (c.remoteID >> 24); + msg[2] = (byte) (c.remoteID >> 16); + msg[3] = (byte) (c.remoteID >> 8); + msg[4] = (byte) (c.remoteID); + msg[5] = (byte) (thislen >> 24); + msg[6] = (byte) (thislen >> 16); + msg[7] = (byte) (thislen >> 8); + msg[8] = (byte) (thislen); + + System.arraycopy(buffer, pos, msg, 9, thislen); + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent == true) + throw new IOException("SSH channel is closed. (" + c.getReasonClosed() + ")"); + + tm.sendMessage(msg); + } + + pos += thislen; + len -= thislen; + } + } + + public int requestGlobalForward(String bindAddress, int bindPort, String targetAddress, int targetPort) + throws IOException + { + RemoteForwardingData rfd = new RemoteForwardingData(); + + rfd.bindAddress = bindAddress; + rfd.bindPort = bindPort; + rfd.targetAddress = targetAddress; + rfd.targetPort = targetPort; + + synchronized (remoteForwardings) + { + Integer key = Integer.valueOf(bindPort); + + if (remoteForwardings.get(key) != null) + { + throw new IOException("There is already a forwarding for remote port " + bindPort); + } + + remoteForwardings.put(key, rfd); + } + + synchronized (channels) + { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort); + tm.sendMessage(pgf.getPayload()); + + if (log.isEnabled()) + log.log(50, "Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")"); + + try + { + if (waitForGlobalRequestResult() == false) + throw new IOException("The server denied the request (did you enable port forwarding?)"); + } + catch (IOException e) + { + synchronized (remoteForwardings) + { + remoteForwardings.remove(rfd); + } + throw e; + } + + return bindPort; + } + + public void requestCancelGlobalForward(int bindPort) throws IOException + { + RemoteForwardingData rfd = null; + + synchronized (remoteForwardings) + { + rfd = remoteForwardings.get(Integer.valueOf(bindPort)); + + if (rfd == null) + throw new IOException("Sorry, there is no known remote forwarding for remote port " + bindPort); + } + + synchronized (channels) + { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, + rfd.bindPort); + tm.sendMessage(pgcf.getPayload()); + + if (log.isEnabled()) + log.log(50, "Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); + + try + { + if (waitForGlobalRequestResult() == false) + throw new IOException("The server denied the request."); + } + finally + { + synchronized (remoteForwardings) + { + /* Only now we are sure that no more forwarded connections will arrive */ + remoteForwardings.remove(rfd); + } + } + + } + + /** + * @param agent + * @throws IOException + */ + public boolean requestChannelAgentForwarding(Channel c, AuthAgentCallback authAgent) throws IOException { + synchronized (this) + { + if (this.authAgent != null) + throw new IllegalStateException("Auth agent already exists"); + + this.authAgent = authAgent; + } + + synchronized (channels) + { + globalSuccessCounter = globalFailedCounter = 0; + } + + if (log.isEnabled()) + log.log(50, "Requesting agent forwarding"); + + PacketChannelAuthAgentReq aar = new PacketChannelAuthAgentReq(c.remoteID); + tm.sendMessage(aar.getPayload()); + + if (waitForChannelRequestResult(c) == false) { + authAgent = null; + return false; + } + + return true; + } + + public void registerThread(IChannelWorkerThread thr) throws IOException + { + synchronized (listenerThreads) + { + if (listenerThreadsAllowed == false) + throw new IOException("Too late, this connection is closed."); + listenerThreads.addElement(thr); + } + } + + public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address, + int originator_port) throws IOException + { + Channel c = new Channel(this); + + synchronized (c) + { + c.localID = addChannel(c); + // end of synchronized block forces writing out to main memory + } + + PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow, + c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port); + + tm.sendMessage(dtc.getPayload()); + + waitUntilChannelOpen(c); + + return c; + } + + public Channel openSessionChannel() throws IOException + { + Channel c = new Channel(this); + + synchronized (c) + { + c.localID = addChannel(c); + // end of synchronized block forces the writing out to main memory + } + + if (log.isEnabled()) + log.log(50, "Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")"); + + PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize); + tm.sendMessage(smo.getPayload()); + + waitUntilChannelOpen(c); + + return c; + } + + public void requestGlobalTrileadPing() throws IOException + { + synchronized (channels) + { + globalSuccessCounter = globalFailedCounter = 0; + } + + PacketGlobalTrileadPing pgtp = new PacketGlobalTrileadPing(); + + tm.sendMessage(pgtp.getPayload()); + + if (log.isEnabled()) + log.log(50, "Sending SSH_MSG_GLOBAL_REQUEST 'trilead-ping'."); + + try + { + if (waitForGlobalRequestResult() == true) + throw new IOException("Your server is alive - but buggy. " + + "It replied with SSH_MSG_REQUEST_SUCCESS when it actually should not."); + + } + catch (IOException e) + { + throw (IOException) new IOException("The ping request failed.").initCause(e); + } + } + + public void requestChannelTrileadPing(Channel c) throws IOException + { + PacketChannelTrileadPing pctp; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")"); + + pctp = new PacketChannelTrileadPing(c.remoteID); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(pctp.getPayload()); + } + + try + { + if (waitForChannelRequestResult(c) == true) + throw new IOException("Your server is alive - but buggy. " + + "It replied with SSH_MSG_SESSION_SUCCESS when it actually should not."); + + } + catch (IOException e) + { + throw (IOException) new IOException("The ping request failed.").initCause(e); + } + } + + public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters, + int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException + { + PacketSessionPtyRequest spr; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); + + spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters, + term_width_pixels, term_height_pixels, terminal_modes); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(spr.getPayload()); + } + + try + { + if (waitForChannelRequestResult(c) == false) + throw new IOException("The server denied the request."); + } + catch (IOException e) + { + throw (IOException) new IOException("PTY request failed").initCause(e); + } + } + + + public void resizePTY(Channel c, int term_width_characters, int term_height_characters, + int term_width_pixels, int term_height_pixels) throws IOException { + PacketSessionPtyResize spr; + + synchronized (c) { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot request PTY on this channel (" + + c.getReasonClosed() + ")"); + + spr = new PacketSessionPtyResize(c.remoteID, term_width_characters, term_height_characters, + term_width_pixels, term_height_pixels); + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) { + if (c.closeMessageSent) + throw new IOException("Cannot request PTY on this channel (" + + c.getReasonClosed() + ")"); + tm.sendMessage(spr.getPayload()); + } + } + + + public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol, + String x11AuthenticationCookie, int x11ScreenNumber) throws IOException + { + PacketSessionX11Request psr; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); + + psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol, + x11AuthenticationCookie, x11ScreenNumber); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(psr.getPayload()); + } + + if (log.isEnabled()) + log.log(50, "Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")"); + + try + { + if (waitForChannelRequestResult(c) == false) + throw new IOException("The server denied the request."); + } + catch (IOException e) + { + throw (IOException) new IOException("The X11 request failed.").initCause(e); + } + } + + public void requestSubSystem(Channel c, String subSystemName) throws IOException + { + PacketSessionSubsystemRequest ssr; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); + + ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(ssr.getPayload()); + } + + try + { + if (waitForChannelRequestResult(c) == false) + throw new IOException("The server denied the request."); + } + catch (IOException e) + { + throw (IOException) new IOException("The subsystem request failed.").initCause(e); + } + } + + public void requestExecCommand(Channel c, String cmd) throws IOException + { + PacketSessionExecCommand sm; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); + + sm = new PacketSessionExecCommand(c.remoteID, true, cmd); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(sm.getPayload()); + } + + if (log.isEnabled()) + log.log(50, "Executing command (channel " + c.localID + ", '" + cmd + "')"); + + try + { + if (waitForChannelRequestResult(c) == false) + throw new IOException("The server denied the request."); + } + catch (IOException e) + { + throw (IOException) new IOException("The execute request failed.").initCause(e); + } + } + + public void requestShell(Channel c) throws IOException + { + PacketSessionStartShell sm; + + synchronized (c) + { + if (c.state != Channel.STATE_OPEN) + throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); + + sm = new PacketSessionStartShell(c.remoteID, true); + + c.successCounter = c.failedCounter = 0; + } + + synchronized (c.channelSendLock) + { + if (c.closeMessageSent) + throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); + tm.sendMessage(sm.getPayload()); + } + + try + { + if (waitForChannelRequestResult(c) == false) + throw new IOException("The server denied the request."); + } + catch (IOException e) + { + throw (IOException) new IOException("The shell request failed.").initCause(e); + } + } + + public void msgChannelExtendedData(byte[] msg, int msglen) throws IOException + { + if (msglen <= 13) + throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id); + + if (dataType != Packets.SSH_EXTENDED_DATA_STDERR) + throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")"); + + if (len != (msglen - 13)) + throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13) + + ", got " + len + ")"); + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")"); + + synchronized (c) + { + if (c.state == Channel.STATE_CLOSED) + return; // ignore + + if (c.state != Channel.STATE_OPEN) + throw new IOException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state (" + + c.state + ")"); + + if (c.localWindow < len) + throw new IOException("Remote sent too much data, does not fit into window."); + + c.localWindow -= len; + + System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len); + c.stderrWritepos += len; + + c.notifyAll(); + } + } + + /** + * Wait until for a condition. + * + * @param c + * Channel + * @param timeout + * in ms, 0 means no timeout. + * @param condition_mask + * minimum event mask + * @return all current events + * + */ + public int waitForCondition(Channel c, long timeout, int condition_mask) + { + long end_time = 0; + boolean end_time_set = false; + + synchronized (c) + { + while (true) + { + int current_cond = 0; + + int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; + int stderrAvail = c.stderrWritepos - c.stderrReadpos; + + if (stdoutAvail > 0) + current_cond = current_cond | ChannelCondition.STDOUT_DATA; + + if (stderrAvail > 0) + current_cond = current_cond | ChannelCondition.STDERR_DATA; + + if (c.EOF) + current_cond = current_cond | ChannelCondition.EOF; + + if (c.getExitStatus() != null) + current_cond = current_cond | ChannelCondition.EXIT_STATUS; + + if (c.getExitSignal() != null) + current_cond = current_cond | ChannelCondition.EXIT_SIGNAL; + + if (c.state == Channel.STATE_CLOSED) + return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF; + + if ((current_cond & condition_mask) != 0) + return current_cond; + + if (timeout > 0) + { + if (!end_time_set) + { + end_time = System.currentTimeMillis() + timeout; + end_time_set = true; + } + else + { + timeout = end_time - System.currentTimeMillis(); + + if (timeout <= 0) + return current_cond | ChannelCondition.TIMEOUT; + } + } + + try + { + if (timeout > 0) + c.wait(timeout); + else + c.wait(); + } + catch (InterruptedException e) + { + } + } + } + } + + public int getAvailable(Channel c, boolean extended) throws IOException + { + synchronized (c) + { + int avail; + + if (extended) + avail = c.stderrWritepos - c.stderrReadpos; + else + avail = c.stdoutWritepos - c.stdoutReadpos; + + return ((avail > 0) ? avail : (c.EOF ? -1 : 0)); + } + } + + public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException + { + int copylen = 0; + int increment = 0; + int remoteID = 0; + int localID = 0; + + synchronized (c) + { + int stdoutAvail = 0; + int stderrAvail = 0; + + while (true) + { + /* + * Data available? We have to return remaining data even if the + * channel is already closed. + */ + + stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; + stderrAvail = c.stderrWritepos - c.stderrReadpos; + + if ((!extended) && (stdoutAvail != 0)) + break; + + if ((extended) && (stderrAvail != 0)) + break; + + /* Do not wait if more data will never arrive (EOF or CLOSED) */ + + if ((c.EOF) || (c.state != Channel.STATE_OPEN)) + return -1; + + try + { + c.wait(); + } + catch (InterruptedException ignore) + { + } + } + + /* OK, there is some data. Return it. */ + + if (!extended) + { + copylen = (stdoutAvail > len) ? len : stdoutAvail; + System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); + c.stdoutReadpos += copylen; + + if (c.stdoutReadpos != c.stdoutWritepos) + + System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos + - c.stdoutReadpos); + + c.stdoutWritepos -= c.stdoutReadpos; + c.stdoutReadpos = 0; + } + else + { + copylen = (stderrAvail > len) ? len : stderrAvail; + System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen); + c.stderrReadpos += copylen; + + if (c.stderrReadpos != c.stderrWritepos) + + System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos + - c.stderrReadpos); + + c.stderrWritepos -= c.stderrReadpos; + c.stderrReadpos = 0; + } + + if (c.state != Channel.STATE_OPEN) + return copylen; + + if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) + { + int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, Channel.CHANNEL_BUFFER_SIZE + - c.stderrWritepos); + + increment = minFreeSpace - c.localWindow; + c.localWindow = minFreeSpace; + } + + remoteID = c.remoteID; /* read while holding the lock */ + localID = c.localID; /* read while holding the lock */ + } + + /* + * If a consumer reads stdout and stdin in parallel, we may end up with + * sending two msgWindowAdjust messages. Luckily, it + * does not matter in which order they arrive at the server. + */ + + if (increment > 0) + { + if (log.isEnabled()) + log.log(80, "Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); + + synchronized (c.channelSendLock) + { + byte[] msg = c.msgWindowAdjust; + + msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST; + msg[1] = (byte) (remoteID >> 24); + msg[2] = (byte) (remoteID >> 16); + msg[3] = (byte) (remoteID >> 8); + msg[4] = (byte) (remoteID); + msg[5] = (byte) (increment >> 24); + msg[6] = (byte) (increment >> 16); + msg[7] = (byte) (increment >> 8); + msg[8] = (byte) (increment); + + if (c.closeMessageSent == false) + tm.sendMessage(msg); + } + } + + return copylen; + } + + public void msgChannelData(byte[] msg, int msglen) throws IOException + { + if (msglen <= 9) + throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id); + + if (len != (msglen - 9)) + throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got " + + len + ")"); + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); + + synchronized (c) + { + if (c.state == Channel.STATE_CLOSED) + return; // ignore + + if (c.state != Channel.STATE_OPEN) + throw new IOException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")"); + + if (c.localWindow < len) + throw new IOException("Remote sent too much data, does not fit into window."); + + c.localWindow -= len; + + System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len); + c.stdoutWritepos += len; + + c.notifyAll(); + } + } + + public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException + { + if (msglen != 9) + throw new IOException("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id); + + synchronized (c) + { + final long huge = 0xFFFFffffL; /* 2^32 - 1 */ + + c.remoteWindow += (windowChange & huge); /* avoid sign extension */ + + /* TODO - is this a good heuristic? */ + + if ((c.remoteWindow > huge)) + c.remoteWindow = huge; + + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); + } + + public void msgChannelOpen(byte[] msg, int msglen) throws IOException + { + TypesReader tr = new TypesReader(msg, 0, msglen); + + tr.readByte(); // skip packet type + String channelType = tr.readString(); + int remoteID = tr.readUINT32(); /* sender channel */ + int remoteWindow = tr.readUINT32(); /* initial window size */ + int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */ + + if ("x11".equals(channelType)) + { + synchronized (x11_magic_cookies) + { + /* If we did not request X11 forwarding, then simply ignore this bogus request. */ + + if (x11_magic_cookies.size() == 0) + { + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, + Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", ""); + + tm.sendAsynchronousMessage(pcof.getPayload()); + + if (log.isEnabled()) + log.log(20, "Unexpected X11 request, denying it!"); + + return; + } + } + + String remoteOriginatorAddress = tr.readString(); + int remoteOriginatorPort = tr.readUINT32(); + + Channel c = new Channel(this); + + synchronized (c) + { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } + + /* + * The open confirmation message will be sent from another thread + */ + + RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort); + rxat.setDaemon(true); + rxat.start(); + + return; + } + + if ("forwarded-tcpip".equals(channelType)) + { + String remoteConnectedAddress = tr.readString(); /* address that was connected */ + int remoteConnectedPort = tr.readUINT32(); /* port that was connected */ + String remoteOriginatorAddress = tr.readString(); /* originator IP address */ + int remoteOriginatorPort = tr.readUINT32(); /* originator port */ + + RemoteForwardingData rfd = null; + + synchronized (remoteForwardings) + { + rfd = remoteForwardings.get(Integer.valueOf(remoteConnectedPort)); + } + + if (rfd == null) + { + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, + Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, + "No thanks, unknown port in forwarded-tcpip request", ""); + + /* Always try to be polite. */ + + tm.sendAsynchronousMessage(pcof.getPayload()); + + if (log.isEnabled()) + log.log(20, "Unexpected forwarded-tcpip request, denying it!"); + + return; + } + + Channel c = new Channel(this); + + synchronized (c) + { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } + + /* + * The open confirmation message will be sent from another thread. + */ + + RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort, + remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); + + rat.setDaemon(true); + rat.start(); + + return; + } + + if ("auth-agent@openssh.com".equals(channelType)) { + Channel c = new Channel(this); + + synchronized (c) + { + c.remoteID = remoteID; + c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ + c.remoteMaxPacketSize = remoteMaxPacketSize; + c.localID = addChannel(c); + } + + AuthAgentForwardThread aat = new AuthAgentForwardThread(c, authAgent); + + aat.setDaemon(true); + aat.start(); + + return; + } + + /* Tell the server that we have no idea what it is talking about */ + + PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, + "Unknown channel type", ""); + + tm.sendAsynchronousMessage(pcof.getPayload()); + + if (log.isEnabled()) + log.log(20, "The peer tried to open an unsupported channel type (" + channelType + ")"); + } + + public void msgChannelRequest(byte[] msg, int msglen) throws IOException + { + TypesReader tr = new TypesReader(msg, 0, msglen); + + tr.readByte(); // skip packet type + int id = tr.readUINT32(); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); + + String type = tr.readString("US-ASCII"); + boolean wantReply = tr.readBoolean(); + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); + + if (type.equals("exit-status")) + { + if (wantReply != false) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); + + int exit_status = tr.readUINT32(); + + if (tr.remain() != 0) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + + synchronized (c) + { + c.exit_status = Integer.valueOf(exit_status); + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); + + return; + } + + if (type.equals("exit-signal")) + { + if (wantReply != false) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); + + String signame = tr.readString("US-ASCII"); + tr.readBoolean(); + tr.readString(); + tr.readString(); + + if (tr.remain() != 0) + throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); + + synchronized (c) + { + c.exit_signal = signame; + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); + + return; + } + + /* We simply ignore unknown channel requests, however, if the server wants a reply, + * then we signal that we have no idea what it is about. + */ + + if (wantReply) + { + byte[] reply = new byte[5]; + + reply[0] = Packets.SSH_MSG_CHANNEL_FAILURE; + reply[1] = (byte) (c.remoteID >> 24); + reply[2] = (byte) (c.remoteID >> 16); + reply[3] = (byte) (c.remoteID >> 8); + reply[4] = (byte) (c.remoteID); + + tm.sendAsynchronousMessage(reply); + } + + if (log.isEnabled()) + log.log(50, "Channel request '" + type + "' is not known, ignoring it"); + } + + public void msgChannelEOF(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id); + + synchronized (c) + { + c.EOF = true; + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_EOF (channel " + id + ")"); + } + + public void msgChannelClose(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id); + + synchronized (c) + { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed("Close requested by remote"); + c.closeMessageRecv = true; + + removeChannel(c.localID); + + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); + } + + public void msgChannelSuccess(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); + + synchronized (c) + { + c.successCounter++; + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); + } + + public void msgChannelFailure(byte[] msg, int msglen) throws IOException + { + if (msglen != 5) + throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")"); + + int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); + + synchronized (c) + { + c.failedCounter++; + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); + } + + public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException + { + PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen); + + Channel c = getChannel(sm.recipientChannelID); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " + + sm.recipientChannelID); + + synchronized (c) + { + if (c.state != Channel.STATE_OPENING) + throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " + + sm.recipientChannelID); + + c.remoteID = sm.senderChannelID; + c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */ + c.remoteMaxPacketSize = sm.maxPacketSize; + c.state = Channel.STATE_OPEN; + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: " + + sm.senderChannelID + ")"); + } + + public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException + { + if (msglen < 5) + throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")"); + + TypesReader tr = new TypesReader(msg, 0, msglen); + + tr.readByte(); // skip packet type + int id = tr.readUINT32(); /* sender channel */ + + Channel c = getChannel(id); + + if (c == null) + throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); + + int reasonCode = tr.readUINT32(); + String description = tr.readString("UTF-8"); + + String reasonCodeSymbolicName = null; + + switch (reasonCode) + { + case 1: + reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED"; + break; + case 2: + reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED"; + break; + case 3: + reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE"; + break; + case 4: + reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE"; + break; + default: + reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")"; + } + + StringBuffer descriptionBuffer = new StringBuffer(); + descriptionBuffer.append(description); + + for (int i = 0; i < descriptionBuffer.length(); i++) + { + char cc = descriptionBuffer.charAt(i); + + if ((cc >= 32) && (cc <= 126)) + continue; + descriptionBuffer.setCharAt(i, '\uFFFD'); + } + + synchronized (c) + { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed("The server refused to open the channel (" + reasonCodeSymbolicName + ", '" + + descriptionBuffer.toString() + "')"); + c.notifyAll(); + } + + if (log.isEnabled()) + log.log(50, "Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); + } + + public void msgGlobalRequest(byte[] msg, int msglen) throws IOException + { + /* Currently we do not support any kind of global request */ + + TypesReader tr = new TypesReader(msg, 0, msglen); + + tr.readByte(); // skip packet type + String requestName = tr.readString(); + boolean wantReply = tr.readBoolean(); + + if (wantReply) + { + byte[] reply_failure = new byte[1]; + reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; + + tm.sendAsynchronousMessage(reply_failure); + } + + /* We do not clean up the requestName String - that is OK for debug */ + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); + } + + public void msgGlobalSuccess() throws IOException + { + synchronized (channels) + { + globalSuccessCounter++; + channels.notifyAll(); + } + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_REQUEST_SUCCESS"); + } + + public void msgGlobalFailure() throws IOException + { + synchronized (channels) + { + globalFailedCounter++; + channels.notifyAll(); + } + + if (log.isEnabled()) + log.log(80, "Got SSH_MSG_REQUEST_FAILURE"); + } + + public void handleMessage(byte[] msg, int msglen) throws IOException + { + if (msg == null) + { + if (log.isEnabled()) + log.log(50, "HandleMessage: got shutdown"); + + synchronized (listenerThreads) + { + for (int i = 0; i < listenerThreads.size(); i++) + { + IChannelWorkerThread lat = listenerThreads.elementAt(i); + lat.stopWorking(); + } + listenerThreadsAllowed = false; + } + + synchronized (channels) + { + shutdown = true; + + for (int i = 0; i < channels.size(); i++) + { + Channel c = channels.elementAt(i); + synchronized (c) + { + c.EOF = true; + c.state = Channel.STATE_CLOSED; + c.setReasonClosed("The connection is being shutdown"); + c.closeMessageRecv = true; /* + * You never know, perhaps + * we are waiting for a + * pending close message + * from the server... + */ + c.notifyAll(); + } + } + /* Works with J2ME */ + channels.setSize(0); + channels.trimToSize(); + channels.notifyAll(); /* Notify global response waiters */ + return; + } + } + + switch (msg[0]) + { + case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: + msgChannelOpenConfirmation(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST: + msgChannelWindowAdjust(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_DATA: + msgChannelData(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA: + msgChannelExtendedData(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_REQUEST: + msgChannelRequest(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_EOF: + msgChannelEOF(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_OPEN: + msgChannelOpen(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_CLOSE: + msgChannelClose(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_SUCCESS: + msgChannelSuccess(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_FAILURE: + msgChannelFailure(msg, msglen); + break; + case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE: + msgChannelOpenFailure(msg, msglen); + break; + case Packets.SSH_MSG_GLOBAL_REQUEST: + msgGlobalRequest(msg, msglen); + break; + case Packets.SSH_MSG_REQUEST_SUCCESS: + msgGlobalSuccess(); + break; + case Packets.SSH_MSG_REQUEST_FAILURE: + msgGlobalFailure(); + break; + default: + throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff)); + } + } +} diff --git a/lib/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java b/lib/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java index 30e8fd8..c1d56e8 100644 --- a/lib/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java +++ b/lib/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java @@ -1,71 +1,71 @@ -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * ChannelOutputStream. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: ChannelOutputStream.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public final class ChannelOutputStream extends OutputStream -{ - Channel c; - - private byte[] writeBuffer; - - boolean isClosed = false; - - ChannelOutputStream(Channel c) - { - this.c = c; - writeBuffer = new byte[1]; - } - - public void write(int b) throws IOException - { - writeBuffer[0] = (byte) b; - - write(writeBuffer, 0, 1); - } - - public void close() throws IOException - { - if (isClosed == false) - { - isClosed = true; - c.cm.sendEOF(c); - } - } - - public void flush() throws IOException - { - if (isClosed) - throw new IOException("This OutputStream is closed."); - - /* This is a no-op, since this stream is unbuffered */ - } - - public void write(byte[] b, int off, int len) throws IOException - { - if (isClosed) - throw new IOException("This OutputStream is closed."); - - if (b == null) - throw new NullPointerException(); - - if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) - throw new IndexOutOfBoundsException(); - - if (len == 0) - return; - - c.cm.sendData(c, b, off, len); - } - - public void write(byte[] b) throws IOException - { - write(b, 0, b.length); - } -} +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * ChannelOutputStream. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: ChannelOutputStream.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public final class ChannelOutputStream extends OutputStream +{ + Channel c; + + private byte[] writeBuffer; + + boolean isClosed = false; + + ChannelOutputStream(Channel c) + { + this.c = c; + writeBuffer = new byte[1]; + } + + public void write(int b) throws IOException + { + writeBuffer[0] = (byte) b; + + write(writeBuffer, 0, 1); + } + + public void close() throws IOException + { + if (isClosed == false) + { + isClosed = true; + c.cm.sendEOF(c); + } + } + + public void flush() throws IOException + { + if (isClosed) + throw new IOException("This OutputStream is closed."); + + /* This is a no-op, since this stream is unbuffered */ + } + + public void write(byte[] b, int off, int len) throws IOException + { + if (isClosed) + throw new IOException("This OutputStream is closed."); + + if (b == null) + throw new NullPointerException(); + + if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length)) + throw new IndexOutOfBoundsException(); + + if (len == 0) + return; + + c.cm.sendData(c, b, off, len); + } + + public void write(byte[] b) throws IOException + { + write(b, 0, b.length); + } +} diff --git a/lib/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java b/lib/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java index c085421..bce9b1b 100644 --- a/lib/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java +++ b/lib/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java @@ -1,13 +1,13 @@ - -package com.trilead.ssh2.channel; - -/** - * IChannelWorkerThread. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: IChannelWorkerThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -interface IChannelWorkerThread -{ - public void stopWorking(); -} + +package com.trilead.ssh2.channel; + +/** + * IChannelWorkerThread. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: IChannelWorkerThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +interface IChannelWorkerThread +{ + public void stopWorking(); +} diff --git a/lib/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java b/lib/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java index 885b41f..0d1bb35 100644 --- a/lib/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java +++ b/lib/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java @@ -1,135 +1,135 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; - -/** - * LocalAcceptThread. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: LocalAcceptThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class LocalAcceptThread extends Thread implements IChannelWorkerThread -{ - ChannelManager cm; - String host_to_connect; - int port_to_connect; - - final ServerSocket ss; - - public LocalAcceptThread(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) - throws IOException - { - this.cm = cm; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; - - ss = new ServerSocket(local_port); - } - - public LocalAcceptThread(ChannelManager cm, InetSocketAddress localAddress, String host_to_connect, - int port_to_connect) throws IOException - { - this.cm = cm; - this.host_to_connect = host_to_connect; - this.port_to_connect = port_to_connect; - - ss = new ServerSocket(); - ss.bind(localAddress); - } - - public void run() - { - try - { - cm.registerThread(this); - } - catch (IOException e) - { - stopWorking(); - return; - } - - while (true) - { - Socket s = null; - - try - { - s = ss.accept(); - } - catch (IOException e) - { - stopWorking(); - return; - } - - Channel cn = null; - StreamForwarder r2l = null; - StreamForwarder l2r = null; - - try - { - /* This may fail, e.g., if the remote port is closed (in optimistic terms: not open yet) */ - - cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, s.getInetAddress().getHostAddress(), s - .getPort()); - - } - catch (IOException e) - { - /* Simply close the local socket and wait for the next incoming connection */ - - try - { - s.close(); - } - catch (IOException ignore) - { - } - - continue; - } - - try - { - r2l = new StreamForwarder(cn, null, s, cn.stdoutStream, s.getOutputStream(), "RemoteToLocal"); - l2r = new StreamForwarder(cn, r2l, s, s.getInputStream(), cn.stdinStream, "LocalToRemote"); - } - catch (IOException e) - { - try - { - /* This message is only visible during debugging, since we discard the channel immediatelly */ - cn.cm.closeChannel(cn, "Weird error during creation of StreamForwarder (" + e.getMessage() + ")", - true); - } - catch (IOException ignore) - { - } - - continue; - } - - r2l.setDaemon(true); - l2r.setDaemon(true); - r2l.start(); - l2r.start(); - } - } - - public void stopWorking() - { - try - { - /* This will lead to an IOException in the ss.accept() call */ - ss.close(); - } - catch (IOException e) - { - } - } -} + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * LocalAcceptThread. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: LocalAcceptThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class LocalAcceptThread extends Thread implements IChannelWorkerThread +{ + ChannelManager cm; + String host_to_connect; + int port_to_connect; + + final ServerSocket ss; + + public LocalAcceptThread(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect) + throws IOException + { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + + ss = new ServerSocket(local_port); + } + + public LocalAcceptThread(ChannelManager cm, InetSocketAddress localAddress, String host_to_connect, + int port_to_connect) throws IOException + { + this.cm = cm; + this.host_to_connect = host_to_connect; + this.port_to_connect = port_to_connect; + + ss = new ServerSocket(); + ss.bind(localAddress); + } + + public void run() + { + try + { + cm.registerThread(this); + } + catch (IOException e) + { + stopWorking(); + return; + } + + while (true) + { + Socket s = null; + + try + { + s = ss.accept(); + } + catch (IOException e) + { + stopWorking(); + return; + } + + Channel cn = null; + StreamForwarder r2l = null; + StreamForwarder l2r = null; + + try + { + /* This may fail, e.g., if the remote port is closed (in optimistic terms: not open yet) */ + + cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, s.getInetAddress().getHostAddress(), s + .getPort()); + + } + catch (IOException e) + { + /* Simply close the local socket and wait for the next incoming connection */ + + try + { + s.close(); + } + catch (IOException ignore) + { + } + + continue; + } + + try + { + r2l = new StreamForwarder(cn, null, s, cn.stdoutStream, s.getOutputStream(), "RemoteToLocal"); + l2r = new StreamForwarder(cn, r2l, s, s.getInputStream(), cn.stdinStream, "LocalToRemote"); + } + catch (IOException e) + { + try + { + /* This message is only visible during debugging, since we discard the channel immediatelly */ + cn.cm.closeChannel(cn, "Weird error during creation of StreamForwarder (" + e.getMessage() + ")", + true); + } + catch (IOException ignore) + { + } + + continue; + } + + r2l.setDaemon(true); + l2r.setDaemon(true); + r2l.start(); + l2r.start(); + } + } + + public void stopWorking() + { + try + { + /* This will lead to an IOException in the ss.accept() call */ + ss.close(); + } + catch (IOException e) + { + } + } +} diff --git a/lib/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java b/lib/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java index 8b97b55..29b02b8 100644 --- a/lib/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java +++ b/lib/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java @@ -1,103 +1,103 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.net.Socket; - -import com.trilead.ssh2.log.Logger; - - -/** - * RemoteAcceptThread. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: RemoteAcceptThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class RemoteAcceptThread extends Thread -{ - private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); - - Channel c; - - String remoteConnectedAddress; - int remoteConnectedPort; - String remoteOriginatorAddress; - int remoteOriginatorPort; - String targetAddress; - int targetPort; - - Socket s; - - public RemoteAcceptThread(Channel c, String remoteConnectedAddress, int remoteConnectedPort, - String remoteOriginatorAddress, int remoteOriginatorPort, String targetAddress, int targetPort) - { - this.c = c; - this.remoteConnectedAddress = remoteConnectedAddress; - this.remoteConnectedPort = remoteConnectedPort; - this.remoteOriginatorAddress = remoteOriginatorAddress; - this.remoteOriginatorPort = remoteOriginatorPort; - this.targetAddress = targetAddress; - this.targetPort = targetPort; - - if (log.isEnabled()) - log.log(20, "RemoteAcceptThread: " + remoteConnectedAddress + "/" + remoteConnectedPort + ", R: " - + remoteOriginatorAddress + "/" + remoteOriginatorPort); - } - - public void run() - { - try - { - c.cm.sendOpenConfirmation(c); - - s = new Socket(targetAddress, targetPort); - - StreamForwarder r2l = new StreamForwarder(c, null, s, c.getStdoutStream(), s.getOutputStream(), - "RemoteToLocal"); - StreamForwarder l2r = new StreamForwarder(c, null, null, s.getInputStream(), c.getStdinStream(), - "LocalToRemote"); - - /* No need to start two threads, one can be executed in the current thread */ - - r2l.setDaemon(true); - r2l.start(); - l2r.run(); - - while (r2l.isAlive()) - { - try - { - r2l.join(); - } - catch (InterruptedException e) - { - } - } - - /* If the channel is already closed, then this is a no-op */ - - c.cm.closeChannel(c, "EOF on both streams reached.", true); - s.close(); - } - catch (IOException e) - { - log.log(50, "IOException in proxy code: " + e.getMessage()); - - try - { - c.cm.closeChannel(c, "IOException in proxy code (" + e.getMessage() + ")", true); - } - catch (IOException e1) - { - } - try - { - if (s != null) - s.close(); - } - catch (IOException e1) - { - } - } - } -} + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.net.Socket; + +import com.trilead.ssh2.log.Logger; + + +/** + * RemoteAcceptThread. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: RemoteAcceptThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class RemoteAcceptThread extends Thread +{ + private static final Logger log = Logger.getLogger(RemoteAcceptThread.class); + + Channel c; + + String remoteConnectedAddress; + int remoteConnectedPort; + String remoteOriginatorAddress; + int remoteOriginatorPort; + String targetAddress; + int targetPort; + + Socket s; + + public RemoteAcceptThread(Channel c, String remoteConnectedAddress, int remoteConnectedPort, + String remoteOriginatorAddress, int remoteOriginatorPort, String targetAddress, int targetPort) + { + this.c = c; + this.remoteConnectedAddress = remoteConnectedAddress; + this.remoteConnectedPort = remoteConnectedPort; + this.remoteOriginatorAddress = remoteOriginatorAddress; + this.remoteOriginatorPort = remoteOriginatorPort; + this.targetAddress = targetAddress; + this.targetPort = targetPort; + + if (log.isEnabled()) + log.log(20, "RemoteAcceptThread: " + remoteConnectedAddress + "/" + remoteConnectedPort + ", R: " + + remoteOriginatorAddress + "/" + remoteOriginatorPort); + } + + public void run() + { + try + { + c.cm.sendOpenConfirmation(c); + + s = new Socket(targetAddress, targetPort); + + StreamForwarder r2l = new StreamForwarder(c, null, s, c.getStdoutStream(), s.getOutputStream(), + "RemoteToLocal"); + StreamForwarder l2r = new StreamForwarder(c, null, null, s.getInputStream(), c.getStdinStream(), + "LocalToRemote"); + + /* No need to start two threads, one can be executed in the current thread */ + + r2l.setDaemon(true); + r2l.start(); + l2r.run(); + + while (r2l.isAlive()) + { + try + { + r2l.join(); + } + catch (InterruptedException e) + { + } + } + + /* If the channel is already closed, then this is a no-op */ + + c.cm.closeChannel(c, "EOF on both streams reached.", true); + s.close(); + } + catch (IOException e) + { + log.log(50, "IOException in proxy code: " + e.getMessage()); + + try + { + c.cm.closeChannel(c, "IOException in proxy code (" + e.getMessage() + ")", true); + } + catch (IOException e1) + { + } + try + { + if (s != null) + s.close(); + } + catch (IOException e1) + { + } + } + } +} diff --git a/lib/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java b/lib/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java index 0bafce2..d05378e 100644 --- a/lib/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java +++ b/lib/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java @@ -1,17 +1,17 @@ - -package com.trilead.ssh2.channel; - -/** - * RemoteForwardingData. Data about a requested remote forwarding. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: RemoteForwardingData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class RemoteForwardingData -{ - public String bindAddress; - public int bindPort; - - String targetAddress; - int targetPort; -} + +package com.trilead.ssh2.channel; + +/** + * RemoteForwardingData. Data about a requested remote forwarding. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: RemoteForwardingData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class RemoteForwardingData +{ + public String bindAddress; + public int bindPort; + + String targetAddress; + int targetPort; +} diff --git a/lib/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java b/lib/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java index 34ce51f..9f99410 100644 --- a/lib/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java +++ b/lib/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java @@ -1,240 +1,240 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; - -import com.trilead.ssh2.log.Logger; - - -/** - * RemoteX11AcceptThread. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: RemoteX11AcceptThread.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ - */ -public class RemoteX11AcceptThread extends Thread -{ - private static final Logger log = Logger.getLogger(RemoteX11AcceptThread.class); - - Channel c; - - String remoteOriginatorAddress; - int remoteOriginatorPort; - - Socket s; - - public RemoteX11AcceptThread(Channel c, String remoteOriginatorAddress, int remoteOriginatorPort) - { - this.c = c; - this.remoteOriginatorAddress = remoteOriginatorAddress; - this.remoteOriginatorPort = remoteOriginatorPort; - } - - public void run() - { - try - { - /* Send Open Confirmation */ - - c.cm.sendOpenConfirmation(c); - - /* Read startup packet from client */ - - OutputStream remote_os = c.getStdinStream(); - InputStream remote_is = c.getStdoutStream(); - - /* The following code is based on the protocol description given in: - * Scheifler/Gettys, - * X Windows System: Core and Extension Protocols: - * X Version 11, Releases 6 and 6.1 ISBN 1-55558-148-X - */ - - /* - * Client startup: - * - * 1 0X42 MSB first/0x6c lSB first - byteorder - * 1 - unused - * 2 card16 - protocol-major-version - * 2 card16 - protocol-minor-version - * 2 n - lenght of authorization-protocol-name - * 2 d - lenght of authorization-protocol-data - * 2 - unused - * string8 - authorization-protocol-name - * p - unused, p=pad(n) - * string8 - authorization-protocol-data - * q - unused, q=pad(d) - * - * pad(X) = (4 - (X mod 4)) mod 4 - * - * Server response: - * - * 1 (0 failed, 2 authenticate, 1 success) - * ... - * - */ - - /* Later on we will simply forward the first 6 header bytes to the "real" X11 server */ - - byte[] header = new byte[6]; - - if (remote_is.read(header) != 6) - throw new IOException("Unexpected EOF on X11 startup!"); - - if ((header[0] != 0x42) && (header[0] != 0x6c)) // 0x42 MSB first, 0x6C LSB first - throw new IOException("Unknown endian format in X11 message!"); - - /* Yes, I came up with this myself - shall I file an application for a patent? =) */ - - int idxMSB = (header[0] == 0x42) ? 0 : 1; - - /* Read authorization data header */ - - byte[] auth_buff = new byte[6]; - - if (remote_is.read(auth_buff) != 6) - throw new IOException("Unexpected EOF on X11 startup!"); - - int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff); - int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff); - - if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256)) - throw new IOException("Buggy X11 authorization data"); - - int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4); - int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4); - - byte[] authProtocolName = new byte[authProtocolNameLength]; - byte[] authProtocolData = new byte[authProtocolDataLength]; - - byte[] paddingBuffer = new byte[4]; - - if (remote_is.read(authProtocolName) != authProtocolNameLength) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolName)"); - - if (remote_is.read(paddingBuffer, 0, authProtocolNamePadding) != authProtocolNamePadding) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)"); - - if (remote_is.read(authProtocolData) != authProtocolDataLength) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolData)"); - - if (remote_is.read(paddingBuffer, 0, authProtocolDataPadding) != authProtocolDataPadding) - throw new IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)"); - - if ("MIT-MAGIC-COOKIE-1".equals(new String(authProtocolName, "ISO-8859-1")) == false) - throw new IOException("Unknown X11 authorization protocol!"); - - if (authProtocolDataLength != 16) - throw new IOException("Wrong data length for X11 authorization data!"); - - StringBuffer tmp = new StringBuffer(32); - for (int i = 0; i < authProtocolData.length; i++) - { - String digit2 = Integer.toHexString(authProtocolData[i] & 0xff); - tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); - } - String hexEncodedFakeCookie = tmp.toString(); - - /* Order is very important here - it may be that a certain x11 forwarding - * gets disabled right in the moment when we check and register our connection - * */ - - synchronized (c) - { - /* Please read the comment in Channel.java */ - c.hexX11FakeCookie = hexEncodedFakeCookie; - } - - /* Now check our fake cookie directory to see if we produced this cookie */ - - X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie); - - if (sd == null) - throw new IOException("Invalid X11 cookie received."); - - /* If the session which corresponds to this cookie is closed then we will - * detect this: the session's close code will close all channels - * with the session's assigned x11 fake cookie. - */ - - s = new Socket(sd.hostname, sd.port); - - OutputStream x11_os = s.getOutputStream(); - InputStream x11_is = s.getInputStream(); - - /* Now we are sending the startup packet to the real X11 server */ - - x11_os.write(header); - - if (sd.x11_magic_cookie == null) - { - byte[] emptyAuthData = new byte[6]; - /* empty auth data, hopefully you are connecting to localhost =) */ - x11_os.write(emptyAuthData); - } - else - { - if (sd.x11_magic_cookie.length != 16) - throw new IOException("The real X11 cookie has an invalid length!"); - - /* send X11 cookie specified by client */ - x11_os.write(auth_buff); - x11_os.write(authProtocolName); /* re-use */ - x11_os.write(paddingBuffer, 0, authProtocolNamePadding); - x11_os.write(sd.x11_magic_cookie); - x11_os.write(paddingBuffer, 0, authProtocolDataPadding); - } - - x11_os.flush(); - - /* Start forwarding traffic */ - - StreamForwarder r2l = new StreamForwarder(c, null, s, remote_is, x11_os, "RemoteToX11"); - StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote"); - - /* No need to start two threads, one can be executed in the current thread */ - - r2l.setDaemon(true); - r2l.start(); - l2r.run(); - - while (r2l.isAlive()) - { - try - { - r2l.join(); - } - catch (InterruptedException e) - { - } - } - - /* If the channel is already closed, then this is a no-op */ - - c.cm.closeChannel(c, "EOF on both X11 streams reached.", true); - s.close(); - } - catch (IOException e) - { - log.log(50, "IOException in X11 proxy code: " + e.getMessage()); - - try - { - c.cm.closeChannel(c, "IOException in X11 proxy code (" + e.getMessage() + ")", true); - } - catch (IOException e1) - { - } - try - { - if (s != null) - s.close(); - } - catch (IOException e1) - { - } - } - } -} + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +import com.trilead.ssh2.log.Logger; + + +/** + * RemoteX11AcceptThread. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: RemoteX11AcceptThread.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $ + */ +public class RemoteX11AcceptThread extends Thread +{ + private static final Logger log = Logger.getLogger(RemoteX11AcceptThread.class); + + Channel c; + + String remoteOriginatorAddress; + int remoteOriginatorPort; + + Socket s; + + public RemoteX11AcceptThread(Channel c, String remoteOriginatorAddress, int remoteOriginatorPort) + { + this.c = c; + this.remoteOriginatorAddress = remoteOriginatorAddress; + this.remoteOriginatorPort = remoteOriginatorPort; + } + + public void run() + { + try + { + /* Send Open Confirmation */ + + c.cm.sendOpenConfirmation(c); + + /* Read startup packet from client */ + + OutputStream remote_os = c.getStdinStream(); + InputStream remote_is = c.getStdoutStream(); + + /* The following code is based on the protocol description given in: + * Scheifler/Gettys, + * X Windows System: Core and Extension Protocols: + * X Version 11, Releases 6 and 6.1 ISBN 1-55558-148-X + */ + + /* + * Client startup: + * + * 1 0X42 MSB first/0x6c lSB first - byteorder + * 1 - unused + * 2 card16 - protocol-major-version + * 2 card16 - protocol-minor-version + * 2 n - lenght of authorization-protocol-name + * 2 d - lenght of authorization-protocol-data + * 2 - unused + * string8 - authorization-protocol-name + * p - unused, p=pad(n) + * string8 - authorization-protocol-data + * q - unused, q=pad(d) + * + * pad(X) = (4 - (X mod 4)) mod 4 + * + * Server response: + * + * 1 (0 failed, 2 authenticate, 1 success) + * ... + * + */ + + /* Later on we will simply forward the first 6 header bytes to the "real" X11 server */ + + byte[] header = new byte[6]; + + if (remote_is.read(header) != 6) + throw new IOException("Unexpected EOF on X11 startup!"); + + if ((header[0] != 0x42) && (header[0] != 0x6c)) // 0x42 MSB first, 0x6C LSB first + throw new IOException("Unknown endian format in X11 message!"); + + /* Yes, I came up with this myself - shall I file an application for a patent? =) */ + + int idxMSB = (header[0] == 0x42) ? 0 : 1; + + /* Read authorization data header */ + + byte[] auth_buff = new byte[6]; + + if (remote_is.read(auth_buff) != 6) + throw new IOException("Unexpected EOF on X11 startup!"); + + int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff); + int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff); + + if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256)) + throw new IOException("Buggy X11 authorization data"); + + int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4); + int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4); + + byte[] authProtocolName = new byte[authProtocolNameLength]; + byte[] authProtocolData = new byte[authProtocolDataLength]; + + byte[] paddingBuffer = new byte[4]; + + if (remote_is.read(authProtocolName) != authProtocolNameLength) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolName)"); + + if (remote_is.read(paddingBuffer, 0, authProtocolNamePadding) != authProtocolNamePadding) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)"); + + if (remote_is.read(authProtocolData) != authProtocolDataLength) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolData)"); + + if (remote_is.read(paddingBuffer, 0, authProtocolDataPadding) != authProtocolDataPadding) + throw new IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)"); + + if ("MIT-MAGIC-COOKIE-1".equals(new String(authProtocolName, "ISO-8859-1")) == false) + throw new IOException("Unknown X11 authorization protocol!"); + + if (authProtocolDataLength != 16) + throw new IOException("Wrong data length for X11 authorization data!"); + + StringBuffer tmp = new StringBuffer(32); + for (int i = 0; i < authProtocolData.length; i++) + { + String digit2 = Integer.toHexString(authProtocolData[i] & 0xff); + tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2); + } + String hexEncodedFakeCookie = tmp.toString(); + + /* Order is very important here - it may be that a certain x11 forwarding + * gets disabled right in the moment when we check and register our connection + * */ + + synchronized (c) + { + /* Please read the comment in Channel.java */ + c.hexX11FakeCookie = hexEncodedFakeCookie; + } + + /* Now check our fake cookie directory to see if we produced this cookie */ + + X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie); + + if (sd == null) + throw new IOException("Invalid X11 cookie received."); + + /* If the session which corresponds to this cookie is closed then we will + * detect this: the session's close code will close all channels + * with the session's assigned x11 fake cookie. + */ + + s = new Socket(sd.hostname, sd.port); + + OutputStream x11_os = s.getOutputStream(); + InputStream x11_is = s.getInputStream(); + + /* Now we are sending the startup packet to the real X11 server */ + + x11_os.write(header); + + if (sd.x11_magic_cookie == null) + { + byte[] emptyAuthData = new byte[6]; + /* empty auth data, hopefully you are connecting to localhost =) */ + x11_os.write(emptyAuthData); + } + else + { + if (sd.x11_magic_cookie.length != 16) + throw new IOException("The real X11 cookie has an invalid length!"); + + /* send X11 cookie specified by client */ + x11_os.write(auth_buff); + x11_os.write(authProtocolName); /* re-use */ + x11_os.write(paddingBuffer, 0, authProtocolNamePadding); + x11_os.write(sd.x11_magic_cookie); + x11_os.write(paddingBuffer, 0, authProtocolDataPadding); + } + + x11_os.flush(); + + /* Start forwarding traffic */ + + StreamForwarder r2l = new StreamForwarder(c, null, s, remote_is, x11_os, "RemoteToX11"); + StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote"); + + /* No need to start two threads, one can be executed in the current thread */ + + r2l.setDaemon(true); + r2l.start(); + l2r.run(); + + while (r2l.isAlive()) + { + try + { + r2l.join(); + } + catch (InterruptedException e) + { + } + } + + /* If the channel is already closed, then this is a no-op */ + + c.cm.closeChannel(c, "EOF on both X11 streams reached.", true); + s.close(); + } + catch (IOException e) + { + log.log(50, "IOException in X11 proxy code: " + e.getMessage()); + + try + { + c.cm.closeChannel(c, "IOException in X11 proxy code (" + e.getMessage() + ")", true); + } + catch (IOException e1) + { + } + try + { + if (s != null) + s.close(); + } + catch (IOException e1) + { + } + } + } +} diff --git a/lib/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java b/lib/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java index 7e54efb..e1afee8 100644 --- a/lib/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java +++ b/lib/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java @@ -1,113 +1,113 @@ - -package com.trilead.ssh2.channel; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; - -/** - * A StreamForwarder forwards data between two given streams. - * If two StreamForwarder threads are used (one for each direction) - * then one can be configured to shutdown the underlying channel/socket - * if both threads have finished forwarding (EOF). - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: StreamForwarder.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - */ -public class StreamForwarder extends Thread -{ - final OutputStream os; - final InputStream is; - final byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; - final Channel c; - final StreamForwarder sibling; - final Socket s; - final String mode; - - StreamForwarder(Channel c, StreamForwarder sibling, Socket s, InputStream is, OutputStream os, String mode) - throws IOException - { - this.is = is; - this.os = os; - this.mode = mode; - this.c = c; - this.sibling = sibling; - this.s = s; - } - - public void run() - { - try - { - while (true) - { - int len = is.read(buffer); - if (len <= 0) - break; - os.write(buffer, 0, len); - os.flush(); - } - } - catch (IOException ignore) - { - try - { - c.cm.closeChannel(c, "Closed due to exception in StreamForwarder (" + mode + "): " - + ignore.getMessage(), true); - } - catch (IOException e) - { - } - } - finally - { - try - { - os.close(); - } - catch (IOException e1) - { - } - try - { - is.close(); - } - catch (IOException e2) - { - } - - if (sibling != null) - { - while (sibling.isAlive()) - { - try - { - sibling.join(); - } - catch (InterruptedException e) - { - } - } - - try - { - c.cm.closeChannel(c, "StreamForwarder (" + mode + ") is cleaning up the connection", true); - } - catch (IOException e3) - { - } - } - - if (s != null) { - try - { - s.close(); - } - catch (IOException e1) - { - } - } - } - } -} + +package com.trilead.ssh2.channel; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * A StreamForwarder forwards data between two given streams. + * If two StreamForwarder threads are used (one for each direction) + * then one can be configured to shutdown the underlying channel/socket + * if both threads have finished forwarding (EOF). + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: StreamForwarder.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + */ +public class StreamForwarder extends Thread +{ + final OutputStream os; + final InputStream is; + final byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE]; + final Channel c; + final StreamForwarder sibling; + final Socket s; + final String mode; + + StreamForwarder(Channel c, StreamForwarder sibling, Socket s, InputStream is, OutputStream os, String mode) + throws IOException + { + this.is = is; + this.os = os; + this.mode = mode; + this.c = c; + this.sibling = sibling; + this.s = s; + } + + public void run() + { + try + { + while (true) + { + int len = is.read(buffer); + if (len <= 0) + break; + os.write(buffer, 0, len); + os.flush(); + } + } + catch (IOException ignore) + { + try + { + c.cm.closeChannel(c, "Closed due to exception in StreamForwarder (" + mode + "): " + + ignore.getMessage(), true); + } + catch (IOException e) + { + } + } + finally + { + try + { + os.close(); + } + catch (IOException e1) + { + } + try + { + is.close(); + } + catch (IOException e2) + { + } + + if (sibling != null) + { + while (sibling.isAlive()) + { + try + { + sibling.join(); + } + catch (InterruptedException e) + { + } + } + + try + { + c.cm.closeChannel(c, "StreamForwarder (" + mode + ") is cleaning up the connection", true); + } + catch (IOException e3) + { + } + } + + if (s != null) { + try + { + s.close(); + } + catch (IOException e1) + { + } + } + } + } +} diff --git a/lib/src/main/java/com/trilead/ssh2/channel/X11ServerData.java b/lib/src/main/java/com/trilead/ssh2/channel/X11ServerData.java index 0840376..041f9cb 100644 --- a/lib/src/main/java/com/trilead/ssh2/channel/X11ServerData.java +++ b/lib/src/main/java/com/trilead/ssh2/channel/X11ServerData.java @@ -1,16 +1,16 @@ - -package com.trilead.ssh2.channel; - -/** - * X11ServerData. Data regarding an x11 forwarding target. - * - * @author Christian Plattner, plattner@trilead.com - * @version $Id: X11ServerData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ - * - */ -public class X11ServerData -{ - public String hostname; - public int port; - public byte[] x11_magic_cookie; /* not the remote (fake) one, the local (real) one */ -} + +package com.trilead.ssh2.channel; + +/** + * X11ServerData. Data regarding an x11 forwarding target. + * + * @author Christian Plattner, plattner@trilead.com + * @version $Id: X11ServerData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $ + * + */ +public class X11ServerData +{ + public String hostname; + public int port; + public byte[] x11_magic_cookie; /* not the remote (fake) one, the local (real) one */ +} -- cgit v1.2.3