diff options
| author | Kenny Root <kenny@the-b.org> | 2013-04-11 21:01:32 -0700 | 
|---|---|---|
| committer | Kenny Root <kenny@the-b.org> | 2013-04-11 21:01:32 -0700 | 
| commit | 3359a7f6d20f4d799140e304f646491863735028 (patch) | |
| tree | dd2f06200b213687aff983bd2c67cec609f612ef /lib/src/main/java/com/trilead/ssh2/channel/ChannelManager.java | |
| parent | 54ca2c37bda02ff6f2579a20e122f3a893da705d (diff) | |
| download | sshlib-3359a7f6d20f4d799140e304f646491863735028.tar.gz sshlib-3359a7f6d20f4d799140e304f646491863735028.tar.bz2 sshlib-3359a7f6d20f4d799140e304f646491863735028.zip | |
Fix line endings
Diffstat (limited to 'lib/src/main/java/com/trilead/ssh2/channel/ChannelManager.java')
| -rw-r--r-- | lib/src/main/java/com/trilead/ssh2/channel/ChannelManager.java | 3512 | 
1 files changed, 1756 insertions, 1756 deletions
| 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.
 - * <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<String, X11ServerData> x11_magic_cookies = new HashMap<String, X11ServerData>();
 -
 -	private TransportManager tm;
 -
 -	private Vector<Channel> channels = new Vector<Channel>();
 -	private int nextLocalChannel = 100;
 -	private boolean shutdown = false;
 -	private int globalSuccessCounter = 0;
 -	private int globalFailedCounter = 0;
 -
 -	private HashMap<Integer, RemoteForwardingData> remoteForwardings = new HashMap<Integer, RemoteForwardingData>();
 -
 -	private AuthAgentCallback authAgent;
 -
 -	private Vector<IChannelWorkerThread> listenerThreads = new Vector<IChannelWorkerThread>();
 -
 -	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> channel_copy;
 -
 -		synchronized (channels)
 -		{
 -			channel_copy = (Vector<Channel>) 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> channel_copy;
 -
 -		synchronized (channels)
 -		{
 -			channel_copy = (Vector<Channel>) 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. + * <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<String, X11ServerData> x11_magic_cookies = new HashMap<String, X11ServerData>(); + +	private TransportManager tm; + +	private Vector<Channel> channels = new Vector<Channel>(); +	private int nextLocalChannel = 100; +	private boolean shutdown = false; +	private int globalSuccessCounter = 0; +	private int globalFailedCounter = 0; + +	private HashMap<Integer, RemoteForwardingData> remoteForwardings = new HashMap<Integer, RemoteForwardingData>(); + +	private AuthAgentCallback authAgent; + +	private Vector<IChannelWorkerThread> listenerThreads = new Vector<IChannelWorkerThread>(); + +	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> channel_copy; + +		synchronized (channels) +		{ +			channel_copy = (Vector<Channel>) 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> channel_copy; + +		synchronized (channels) +		{ +			channel_copy = (Vector<Channel>) 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)); +		} +	} +} | 
