blob: ad16d613c2a4556b59b0ecee94d34e2931bd5d0d [file] [log] [blame]
package com.trilead.ssh2.channel;
import com.trilead.ssh2.ChannelCondition;
import com.trilead.ssh2.log.Logger;
import com.trilead.ssh2.packets.*;
import com.trilead.ssh2.transport.MessageHandler;
import com.trilead.ssh2.transport.TransportManager;
import java.io.IOException;
import java.util.HashMap;
import java.util.Vector;
/**
* ChannelManager. Please read the comments in Channel.java.
* <p>
* 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 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 = (Channel) 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 = (Channel) 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) 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 (X11ServerData) 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) 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 = new Integer(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 = (RemoteForwardingData) remoteForwardings.get(new Integer(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);
}
}
}
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 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 = (RemoteForwardingData) remoteForwardings.get(new Integer(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;
}
/* 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 = new Integer(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 = (IChannelWorkerThread) listenerThreads.elementAt(i);
lat.stopWorking();
}
listenerThreadsAllowed = false;
}
synchronized (channels)
{
shutdown = true;
for (int i = 0; i < channels.size(); i++)
{
Channel c = (Channel) 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));
}
}
}