aboutsummaryrefslogtreecommitdiffstats
path: root/sshlib/src/main/java/com/trilead/ssh2/channel
diff options
context:
space:
mode:
authorKenny Root <kenny@the-b.org>2015-07-18 11:30:18 -0700
committerKenny Root <kenny@the-b.org>2015-07-18 11:30:18 -0700
commit2f5f3754dce85212a71138fd80c2300b73461908 (patch)
treeac51a1c9f143bc77a0cfec2e7da1b7c184394a98 /sshlib/src/main/java/com/trilead/ssh2/channel
parent0cf7ac30faecc82e04b080f418b08758624b07f5 (diff)
downloadsshlib-2f5f3754dce85212a71138fd80c2300b73461908.tar.gz
sshlib-2f5f3754dce85212a71138fd80c2300b73461908.tar.bz2
sshlib-2f5f3754dce85212a71138fd80c2300b73461908.zip
Rename project to sshlib
Diffstat (limited to 'sshlib/src/main/java/com/trilead/ssh2/channel')
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/AuthAgentForwardThread.java579
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/Channel.java207
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java86
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/ChannelManager.java1757
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java71
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java284
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java13
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java135
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java103
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java17
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java240
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java113
-rw-r--r--sshlib/src/main/java/com/trilead/ssh2/channel/X11ServerData.java16
13 files changed, 3621 insertions, 0 deletions
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/AuthAgentForwardThread.java b/sshlib/src/main/java/com/trilead/ssh2/channel/AuthAgentForwardThread.java
new file mode 100644
index 0000000..c6831e6
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/AuthAgentForwardThread.java
@@ -0,0 +1,579 @@
+/*
+ * ConnectBot: simple, powerful, open-source SSH client for Android
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.trilead.ssh2.channel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.DSAPrivateKeySpec;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.trilead.ssh2.AuthAgentCallback;
+import com.trilead.ssh2.log.Logger;
+import com.trilead.ssh2.packets.TypesReader;
+import com.trilead.ssh2.packets.TypesWriter;
+import com.trilead.ssh2.signature.DSASHA1Verify;
+import com.trilead.ssh2.signature.ECDSASHA2Verify;
+import com.trilead.ssh2.signature.RSASHA1Verify;
+
+/**
+ * AuthAgentForwardThread.
+ *
+ * @author Kenny Root
+ * @version $Id$
+ */
+public class AuthAgentForwardThread extends Thread implements IChannelWorkerThread
+{
+ private static final byte[] SSH_AGENT_FAILURE = {0, 0, 0, 1, 5}; // 5
+ private static final byte[] SSH_AGENT_SUCCESS = {0, 0, 0, 1, 6}; // 6
+
+ private static final int SSH2_AGENTC_REQUEST_IDENTITIES = 11;
+ private static final int SSH2_AGENT_IDENTITIES_ANSWER = 12;
+
+ private static final int SSH2_AGENTC_SIGN_REQUEST = 13;
+ private static final int SSH2_AGENT_SIGN_RESPONSE = 14;
+
+ private static final int SSH2_AGENTC_ADD_IDENTITY = 17;
+ private static final int SSH2_AGENTC_REMOVE_IDENTITY = 18;
+ private static final int SSH2_AGENTC_REMOVE_ALL_IDENTITIES = 19;
+
+// private static final int SSH_AGENTC_ADD_SMARTCARD_KEY = 20;
+// private static final int SSH_AGENTC_REMOVE_SMARTCARD_KEY = 21;
+
+ private static final int SSH_AGENTC_LOCK = 22;
+ private static final int SSH_AGENTC_UNLOCK = 23;
+
+ private static final int SSH2_AGENTC_ADD_ID_CONSTRAINED = 25;
+// private static final int SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED = 26;
+
+ // Constraints for adding keys
+ private static final int SSH_AGENT_CONSTRAIN_LIFETIME = 1;
+ private static final int SSH_AGENT_CONSTRAIN_CONFIRM = 2;
+
+ // Flags for signature requests
+// private static final int SSH_AGENT_OLD_SIGNATURE = 1;
+
+ private static final Logger log = Logger.getLogger(RemoteAcceptThread.class);
+
+ AuthAgentCallback authAgent;
+ OutputStream os;
+ InputStream is;
+ Channel c;
+
+ byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE];
+
+ public AuthAgentForwardThread(Channel c, AuthAgentCallback authAgent)
+ {
+ this.c = c;
+ this.authAgent = authAgent;
+
+ if (log.isEnabled())
+ log.log(20, "AuthAgentForwardThread started");
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ c.cm.registerThread(this);
+ }
+ catch (IOException e)
+ {
+ stopWorking();
+ return;
+ }
+
+ try
+ {
+ c.cm.sendOpenConfirmation(c);
+
+ is = c.getStdoutStream();
+ os = c.getStdinStream();
+
+ int totalSize = 4;
+ int readSoFar = 0;
+
+ while (true) {
+ int len;
+
+ try
+ {
+ len = is.read(buffer, readSoFar, buffer.length - readSoFar);
+ }
+ catch (IOException e)
+ {
+ stopWorking();
+ return;
+ }
+
+ if (len <= 0)
+ break;
+
+ readSoFar += len;
+
+ if (readSoFar >= 4) {
+ TypesReader tr = new TypesReader(buffer, 0, 4);
+ totalSize = tr.readUINT32() + 4;
+ }
+
+ if (totalSize == readSoFar) {
+ TypesReader tr = new TypesReader(buffer, 4, readSoFar - 4);
+ int messageType = tr.readByte();
+
+ switch (messageType) {
+ case SSH2_AGENTC_REQUEST_IDENTITIES:
+ sendIdentities();
+ break;
+ case SSH2_AGENTC_ADD_IDENTITY:
+ addIdentity(tr, false);
+ break;
+ case SSH2_AGENTC_ADD_ID_CONSTRAINED:
+ addIdentity(tr, true);
+ break;
+ case SSH2_AGENTC_REMOVE_IDENTITY:
+ removeIdentity(tr);
+ break;
+ case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
+ removeAllIdentities(tr);
+ break;
+ case SSH2_AGENTC_SIGN_REQUEST:
+ processSignRequest(tr);
+ break;
+ case SSH_AGENTC_LOCK:
+ processLockRequest(tr);
+ break;
+ case SSH_AGENTC_UNLOCK:
+ processUnlockRequest(tr);
+ break;
+ default:
+ os.write(SSH_AGENT_FAILURE);
+ break;
+ }
+
+ readSoFar = 0;
+ }
+ }
+
+ c.cm.closeChannel(c, "EOF on both streams reached.", true);
+ }
+ catch (IOException e)
+ {
+ log.log(50, "IOException in agent forwarder: " + e.getMessage());
+
+ try
+ {
+ is.close();
+ }
+ catch (IOException e1)
+ {
+ }
+
+ try
+ {
+ os.close();
+ }
+ catch (IOException e2)
+ {
+ }
+
+ try
+ {
+ c.cm.closeChannel(c, "IOException in agent forwarder (" + e.getMessage() + ")", true);
+ }
+ catch (IOException e3)
+ {
+ }
+ }
+ }
+
+ public void stopWorking() {
+ try
+ {
+ /* This will lead to an IOException in the is.read() call */
+ is.close();
+ }
+ catch (IOException e)
+ {
+ }
+ }
+
+ /**
+ * @return whether the agent is locked
+ */
+ private boolean failWhenLocked() throws IOException
+ {
+ if (authAgent.isAgentLocked()) {
+ os.write(SSH_AGENT_FAILURE);
+ return true;
+ } else
+ return false;
+ }
+
+ private void sendIdentities() throws IOException
+ {
+ Map<String,byte[]> keys = null;
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeByte(SSH2_AGENT_IDENTITIES_ANSWER);
+ int numKeys = 0;
+
+ if (!authAgent.isAgentLocked())
+ keys = authAgent.retrieveIdentities();
+
+ if (keys != null)
+ numKeys = keys.size();
+
+ tw.writeUINT32(numKeys);
+
+ if (keys != null) {
+ for (Entry<String,byte[]> entry : keys.entrySet()) {
+ byte[] keyBytes = entry.getValue();
+ tw.writeString(keyBytes, 0, keyBytes.length);
+ tw.writeString(entry.getKey());
+ }
+ }
+
+ sendPacket(tw.getBytes());
+ }
+
+ /**
+ * @param tr
+ */
+ private void addIdentity(TypesReader tr, boolean checkConstraints) {
+ try
+ {
+ if (failWhenLocked())
+ return;
+
+ String type = tr.readString();
+
+ String comment;
+ String keyType;
+ KeySpec pubSpec;
+ KeySpec privSpec;
+
+ if (type.equals("ssh-rsa")) {
+ keyType = "RSA";
+
+ BigInteger n = tr.readMPINT();
+ BigInteger e = tr.readMPINT();
+ BigInteger d = tr.readMPINT();
+ BigInteger iqmp = tr.readMPINT();
+ BigInteger p = tr.readMPINT();
+ BigInteger q = tr.readMPINT();
+ comment = tr.readString();
+
+ // Derive the extra values Java needs.
+ BigInteger dmp1 = d.mod(p.subtract(BigInteger.ONE));
+ BigInteger dmq1 = d.mod(q.subtract(BigInteger.ONE));
+
+ pubSpec = new RSAPublicKeySpec(n, e);
+ privSpec = new RSAPrivateCrtKeySpec(n, e, d, p, q, dmp1, dmq1, iqmp);
+ } else if (type.equals("ssh-dss")) {
+ keyType = "DSA";
+
+ BigInteger p = tr.readMPINT();
+ BigInteger q = tr.readMPINT();
+ BigInteger g = tr.readMPINT();
+ BigInteger y = tr.readMPINT();
+ BigInteger x = tr.readMPINT();
+ comment = tr.readString();
+
+ pubSpec = new DSAPublicKeySpec(y, p, q, g);
+ privSpec = new DSAPrivateKeySpec(x, p, q, g);
+ } else if (type.equals("ecdsa-sha2-nistp256")) {
+ keyType = "EC";
+
+ String curveName = tr.readString();
+ byte[] groupBytes = tr.readByteString();
+ BigInteger exponent = tr.readMPINT();
+ comment = tr.readString();
+
+ if (!"nistp256".equals(curveName)) {
+ log.log(2, "Invalid curve name for ecdsa-sha2-nistp256: " + curveName);
+ os.write(SSH_AGENT_FAILURE);
+ return;
+ }
+
+ ECParameterSpec nistp256 = ECDSASHA2Verify.EllipticCurves.nistp256;
+ ECPoint group = ECDSASHA2Verify.decodeECPoint(groupBytes, nistp256.getCurve());
+ if (group == null) {
+ // TODO log error
+ os.write(SSH_AGENT_FAILURE);
+ return;
+ }
+
+ pubSpec = new ECPublicKeySpec(group, nistp256);
+ privSpec = new ECPrivateKeySpec(exponent, nistp256);
+ } else {
+ log.log(2, "Unknown key type: " + type);
+ os.write(SSH_AGENT_FAILURE);
+ return;
+ }
+
+ PublicKey pubKey;
+ PrivateKey privKey;
+ try {
+ KeyFactory kf = KeyFactory.getInstance(keyType);
+ pubKey = kf.generatePublic(pubSpec);
+ privKey = kf.generatePrivate(privSpec);
+ } catch (NoSuchAlgorithmException ex) {
+ // TODO: log error
+ os.write(SSH_AGENT_FAILURE);
+ return;
+ } catch (InvalidKeySpecException ex) {
+ // TODO: log error
+ os.write(SSH_AGENT_FAILURE);
+ return;
+ }
+
+ KeyPair pair = new KeyPair(pubKey, privKey);
+
+ boolean confirmUse = false;
+ int lifetime = 0;
+
+ if (checkConstraints) {
+ while (tr.remain() > 0) {
+ int constraint = tr.readByte();
+ if (constraint == SSH_AGENT_CONSTRAIN_CONFIRM)
+ confirmUse = true;
+ else if (constraint == SSH_AGENT_CONSTRAIN_LIFETIME)
+ lifetime = tr.readUINT32();
+ else {
+ // Unknown constraint. Bail.
+ os.write(SSH_AGENT_FAILURE);
+ return;
+ }
+ }
+ }
+
+ if (authAgent.addIdentity(pair, comment, confirmUse, lifetime))
+ os.write(SSH_AGENT_SUCCESS);
+ else
+ os.write(SSH_AGENT_FAILURE);
+ }
+ catch (IOException e)
+ {
+ try
+ {
+ os.write(SSH_AGENT_FAILURE);
+ }
+ catch (IOException e1)
+ {
+ }
+ }
+ }
+
+ /**
+ * @param tr
+ */
+ private void removeIdentity(TypesReader tr) {
+ try
+ {
+ if (failWhenLocked())
+ return;
+
+ byte[] publicKey = tr.readByteString();
+ if (authAgent.removeIdentity(publicKey))
+ os.write(SSH_AGENT_SUCCESS);
+ else
+ os.write(SSH_AGENT_FAILURE);
+ }
+ catch (IOException e)
+ {
+ try
+ {
+ os.write(SSH_AGENT_FAILURE);
+ }
+ catch (IOException e1)
+ {
+ }
+ }
+ }
+
+ /**
+ * @param tr
+ */
+ private void removeAllIdentities(TypesReader tr) {
+ try
+ {
+ if (failWhenLocked())
+ return;
+
+ if (authAgent.removeAllIdentities())
+ os.write(SSH_AGENT_SUCCESS);
+ else
+ os.write(SSH_AGENT_FAILURE);
+ }
+ catch (IOException e)
+ {
+ try
+ {
+ os.write(SSH_AGENT_FAILURE);
+ }
+ catch (IOException e1)
+ {
+ }
+ }
+ }
+
+ private void processSignRequest(TypesReader tr)
+ {
+ try
+ {
+ if (failWhenLocked())
+ return;
+
+ byte[] publicKeyBytes = tr.readByteString();
+ byte[] challenge = tr.readByteString();
+
+ int flags = tr.readUINT32();
+
+ if (flags != 0) {
+ // We don't understand any flags; abort!
+ os.write(SSH_AGENT_FAILURE);
+ return;
+ }
+
+ KeyPair pair = authAgent.getKeyPair(publicKeyBytes);
+
+ if (pair == null) {
+ os.write(SSH_AGENT_FAILURE);
+ return;
+ }
+
+ byte[] response;
+
+ PrivateKey privKey = pair.getPrivate();
+ if (privKey instanceof RSAPrivateKey) {
+ byte[] signature = RSASHA1Verify.generateSignature(challenge,
+ (RSAPrivateKey) privKey);
+ response = RSASHA1Verify.encodeSSHRSASignature(signature);
+ } else if (privKey instanceof DSAPrivateKey) {
+ byte[] signature = DSASHA1Verify.generateSignature(challenge,
+ (DSAPrivateKey) privKey, new SecureRandom());
+ response = DSASHA1Verify.encodeSSHDSASignature(signature);
+ } else {
+ os.write(SSH_AGENT_FAILURE);
+ return;
+ }
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeByte(SSH2_AGENT_SIGN_RESPONSE);
+ tw.writeString(response, 0, response.length);
+
+ sendPacket(tw.getBytes());
+ }
+ catch (IOException e)
+ {
+ try
+ {
+ os.write(SSH_AGENT_FAILURE);
+ }
+ catch (IOException e1)
+ {
+ }
+ }
+ }
+
+ /**
+ * @param tr
+ */
+ private void processLockRequest(TypesReader tr) {
+ try
+ {
+ if (failWhenLocked())
+ return;
+
+ String lockPassphrase = tr.readString();
+ if (!authAgent.setAgentLock(lockPassphrase)) {
+ os.write(SSH_AGENT_FAILURE);
+ return;
+ } else
+ os.write(SSH_AGENT_SUCCESS);
+ }
+ catch (IOException e)
+ {
+ try
+ {
+ os.write(SSH_AGENT_FAILURE);
+ }
+ catch (IOException e1)
+ {
+ }
+ }
+ }
+
+ /**
+ * @param tr
+ */
+ private void processUnlockRequest(TypesReader tr)
+ {
+ try
+ {
+ String unlockPassphrase = tr.readString();
+
+ if (authAgent.requestAgentUnlock(unlockPassphrase))
+ os.write(SSH_AGENT_SUCCESS);
+ else
+ os.write(SSH_AGENT_FAILURE);
+ }
+ catch (IOException e)
+ {
+ try
+ {
+ os.write(SSH_AGENT_FAILURE);
+ }
+ catch (IOException e1)
+ {
+ }
+ }
+ }
+
+ /**
+ * @param tw
+ * @throws IOException
+ */
+ private void sendPacket(byte[] message) throws IOException
+ {
+ TypesWriter packet = new TypesWriter();
+ packet.writeUINT32(message.length);
+ packet.writeBytes(message);
+ os.write(packet.getBytes());
+ }
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/Channel.java b/sshlib/src/main/java/com/trilead/ssh2/channel/Channel.java
new file mode 100644
index 0000000..8365f12
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/Channel.java
@@ -0,0 +1,207 @@
+
+package com.trilead.ssh2.channel;
+
+/**
+ * Channel.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: Channel.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class Channel
+{
+ /*
+ * OK. Here is an important part of the JVM Specification:
+ * (http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#22214)
+ *
+ * Any association between locks and variables is purely conventional.
+ * Locking any lock conceptually flushes all variables from a thread's
+ * working memory, and unlocking any lock forces the writing out to main
+ * memory of all variables that the thread has assigned. That a lock may be
+ * associated with a particular object or a class is purely a convention.
+ * (...)
+ *
+ * If a thread uses a particular shared variable only after locking a
+ * particular lock and before the corresponding unlocking of that same lock,
+ * then the thread will read the shared value of that variable from main
+ * memory after the lock operation, if necessary, and will copy back to main
+ * memory the value most recently assigned to that variable before the
+ * unlock operation.
+ *
+ * This, in conjunction with the mutual exclusion rules for locks, suffices
+ * to guarantee that values are correctly transmitted from one thread to
+ * another through shared variables.
+ *
+ * ====> Always keep that in mind when modifying the Channel/ChannelManger
+ * code.
+ *
+ */
+
+ static final int STATE_OPENING = 1;
+ static final int STATE_OPEN = 2;
+ static final int STATE_CLOSED = 4;
+
+ static final int CHANNEL_BUFFER_SIZE = 30000;
+
+ /*
+ * To achieve correctness, the following rules have to be respected when
+ * accessing this object:
+ */
+
+ // These fields can always be read
+ final ChannelManager cm;
+ final ChannelOutputStream stdinStream;
+ final ChannelInputStream stdoutStream;
+ final ChannelInputStream stderrStream;
+
+ // These two fields will only be written while the Channel is in state
+ // STATE_OPENING.
+ // The code makes sure that the two fields are written out when the state is
+ // changing to STATE_OPEN.
+ // Therefore, if you know that the Channel is in state STATE_OPEN, then you
+ // can read these two fields without synchronizing on the Channel. However, make
+ // sure that you get the latest values (e.g., flush caches by synchronizing on any
+ // object). However, to be on the safe side, you can lock the channel.
+
+ int localID = -1;
+ int remoteID = -1;
+
+ /*
+ * Make sure that we never send a data/EOF/WindowChange msg after a CLOSE
+ * msg.
+ *
+ * This is a little bit complicated, but we have to do it in that way, since
+ * we cannot keep a lock on the Channel during the send operation (this
+ * would block sometimes the receiver thread, and, in extreme cases, can
+ * lead to a deadlock on both sides of the connection (senders are blocked
+ * since the receive buffers on the other side are full, and receiver
+ * threads wait for the senders to finish). It all depends on the
+ * implementation on the other side. But we cannot make any assumptions, we
+ * have to assume the worst case. Confused? Just believe me.
+ */
+
+ /*
+ * If you send a message on a channel, then you have to aquire the
+ * "channelSendLock" and check the "closeMessageSent" flag (this variable
+ * may only be accessed while holding the "channelSendLock" !!!
+ *
+ * BTW: NEVER EVER SEND MESSAGES FROM THE RECEIVE THREAD - see explanation
+ * above.
+ */
+
+ final Object channelSendLock = new Object();
+ boolean closeMessageSent = false;
+
+ /*
+ * Stop memory fragmentation by allocating this often used buffer.
+ * May only be used while holding the channelSendLock
+ */
+
+ final byte[] msgWindowAdjust = new byte[9];
+
+ // If you access (read or write) any of the following fields, then you have
+ // to synchronize on the channel.
+
+ int state = STATE_OPENING;
+
+ boolean closeMessageRecv = false;
+
+ /* This is a stupid implementation. At the moment we can only wait
+ * for one pending request per channel.
+ */
+ int successCounter = 0;
+ int failedCounter = 0;
+
+ int localWindow = 0; /* locally, we use a small window, < 2^31 */
+ long remoteWindow = 0; /* long for readable 2^32 - 1 window support */
+
+ int localMaxPacketSize = -1;
+ int remoteMaxPacketSize = -1;
+
+ final byte[] stdoutBuffer = new byte[CHANNEL_BUFFER_SIZE];
+ final byte[] stderrBuffer = new byte[CHANNEL_BUFFER_SIZE];
+
+ int stdoutReadpos = 0;
+ int stdoutWritepos = 0;
+ int stderrReadpos = 0;
+ int stderrWritepos = 0;
+
+ boolean EOF = false;
+
+ Integer exit_status;
+
+ String exit_signal;
+
+ // we keep the x11 cookie so that this channel can be closed when this
+ // specific x11 forwarding gets stopped
+
+ String hexX11FakeCookie;
+
+ // reasonClosed is special, since we sometimes need to access it
+ // while holding the channelSendLock.
+ // We protect it with a private short term lock.
+
+ private final Object reasonClosedLock = new Object();
+ private String reasonClosed = null;
+
+ public Channel(ChannelManager cm)
+ {
+ this.cm = cm;
+
+ this.localWindow = CHANNEL_BUFFER_SIZE;
+ this.localMaxPacketSize = 35000 - 1024; // leave enough slack
+
+ this.stdinStream = new ChannelOutputStream(this);
+ this.stdoutStream = new ChannelInputStream(this, false);
+ this.stderrStream = new ChannelInputStream(this, true);
+ }
+
+ /* Methods to allow access from classes outside of this package */
+
+ public ChannelInputStream getStderrStream()
+ {
+ return stderrStream;
+ }
+
+ public ChannelOutputStream getStdinStream()
+ {
+ return stdinStream;
+ }
+
+ public ChannelInputStream getStdoutStream()
+ {
+ return stdoutStream;
+ }
+
+ public String getExitSignal()
+ {
+ synchronized (this)
+ {
+ return exit_signal;
+ }
+ }
+
+ public Integer getExitStatus()
+ {
+ synchronized (this)
+ {
+ return exit_status;
+ }
+ }
+
+ public String getReasonClosed()
+ {
+ synchronized (reasonClosedLock)
+ {
+ return reasonClosed;
+ }
+ }
+
+ public void setReasonClosed(String reasonClosed)
+ {
+ synchronized (reasonClosedLock)
+ {
+ if (this.reasonClosed == null)
+ this.reasonClosed = reasonClosed;
+ }
+ }
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java b/sshlib/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java
new file mode 100644
index 0000000..f88522c
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java
@@ -0,0 +1,86 @@
+
+package com.trilead.ssh2.channel;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * ChannelInputStream.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: ChannelInputStream.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public final class ChannelInputStream extends InputStream
+{
+ Channel c;
+
+ boolean isClosed = false;
+ boolean isEOF = false;
+ boolean extendedFlag = false;
+
+ ChannelInputStream(Channel c, boolean isExtended)
+ {
+ this.c = c;
+ this.extendedFlag = isExtended;
+ }
+
+ public int available() throws IOException
+ {
+ if (isEOF)
+ return 0;
+
+ int avail = c.cm.getAvailable(c, extendedFlag);
+
+ /* We must not return -1 on EOF */
+
+ return (avail > 0) ? avail : 0;
+ }
+
+ public void close() throws IOException
+ {
+ isClosed = true;
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ if (b == null)
+ throw new NullPointerException();
+
+ if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length))
+ throw new IndexOutOfBoundsException();
+
+ if (len == 0)
+ return 0;
+
+ if (isEOF)
+ return -1;
+
+ int ret = c.cm.getChannelData(c, extendedFlag, b, off, len);
+
+ if (ret == -1)
+ {
+ isEOF = true;
+ }
+
+ return ret;
+ }
+
+ public int read(byte[] b) throws IOException
+ {
+ return read(b, 0, b.length);
+ }
+
+ public int read() throws IOException
+ {
+ /* Yes, this stream is pure and unbuffered, a single byte read() is slow */
+
+ final byte b[] = new byte[1];
+
+ int ret = read(b, 0, 1);
+
+ if (ret != 1)
+ return -1;
+
+ return b[0] & 0xff;
+ }
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/ChannelManager.java b/sshlib/src/main/java/com/trilead/ssh2/channel/ChannelManager.java
new file mode 100644
index 0000000..3f05468
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/ChannelManager.java
@@ -0,0 +1,1757 @@
+
+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 c
+ * @param authAgent
+ * @throws IOException
+ */
+ public boolean requestChannelAgentForwarding(Channel c, AuthAgentCallback authAgent) throws IOException {
+ synchronized (this)
+ {
+ if (this.authAgent != null)
+ throw new IllegalStateException("Auth agent already exists");
+
+ this.authAgent = authAgent;
+ }
+
+ synchronized (channels)
+ {
+ globalSuccessCounter = globalFailedCounter = 0;
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Requesting agent forwarding");
+
+ PacketChannelAuthAgentReq aar = new PacketChannelAuthAgentReq(c.remoteID);
+ tm.sendMessage(aar.getPayload());
+
+ if (waitForChannelRequestResult(c) == false) {
+ authAgent = null;
+ return false;
+ }
+
+ return true;
+ }
+
+ public void registerThread(IChannelWorkerThread thr) throws IOException
+ {
+ synchronized (listenerThreads)
+ {
+ if (listenerThreadsAllowed == false)
+ throw new IOException("Too late, this connection is closed.");
+ listenerThreads.addElement(thr);
+ }
+ }
+
+ public Channel openDirectTCPIPChannel(String host_to_connect, int port_to_connect, String originator_IP_address,
+ int originator_port) throws IOException
+ {
+ Channel c = new Channel(this);
+
+ synchronized (c)
+ {
+ c.localID = addChannel(c);
+ // end of synchronized block forces writing out to main memory
+ }
+
+ PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel(c.localID, c.localWindow,
+ c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port);
+
+ tm.sendMessage(dtc.getPayload());
+
+ waitUntilChannelOpen(c);
+
+ return c;
+ }
+
+ public Channel openSessionChannel() throws IOException
+ {
+ Channel c = new Channel(this);
+
+ synchronized (c)
+ {
+ c.localID = addChannel(c);
+ // end of synchronized block forces the writing out to main memory
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")");
+
+ PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize);
+ tm.sendMessage(smo.getPayload());
+
+ waitUntilChannelOpen(c);
+
+ return c;
+ }
+
+ public void requestGlobalTrileadPing() throws IOException
+ {
+ synchronized (channels)
+ {
+ globalSuccessCounter = globalFailedCounter = 0;
+ }
+
+ PacketGlobalTrileadPing pgtp = new PacketGlobalTrileadPing();
+
+ tm.sendMessage(pgtp.getPayload());
+
+ if (log.isEnabled())
+ log.log(50, "Sending SSH_MSG_GLOBAL_REQUEST 'trilead-ping'.");
+
+ try
+ {
+ if (waitForGlobalRequestResult() == true)
+ throw new IOException("Your server is alive - but buggy. "
+ + "It replied with SSH_MSG_REQUEST_SUCCESS when it actually should not.");
+
+ }
+ catch (IOException e)
+ {
+ throw (IOException) new IOException("The ping request failed.").initCause(e);
+ }
+ }
+
+ public void requestChannelTrileadPing(Channel c) throws IOException
+ {
+ PacketChannelTrileadPing pctp;
+
+ synchronized (c)
+ {
+ if (c.state != Channel.STATE_OPEN)
+ throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")");
+
+ pctp = new PacketChannelTrileadPing(c.remoteID);
+
+ c.successCounter = c.failedCounter = 0;
+ }
+
+ synchronized (c.channelSendLock)
+ {
+ if (c.closeMessageSent)
+ throw new IOException("Cannot ping this channel (" + c.getReasonClosed() + ")");
+ tm.sendMessage(pctp.getPayload());
+ }
+
+ try
+ {
+ if (waitForChannelRequestResult(c) == true)
+ throw new IOException("Your server is alive - but buggy. "
+ + "It replied with SSH_MSG_SESSION_SUCCESS when it actually should not.");
+
+ }
+ catch (IOException e)
+ {
+ throw (IOException) new IOException("The ping request failed.").initCause(e);
+ }
+ }
+
+ public void requestPTY(Channel c, String term, int term_width_characters, int term_height_characters,
+ int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException
+ {
+ PacketSessionPtyRequest spr;
+
+ synchronized (c)
+ {
+ if (c.state != Channel.STATE_OPEN)
+ throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")");
+
+ spr = new PacketSessionPtyRequest(c.remoteID, true, term, term_width_characters, term_height_characters,
+ term_width_pixels, term_height_pixels, terminal_modes);
+
+ c.successCounter = c.failedCounter = 0;
+ }
+
+ synchronized (c.channelSendLock)
+ {
+ if (c.closeMessageSent)
+ throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")");
+ tm.sendMessage(spr.getPayload());
+ }
+
+ try
+ {
+ if (waitForChannelRequestResult(c) == false)
+ throw new IOException("The server denied the request.");
+ }
+ catch (IOException e)
+ {
+ throw (IOException) new IOException("PTY request failed").initCause(e);
+ }
+ }
+
+
+ public void resizePTY(Channel c, int term_width_characters, int term_height_characters,
+ int term_width_pixels, int term_height_pixels) throws IOException {
+ PacketSessionPtyResize spr;
+
+ synchronized (c) {
+ if (c.state != Channel.STATE_OPEN)
+ throw new IOException("Cannot request PTY on this channel ("
+ + c.getReasonClosed() + ")");
+
+ spr = new PacketSessionPtyResize(c.remoteID, term_width_characters, term_height_characters,
+ term_width_pixels, term_height_pixels);
+ c.successCounter = c.failedCounter = 0;
+ }
+
+ synchronized (c.channelSendLock) {
+ if (c.closeMessageSent)
+ throw new IOException("Cannot request PTY on this channel ("
+ + c.getReasonClosed() + ")");
+ tm.sendMessage(spr.getPayload());
+ }
+ }
+
+
+ public void requestX11(Channel c, boolean singleConnection, String x11AuthenticationProtocol,
+ String x11AuthenticationCookie, int x11ScreenNumber) throws IOException
+ {
+ PacketSessionX11Request psr;
+
+ synchronized (c)
+ {
+ if (c.state != Channel.STATE_OPEN)
+ throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")");
+
+ psr = new PacketSessionX11Request(c.remoteID, true, singleConnection, x11AuthenticationProtocol,
+ x11AuthenticationCookie, x11ScreenNumber);
+
+ c.successCounter = c.failedCounter = 0;
+ }
+
+ synchronized (c.channelSendLock)
+ {
+ if (c.closeMessageSent)
+ throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")");
+ tm.sendMessage(psr.getPayload());
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")");
+
+ try
+ {
+ if (waitForChannelRequestResult(c) == false)
+ throw new IOException("The server denied the request.");
+ }
+ catch (IOException e)
+ {
+ throw (IOException) new IOException("The X11 request failed.").initCause(e);
+ }
+ }
+
+ public void requestSubSystem(Channel c, String subSystemName) throws IOException
+ {
+ PacketSessionSubsystemRequest ssr;
+
+ synchronized (c)
+ {
+ if (c.state != Channel.STATE_OPEN)
+ throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")");
+
+ ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName);
+
+ c.successCounter = c.failedCounter = 0;
+ }
+
+ synchronized (c.channelSendLock)
+ {
+ if (c.closeMessageSent)
+ throw new IOException("Cannot request subsystem on this channel (" + c.getReasonClosed() + ")");
+ tm.sendMessage(ssr.getPayload());
+ }
+
+ try
+ {
+ if (waitForChannelRequestResult(c) == false)
+ throw new IOException("The server denied the request.");
+ }
+ catch (IOException e)
+ {
+ throw (IOException) new IOException("The subsystem request failed.").initCause(e);
+ }
+ }
+
+ public void requestExecCommand(Channel c, String cmd) throws IOException
+ {
+ PacketSessionExecCommand sm;
+
+ synchronized (c)
+ {
+ if (c.state != Channel.STATE_OPEN)
+ throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")");
+
+ sm = new PacketSessionExecCommand(c.remoteID, true, cmd);
+
+ c.successCounter = c.failedCounter = 0;
+ }
+
+ synchronized (c.channelSendLock)
+ {
+ if (c.closeMessageSent)
+ throw new IOException("Cannot execute command on this channel (" + c.getReasonClosed() + ")");
+ tm.sendMessage(sm.getPayload());
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Executing command (channel " + c.localID + ", '" + cmd + "')");
+
+ try
+ {
+ if (waitForChannelRequestResult(c) == false)
+ throw new IOException("The server denied the request.");
+ }
+ catch (IOException e)
+ {
+ throw (IOException) new IOException("The execute request failed.").initCause(e);
+ }
+ }
+
+ public void requestShell(Channel c) throws IOException
+ {
+ PacketSessionStartShell sm;
+
+ synchronized (c)
+ {
+ if (c.state != Channel.STATE_OPEN)
+ throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")");
+
+ sm = new PacketSessionStartShell(c.remoteID, true);
+
+ c.successCounter = c.failedCounter = 0;
+ }
+
+ synchronized (c.channelSendLock)
+ {
+ if (c.closeMessageSent)
+ throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")");
+ tm.sendMessage(sm.getPayload());
+ }
+
+ try
+ {
+ if (waitForChannelRequestResult(c) == false)
+ throw new IOException("The server denied the request.");
+ }
+ catch (IOException e)
+ {
+ throw (IOException) new IOException("The shell request failed.").initCause(e);
+ }
+ }
+
+ public void msgChannelExtendedData(byte[] msg, int msglen) throws IOException
+ {
+ if (msglen <= 13)
+ throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")");
+
+ int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
+ int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);
+ int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff);
+
+ Channel c = getChannel(id);
+
+ if (c == null)
+ throw new IOException("Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id);
+
+ if (dataType != Packets.SSH_EXTENDED_DATA_STDERR)
+ throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")");
+
+ if (len != (msglen - 13))
+ throw new IOException("SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13)
+ + ", got " + len + ")");
+
+ if (log.isEnabled())
+ log.log(80, "Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")");
+
+ synchronized (c)
+ {
+ if (c.state == Channel.STATE_CLOSED)
+ return; // ignore
+
+ if (c.state != Channel.STATE_OPEN)
+ throw new IOException("Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state ("
+ + c.state + ")");
+
+ if (c.localWindow < len)
+ throw new IOException("Remote sent too much data, does not fit into window.");
+
+ c.localWindow -= len;
+
+ System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len);
+ c.stderrWritepos += len;
+
+ c.notifyAll();
+ }
+ }
+
+ /**
+ * Wait until for a condition.
+ *
+ * @param c
+ * Channel
+ * @param timeout
+ * in ms, 0 means no timeout.
+ * @param condition_mask
+ * minimum event mask
+ * @return all current events
+ *
+ */
+ public int waitForCondition(Channel c, long timeout, int condition_mask)
+ {
+ long end_time = 0;
+ boolean end_time_set = false;
+
+ synchronized (c)
+ {
+ while (true)
+ {
+ int current_cond = 0;
+
+ int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos;
+ int stderrAvail = c.stderrWritepos - c.stderrReadpos;
+
+ if (stdoutAvail > 0)
+ current_cond = current_cond | ChannelCondition.STDOUT_DATA;
+
+ if (stderrAvail > 0)
+ current_cond = current_cond | ChannelCondition.STDERR_DATA;
+
+ if (c.EOF)
+ current_cond = current_cond | ChannelCondition.EOF;
+
+ if (c.getExitStatus() != null)
+ current_cond = current_cond | ChannelCondition.EXIT_STATUS;
+
+ if (c.getExitSignal() != null)
+ current_cond = current_cond | ChannelCondition.EXIT_SIGNAL;
+
+ if (c.state == Channel.STATE_CLOSED)
+ return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF;
+
+ if ((current_cond & condition_mask) != 0)
+ return current_cond;
+
+ if (timeout > 0)
+ {
+ if (!end_time_set)
+ {
+ end_time = System.currentTimeMillis() + timeout;
+ end_time_set = true;
+ }
+ else
+ {
+ timeout = end_time - System.currentTimeMillis();
+
+ if (timeout <= 0)
+ return current_cond | ChannelCondition.TIMEOUT;
+ }
+ }
+
+ try
+ {
+ if (timeout > 0)
+ c.wait(timeout);
+ else
+ c.wait();
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+ }
+ }
+
+ public int getAvailable(Channel c, boolean extended) throws IOException
+ {
+ synchronized (c)
+ {
+ int avail;
+
+ if (extended)
+ avail = c.stderrWritepos - c.stderrReadpos;
+ else
+ avail = c.stdoutWritepos - c.stdoutReadpos;
+
+ return ((avail > 0) ? avail : (c.EOF ? -1 : 0));
+ }
+ }
+
+ public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException
+ {
+ int copylen = 0;
+ int increment = 0;
+ int remoteID = 0;
+ int localID = 0;
+
+ synchronized (c)
+ {
+ int stdoutAvail = 0;
+ int stderrAvail = 0;
+
+ while (true)
+ {
+ /*
+ * Data available? We have to return remaining data even if the
+ * channel is already closed.
+ */
+
+ stdoutAvail = c.stdoutWritepos - c.stdoutReadpos;
+ stderrAvail = c.stderrWritepos - c.stderrReadpos;
+
+ if ((!extended) && (stdoutAvail != 0))
+ break;
+
+ if ((extended) && (stderrAvail != 0))
+ break;
+
+ /* Do not wait if more data will never arrive (EOF or CLOSED) */
+
+ if ((c.EOF) || (c.state != Channel.STATE_OPEN))
+ return -1;
+
+ try
+ {
+ c.wait();
+ }
+ catch (InterruptedException ignore)
+ {
+ }
+ }
+
+ /* OK, there is some data. Return it. */
+
+ if (!extended)
+ {
+ copylen = (stdoutAvail > len) ? len : stdoutAvail;
+ System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen);
+ c.stdoutReadpos += copylen;
+
+ if (c.stdoutReadpos != c.stdoutWritepos)
+
+ System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos
+ - c.stdoutReadpos);
+
+ c.stdoutWritepos -= c.stdoutReadpos;
+ c.stdoutReadpos = 0;
+ }
+ else
+ {
+ copylen = (stderrAvail > len) ? len : stderrAvail;
+ System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen);
+ c.stderrReadpos += copylen;
+
+ if (c.stderrReadpos != c.stderrWritepos)
+
+ System.arraycopy(c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos
+ - c.stderrReadpos);
+
+ c.stderrWritepos -= c.stderrReadpos;
+ c.stderrReadpos = 0;
+ }
+
+ if (c.state != Channel.STATE_OPEN)
+ return copylen;
+
+ if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2))
+ {
+ int minFreeSpace = Math.min(Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, Channel.CHANNEL_BUFFER_SIZE
+ - c.stderrWritepos);
+
+ increment = minFreeSpace - c.localWindow;
+ c.localWindow = minFreeSpace;
+ }
+
+ remoteID = c.remoteID; /* read while holding the lock */
+ localID = c.localID; /* read while holding the lock */
+ }
+
+ /*
+ * If a consumer reads stdout and stdin in parallel, we may end up with
+ * sending two msgWindowAdjust messages. Luckily, it
+ * does not matter in which order they arrive at the server.
+ */
+
+ if (increment > 0)
+ {
+ if (log.isEnabled())
+ log.log(80, "Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")");
+
+ synchronized (c.channelSendLock)
+ {
+ byte[] msg = c.msgWindowAdjust;
+
+ msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST;
+ msg[1] = (byte) (remoteID >> 24);
+ msg[2] = (byte) (remoteID >> 16);
+ msg[3] = (byte) (remoteID >> 8);
+ msg[4] = (byte) (remoteID);
+ msg[5] = (byte) (increment >> 24);
+ msg[6] = (byte) (increment >> 16);
+ msg[7] = (byte) (increment >> 8);
+ msg[8] = (byte) (increment);
+
+ if (c.closeMessageSent == false)
+ tm.sendMessage(msg);
+ }
+ }
+
+ return copylen;
+ }
+
+ public void msgChannelData(byte[] msg, int msglen) throws IOException
+ {
+ if (msglen <= 9)
+ throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")");
+
+ int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
+ int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);
+
+ Channel c = getChannel(id);
+
+ if (c == null)
+ throw new IOException("Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id);
+
+ if (len != (msglen - 9))
+ throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got "
+ + len + ")");
+
+ if (log.isEnabled())
+ log.log(80, "Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")");
+
+ synchronized (c)
+ {
+ if (c.state == Channel.STATE_CLOSED)
+ return; // ignore
+
+ if (c.state != Channel.STATE_OPEN)
+ throw new IOException("Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")");
+
+ if (c.localWindow < len)
+ throw new IOException("Remote sent too much data, does not fit into window.");
+
+ c.localWindow -= len;
+
+ System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len);
+ c.stdoutWritepos += len;
+
+ c.notifyAll();
+ }
+ }
+
+ public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException
+ {
+ if (msglen != 9)
+ throw new IOException("SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")");
+
+ int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
+ int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff);
+
+ Channel c = getChannel(id);
+
+ if (c == null)
+ throw new IOException("Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id);
+
+ synchronized (c)
+ {
+ final long huge = 0xFFFFffffL; /* 2^32 - 1 */
+
+ c.remoteWindow += (windowChange & huge); /* avoid sign extension */
+
+ /* TODO - is this a good heuristic? */
+
+ if ((c.remoteWindow > huge))
+ c.remoteWindow = huge;
+
+ c.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(80, "Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")");
+ }
+
+ public void msgChannelOpen(byte[] msg, int msglen) throws IOException
+ {
+ TypesReader tr = new TypesReader(msg, 0, msglen);
+
+ tr.readByte(); // skip packet type
+ String channelType = tr.readString();
+ int remoteID = tr.readUINT32(); /* sender channel */
+ int remoteWindow = tr.readUINT32(); /* initial window size */
+ int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */
+
+ if ("x11".equals(channelType))
+ {
+ synchronized (x11_magic_cookies)
+ {
+ /* If we did not request X11 forwarding, then simply ignore this bogus request. */
+
+ if (x11_magic_cookies.size() == 0)
+ {
+ PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID,
+ Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", "");
+
+ tm.sendAsynchronousMessage(pcof.getPayload());
+
+ if (log.isEnabled())
+ log.log(20, "Unexpected X11 request, denying it!");
+
+ return;
+ }
+ }
+
+ String remoteOriginatorAddress = tr.readString();
+ int remoteOriginatorPort = tr.readUINT32();
+
+ Channel c = new Channel(this);
+
+ synchronized (c)
+ {
+ c.remoteID = remoteID;
+ c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */
+ c.remoteMaxPacketSize = remoteMaxPacketSize;
+ c.localID = addChannel(c);
+ }
+
+ /*
+ * The open confirmation message will be sent from another thread
+ */
+
+ RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort);
+ rxat.setDaemon(true);
+ rxat.start();
+
+ return;
+ }
+
+ if ("forwarded-tcpip".equals(channelType))
+ {
+ String remoteConnectedAddress = tr.readString(); /* address that was connected */
+ int remoteConnectedPort = tr.readUINT32(); /* port that was connected */
+ String remoteOriginatorAddress = tr.readString(); /* originator IP address */
+ int remoteOriginatorPort = tr.readUINT32(); /* originator port */
+
+ RemoteForwardingData rfd = null;
+
+ synchronized (remoteForwardings)
+ {
+ rfd = remoteForwardings.get(Integer.valueOf(remoteConnectedPort));
+ }
+
+ if (rfd == null)
+ {
+ PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID,
+ Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
+ "No thanks, unknown port in forwarded-tcpip request", "");
+
+ /* Always try to be polite. */
+
+ tm.sendAsynchronousMessage(pcof.getPayload());
+
+ if (log.isEnabled())
+ log.log(20, "Unexpected forwarded-tcpip request, denying it!");
+
+ return;
+ }
+
+ Channel c = new Channel(this);
+
+ synchronized (c)
+ {
+ c.remoteID = remoteID;
+ c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */
+ c.remoteMaxPacketSize = remoteMaxPacketSize;
+ c.localID = addChannel(c);
+ }
+
+ /*
+ * The open confirmation message will be sent from another thread.
+ */
+
+ RemoteAcceptThread rat = new RemoteAcceptThread(c, remoteConnectedAddress, remoteConnectedPort,
+ remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort);
+
+ rat.setDaemon(true);
+ rat.start();
+
+ return;
+ }
+
+ if ("auth-agent@openssh.com".equals(channelType)) {
+ Channel c = new Channel(this);
+
+ synchronized (c)
+ {
+ c.remoteID = remoteID;
+ c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */
+ c.remoteMaxPacketSize = remoteMaxPacketSize;
+ c.localID = addChannel(c);
+ }
+
+ AuthAgentForwardThread aat = new AuthAgentForwardThread(c, authAgent);
+
+ aat.setDaemon(true);
+ aat.start();
+
+ return;
+ }
+
+ /* Tell the server that we have no idea what it is talking about */
+
+ PacketChannelOpenFailure pcof = new PacketChannelOpenFailure(remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE,
+ "Unknown channel type", "");
+
+ tm.sendAsynchronousMessage(pcof.getPayload());
+
+ if (log.isEnabled())
+ log.log(20, "The peer tried to open an unsupported channel type (" + channelType + ")");
+ }
+
+ public void msgChannelRequest(byte[] msg, int msglen) throws IOException
+ {
+ TypesReader tr = new TypesReader(msg, 0, msglen);
+
+ tr.readByte(); // skip packet type
+ int id = tr.readUINT32();
+
+ Channel c = getChannel(id);
+
+ if (c == null)
+ throw new IOException("Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id);
+
+ String type = tr.readString("US-ASCII");
+ boolean wantReply = tr.readBoolean();
+
+ if (log.isEnabled())
+ log.log(80, "Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')");
+
+ if (type.equals("exit-status"))
+ {
+ if (wantReply != false)
+ throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true");
+
+ int exit_status = tr.readUINT32();
+
+ if (tr.remain() != 0)
+ throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");
+
+ synchronized (c)
+ {
+ c.exit_status = Integer.valueOf(exit_status);
+ c.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Got EXIT STATUS (channel " + id + ", status " + exit_status + ")");
+
+ return;
+ }
+
+ if (type.equals("exit-signal"))
+ {
+ if (wantReply != false)
+ throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true");
+
+ String signame = tr.readString("US-ASCII");
+ tr.readBoolean();
+ tr.readString();
+ tr.readString();
+
+ if (tr.remain() != 0)
+ throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message");
+
+ synchronized (c)
+ {
+ c.exit_signal = signame;
+ c.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")");
+
+ return;
+ }
+
+ /* We simply ignore unknown channel requests, however, if the server wants a reply,
+ * then we signal that we have no idea what it is about.
+ */
+
+ if (wantReply)
+ {
+ byte[] reply = new byte[5];
+
+ reply[0] = Packets.SSH_MSG_CHANNEL_FAILURE;
+ reply[1] = (byte) (c.remoteID >> 24);
+ reply[2] = (byte) (c.remoteID >> 16);
+ reply[3] = (byte) (c.remoteID >> 8);
+ reply[4] = (byte) (c.remoteID);
+
+ tm.sendAsynchronousMessage(reply);
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Channel request '" + type + "' is not known, ignoring it");
+ }
+
+ public void msgChannelEOF(byte[] msg, int msglen) throws IOException
+ {
+ if (msglen != 5)
+ throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")");
+
+ int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
+
+ Channel c = getChannel(id);
+
+ if (c == null)
+ throw new IOException("Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id);
+
+ synchronized (c)
+ {
+ c.EOF = true;
+ c.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Got SSH_MSG_CHANNEL_EOF (channel " + id + ")");
+ }
+
+ public void msgChannelClose(byte[] msg, int msglen) throws IOException
+ {
+ if (msglen != 5)
+ throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")");
+
+ int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
+
+ Channel c = getChannel(id);
+
+ if (c == null)
+ throw new IOException("Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id);
+
+ synchronized (c)
+ {
+ c.EOF = true;
+ c.state = Channel.STATE_CLOSED;
+ c.setReasonClosed("Close requested by remote");
+ c.closeMessageRecv = true;
+
+ removeChannel(c.localID);
+
+ c.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")");
+ }
+
+ public void msgChannelSuccess(byte[] msg, int msglen) throws IOException
+ {
+ if (msglen != 5)
+ throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")");
+
+ int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
+
+ Channel c = getChannel(id);
+
+ if (c == null)
+ throw new IOException("Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id);
+
+ synchronized (c)
+ {
+ c.successCounter++;
+ c.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(80, "Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")");
+ }
+
+ public void msgChannelFailure(byte[] msg, int msglen) throws IOException
+ {
+ if (msglen != 5)
+ throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")");
+
+ int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff);
+
+ Channel c = getChannel(id);
+
+ if (c == null)
+ throw new IOException("Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id);
+
+ synchronized (c)
+ {
+ c.failedCounter++;
+ c.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")");
+ }
+
+ public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException
+ {
+ PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen);
+
+ Channel c = getChannel(sm.recipientChannelID);
+
+ if (c == null)
+ throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel "
+ + sm.recipientChannelID);
+
+ synchronized (c)
+ {
+ if (c.state != Channel.STATE_OPENING)
+ throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel "
+ + sm.recipientChannelID);
+
+ c.remoteID = sm.senderChannelID;
+ c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */
+ c.remoteMaxPacketSize = sm.maxPacketSize;
+ c.state = Channel.STATE_OPEN;
+ c.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: "
+ + sm.senderChannelID + ")");
+ }
+
+ public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException
+ {
+ if (msglen < 5)
+ throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")");
+
+ TypesReader tr = new TypesReader(msg, 0, msglen);
+
+ tr.readByte(); // skip packet type
+ int id = tr.readUINT32(); /* sender channel */
+
+ Channel c = getChannel(id);
+
+ if (c == null)
+ throw new IOException("Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id);
+
+ int reasonCode = tr.readUINT32();
+ String description = tr.readString("UTF-8");
+
+ String reasonCodeSymbolicName = null;
+
+ switch (reasonCode)
+ {
+ case 1:
+ reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED";
+ break;
+ case 2:
+ reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED";
+ break;
+ case 3:
+ reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE";
+ break;
+ case 4:
+ reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE";
+ break;
+ default:
+ reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")";
+ }
+
+ StringBuffer descriptionBuffer = new StringBuffer();
+ descriptionBuffer.append(description);
+
+ for (int i = 0; i < descriptionBuffer.length(); i++)
+ {
+ char cc = descriptionBuffer.charAt(i);
+
+ if ((cc >= 32) && (cc <= 126))
+ continue;
+ descriptionBuffer.setCharAt(i, '\uFFFD');
+ }
+
+ synchronized (c)
+ {
+ c.EOF = true;
+ c.state = Channel.STATE_CLOSED;
+ c.setReasonClosed("The server refused to open the channel (" + reasonCodeSymbolicName + ", '"
+ + descriptionBuffer.toString() + "')");
+ c.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(50, "Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")");
+ }
+
+ public void msgGlobalRequest(byte[] msg, int msglen) throws IOException
+ {
+ /* Currently we do not support any kind of global request */
+
+ TypesReader tr = new TypesReader(msg, 0, msglen);
+
+ tr.readByte(); // skip packet type
+ String requestName = tr.readString();
+ boolean wantReply = tr.readBoolean();
+
+ if (wantReply)
+ {
+ byte[] reply_failure = new byte[1];
+ reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE;
+
+ tm.sendAsynchronousMessage(reply_failure);
+ }
+
+ /* We do not clean up the requestName String - that is OK for debug */
+
+ if (log.isEnabled())
+ log.log(80, "Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")");
+ }
+
+ public void msgGlobalSuccess() throws IOException
+ {
+ synchronized (channels)
+ {
+ globalSuccessCounter++;
+ channels.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(80, "Got SSH_MSG_REQUEST_SUCCESS");
+ }
+
+ public void msgGlobalFailure() throws IOException
+ {
+ synchronized (channels)
+ {
+ globalFailedCounter++;
+ channels.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(80, "Got SSH_MSG_REQUEST_FAILURE");
+ }
+
+ public void handleMessage(byte[] msg, int msglen) throws IOException
+ {
+ if (msg == null)
+ {
+ if (log.isEnabled())
+ log.log(50, "HandleMessage: got shutdown");
+
+ synchronized (listenerThreads)
+ {
+ for (int i = 0; i < listenerThreads.size(); i++)
+ {
+ IChannelWorkerThread lat = listenerThreads.elementAt(i);
+ lat.stopWorking();
+ }
+ listenerThreadsAllowed = false;
+ }
+
+ synchronized (channels)
+ {
+ shutdown = true;
+
+ for (int i = 0; i < channels.size(); i++)
+ {
+ Channel c = channels.elementAt(i);
+ synchronized (c)
+ {
+ c.EOF = true;
+ c.state = Channel.STATE_CLOSED;
+ c.setReasonClosed("The connection is being shutdown");
+ c.closeMessageRecv = true; /*
+ * You never know, perhaps
+ * we are waiting for a
+ * pending close message
+ * from the server...
+ */
+ c.notifyAll();
+ }
+ }
+ /* Works with J2ME */
+ channels.setSize(0);
+ channels.trimToSize();
+ channels.notifyAll(); /* Notify global response waiters */
+ return;
+ }
+ }
+
+ switch (msg[0])
+ {
+ case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
+ msgChannelOpenConfirmation(msg, msglen);
+ break;
+ case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST:
+ msgChannelWindowAdjust(msg, msglen);
+ break;
+ case Packets.SSH_MSG_CHANNEL_DATA:
+ msgChannelData(msg, msglen);
+ break;
+ case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA:
+ msgChannelExtendedData(msg, msglen);
+ break;
+ case Packets.SSH_MSG_CHANNEL_REQUEST:
+ msgChannelRequest(msg, msglen);
+ break;
+ case Packets.SSH_MSG_CHANNEL_EOF:
+ msgChannelEOF(msg, msglen);
+ break;
+ case Packets.SSH_MSG_CHANNEL_OPEN:
+ msgChannelOpen(msg, msglen);
+ break;
+ case Packets.SSH_MSG_CHANNEL_CLOSE:
+ msgChannelClose(msg, msglen);
+ break;
+ case Packets.SSH_MSG_CHANNEL_SUCCESS:
+ msgChannelSuccess(msg, msglen);
+ break;
+ case Packets.SSH_MSG_CHANNEL_FAILURE:
+ msgChannelFailure(msg, msglen);
+ break;
+ case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE:
+ msgChannelOpenFailure(msg, msglen);
+ break;
+ case Packets.SSH_MSG_GLOBAL_REQUEST:
+ msgGlobalRequest(msg, msglen);
+ break;
+ case Packets.SSH_MSG_REQUEST_SUCCESS:
+ msgGlobalSuccess();
+ break;
+ case Packets.SSH_MSG_REQUEST_FAILURE:
+ msgGlobalFailure();
+ break;
+ default:
+ throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff));
+ }
+ }
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java b/sshlib/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java
new file mode 100644
index 0000000..c1d56e8
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java
@@ -0,0 +1,71 @@
+package com.trilead.ssh2.channel;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * ChannelOutputStream.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: ChannelOutputStream.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public final class ChannelOutputStream extends OutputStream
+{
+ Channel c;
+
+ private byte[] writeBuffer;
+
+ boolean isClosed = false;
+
+ ChannelOutputStream(Channel c)
+ {
+ this.c = c;
+ writeBuffer = new byte[1];
+ }
+
+ public void write(int b) throws IOException
+ {
+ writeBuffer[0] = (byte) b;
+
+ write(writeBuffer, 0, 1);
+ }
+
+ public void close() throws IOException
+ {
+ if (isClosed == false)
+ {
+ isClosed = true;
+ c.cm.sendEOF(c);
+ }
+ }
+
+ public void flush() throws IOException
+ {
+ if (isClosed)
+ throw new IOException("This OutputStream is closed.");
+
+ /* This is a no-op, since this stream is unbuffered */
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException
+ {
+ if (isClosed)
+ throw new IOException("This OutputStream is closed.");
+
+ if (b == null)
+ throw new NullPointerException();
+
+ if ((off < 0) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0) || (off > b.length))
+ throw new IndexOutOfBoundsException();
+
+ if (len == 0)
+ return;
+
+ c.cm.sendData(c, b, off, len);
+ }
+
+ public void write(byte[] b) throws IOException
+ {
+ write(b, 0, b.length);
+ }
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java b/sshlib/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java
new file mode 100644
index 0000000..ef3a3d0
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java
@@ -0,0 +1,284 @@
+/*
+ * ConnectBot: simple, powerful, open-source SSH client for Android
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.trilead.ssh2.channel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.PushbackInputStream;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NoRouteToHostException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import net.sourceforge.jsocks.Proxy;
+import net.sourceforge.jsocks.ProxyMessage;
+import net.sourceforge.jsocks.Socks4Message;
+import net.sourceforge.jsocks.Socks5Message;
+import net.sourceforge.jsocks.SocksException;
+import net.sourceforge.jsocks.server.ServerAuthenticator;
+import net.sourceforge.jsocks.server.ServerAuthenticatorNone;
+
+/**
+ * DynamicAcceptThread.
+ *
+ * @author Kenny Root
+ * @version $Id$
+ */
+public class DynamicAcceptThread extends Thread implements IChannelWorkerThread {
+ private ChannelManager cm;
+ private ServerSocket ss;
+
+ class DynamicAcceptRunnable implements Runnable {
+ private static final int idleTimeout = 180000; //3 minutes
+
+ private ServerAuthenticator auth;
+ private Socket sock;
+ private InputStream in;
+ private OutputStream out;
+ private ProxyMessage msg;
+
+ public DynamicAcceptRunnable(ServerAuthenticator auth, Socket sock) {
+ this.auth = auth;
+ this.sock = sock;
+
+ setName("DynamicAcceptRunnable");
+ }
+
+ public void run() {
+ try {
+ startSession();
+ } catch (IOException ioe) {
+ int error_code = Proxy.SOCKS_FAILURE;
+
+ if (ioe instanceof SocksException)
+ error_code = ((SocksException) ioe).errCode;
+ else if (ioe instanceof NoRouteToHostException)
+ error_code = Proxy.SOCKS_HOST_UNREACHABLE;
+ else if (ioe instanceof ConnectException)
+ error_code = Proxy.SOCKS_CONNECTION_REFUSED;
+ else if (ioe instanceof InterruptedIOException)
+ error_code = Proxy.SOCKS_TTL_EXPIRE;
+
+ if (error_code > Proxy.SOCKS_ADDR_NOT_SUPPORTED
+ || error_code < 0) {
+ error_code = Proxy.SOCKS_FAILURE;
+ }
+
+ sendErrorMessage(error_code);
+ } finally {
+ if (auth != null)
+ auth.endSession();
+ }
+ }
+
+ private ProxyMessage readMsg(InputStream in) throws IOException {
+ PushbackInputStream push_in;
+ if (in instanceof PushbackInputStream)
+ push_in = (PushbackInputStream) in;
+ else
+ push_in = new PushbackInputStream(in);
+
+ int version = push_in.read();
+ push_in.unread(version);
+
+ ProxyMessage msg;
+
+ if (version == 5) {
+ msg = new Socks5Message(push_in, false);
+ } else if (version == 4) {
+ msg = new Socks4Message(push_in, false);
+ } else {
+ throw new SocksException(Proxy.SOCKS_FAILURE);
+ }
+ return msg;
+ }
+
+ private void sendErrorMessage(int error_code) {
+ ProxyMessage err_msg;
+ if (msg instanceof Socks4Message)
+ err_msg = new Socks4Message(Socks4Message.REPLY_REJECTED);
+ else
+ err_msg = new Socks5Message(error_code);
+ try {
+ err_msg.write(out);
+ } catch (IOException ioe) {
+ }
+ }
+
+ private void handleRequest(ProxyMessage msg) throws IOException {
+ if (!auth.checkRequest(msg))
+ throw new SocksException(Proxy.SOCKS_FAILURE);
+
+ switch (msg.command) {
+ case Proxy.SOCKS_CMD_CONNECT:
+ onConnect(msg);
+ break;
+ default:
+ throw new SocksException(Proxy.SOCKS_CMD_NOT_SUPPORTED);
+ }
+ }
+
+ private void startSession() throws IOException {
+ sock.setSoTimeout(idleTimeout);
+
+ try {
+ auth = auth.startSession(sock);
+ } catch (IOException ioe) {
+ System.out.println("Could not start SOCKS session");
+ ioe.printStackTrace();
+ auth = null;
+ return;
+ }
+
+ if (auth == null) { // Authentication failed
+ System.out.println("SOCKS auth failed");
+ return;
+ }
+
+ in = auth.getInputStream();
+ out = auth.getOutputStream();
+
+ msg = readMsg(in);
+ handleRequest(msg);
+ }
+
+ private void onConnect(ProxyMessage msg) throws IOException {
+ ProxyMessage response = null;
+ Channel cn = null;
+ StreamForwarder r2l = null;
+ StreamForwarder l2r = null;
+
+ if (msg instanceof Socks5Message) {
+ response = new Socks5Message(Proxy.SOCKS_SUCCESS, (InetAddress)null, 0);
+ } else {
+ response = new Socks4Message(Socks4Message.REPLY_OK, (InetAddress)null, 0);
+ }
+ response.write(out);
+
+ String destHost = msg.host;
+ if (msg.ip != null)
+ destHost = msg.ip.getHostAddress();
+
+ try {
+ /*
+ * This may fail, e.g., if the remote port is closed (in
+ * optimistic terms: not open yet)
+ */
+
+ cn = cm.openDirectTCPIPChannel(destHost, msg.port,
+ "127.0.0.1", 0);
+
+ } catch (IOException e) {
+ /*
+ * Simply close the local socket and wait for the next incoming
+ * connection
+ */
+
+ try {
+ sock.close();
+ } catch (IOException ignore) {
+ }
+
+ return;
+ }
+
+ try {
+ r2l = new StreamForwarder(cn, null, sock, cn.stdoutStream, out, "RemoteToLocal");
+ l2r = new StreamForwarder(cn, r2l, sock, in, cn.stdinStream, "LocalToRemote");
+ } catch (IOException e) {
+ try {
+ /*
+ * This message is only visible during debugging, since we
+ * discard the channel immediatelly
+ */
+ cn.cm.closeChannel(cn,
+ "Weird error during creation of StreamForwarder ("
+ + e.getMessage() + ")", true);
+ } catch (IOException ignore) {
+ }
+
+ return;
+ }
+
+ r2l.setDaemon(true);
+ l2r.setDaemon(true);
+ r2l.start();
+ l2r.start();
+ }
+ }
+
+ public DynamicAcceptThread(ChannelManager cm, int local_port)
+ throws IOException {
+ this.cm = cm;
+
+ setName("DynamicAcceptThread");
+
+ ss = new ServerSocket(local_port);
+ }
+
+ public DynamicAcceptThread(ChannelManager cm, InetSocketAddress localAddress)
+ throws IOException {
+ this.cm = cm;
+
+ ss = new ServerSocket();
+ ss.bind(localAddress);
+ }
+
+ @Override
+ public void run() {
+ try {
+ cm.registerThread(this);
+ } catch (IOException e) {
+ stopWorking();
+ return;
+ }
+
+ while (true) {
+ Socket sock = null;
+
+ try {
+ sock = ss.accept();
+ } catch (IOException e) {
+ stopWorking();
+ return;
+ }
+
+ DynamicAcceptRunnable dar = new DynamicAcceptRunnable(new ServerAuthenticatorNone(), sock);
+ Thread t = new Thread(dar);
+ t.setDaemon(true);
+ t.start();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.trilead.ssh2.channel.IChannelWorkerThread#stopWorking()
+ */
+ public void stopWorking() {
+ try {
+ /* This will lead to an IOException in the ss.accept() call */
+ ss.close();
+ } catch (IOException e) {
+ }
+ }
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java b/sshlib/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java
new file mode 100644
index 0000000..bce9b1b
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java
@@ -0,0 +1,13 @@
+
+package com.trilead.ssh2.channel;
+
+/**
+ * IChannelWorkerThread.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: IChannelWorkerThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+interface IChannelWorkerThread
+{
+ public void stopWorking();
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java b/sshlib/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java
new file mode 100644
index 0000000..0d1bb35
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java
@@ -0,0 +1,135 @@
+
+package com.trilead.ssh2.channel;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * LocalAcceptThread.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: LocalAcceptThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class LocalAcceptThread extends Thread implements IChannelWorkerThread
+{
+ ChannelManager cm;
+ String host_to_connect;
+ int port_to_connect;
+
+ final ServerSocket ss;
+
+ public LocalAcceptThread(ChannelManager cm, int local_port, String host_to_connect, int port_to_connect)
+ throws IOException
+ {
+ this.cm = cm;
+ this.host_to_connect = host_to_connect;
+ this.port_to_connect = port_to_connect;
+
+ ss = new ServerSocket(local_port);
+ }
+
+ public LocalAcceptThread(ChannelManager cm, InetSocketAddress localAddress, String host_to_connect,
+ int port_to_connect) throws IOException
+ {
+ this.cm = cm;
+ this.host_to_connect = host_to_connect;
+ this.port_to_connect = port_to_connect;
+
+ ss = new ServerSocket();
+ ss.bind(localAddress);
+ }
+
+ public void run()
+ {
+ try
+ {
+ cm.registerThread(this);
+ }
+ catch (IOException e)
+ {
+ stopWorking();
+ return;
+ }
+
+ while (true)
+ {
+ Socket s = null;
+
+ try
+ {
+ s = ss.accept();
+ }
+ catch (IOException e)
+ {
+ stopWorking();
+ return;
+ }
+
+ Channel cn = null;
+ StreamForwarder r2l = null;
+ StreamForwarder l2r = null;
+
+ try
+ {
+ /* This may fail, e.g., if the remote port is closed (in optimistic terms: not open yet) */
+
+ cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, s.getInetAddress().getHostAddress(), s
+ .getPort());
+
+ }
+ catch (IOException e)
+ {
+ /* Simply close the local socket and wait for the next incoming connection */
+
+ try
+ {
+ s.close();
+ }
+ catch (IOException ignore)
+ {
+ }
+
+ continue;
+ }
+
+ try
+ {
+ r2l = new StreamForwarder(cn, null, s, cn.stdoutStream, s.getOutputStream(), "RemoteToLocal");
+ l2r = new StreamForwarder(cn, r2l, s, s.getInputStream(), cn.stdinStream, "LocalToRemote");
+ }
+ catch (IOException e)
+ {
+ try
+ {
+ /* This message is only visible during debugging, since we discard the channel immediatelly */
+ cn.cm.closeChannel(cn, "Weird error during creation of StreamForwarder (" + e.getMessage() + ")",
+ true);
+ }
+ catch (IOException ignore)
+ {
+ }
+
+ continue;
+ }
+
+ r2l.setDaemon(true);
+ l2r.setDaemon(true);
+ r2l.start();
+ l2r.start();
+ }
+ }
+
+ public void stopWorking()
+ {
+ try
+ {
+ /* This will lead to an IOException in the ss.accept() call */
+ ss.close();
+ }
+ catch (IOException e)
+ {
+ }
+ }
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java b/sshlib/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java
new file mode 100644
index 0000000..29b02b8
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java
@@ -0,0 +1,103 @@
+
+package com.trilead.ssh2.channel;
+
+import java.io.IOException;
+import java.net.Socket;
+
+import com.trilead.ssh2.log.Logger;
+
+
+/**
+ * RemoteAcceptThread.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: RemoteAcceptThread.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class RemoteAcceptThread extends Thread
+{
+ private static final Logger log = Logger.getLogger(RemoteAcceptThread.class);
+
+ Channel c;
+
+ String remoteConnectedAddress;
+ int remoteConnectedPort;
+ String remoteOriginatorAddress;
+ int remoteOriginatorPort;
+ String targetAddress;
+ int targetPort;
+
+ Socket s;
+
+ public RemoteAcceptThread(Channel c, String remoteConnectedAddress, int remoteConnectedPort,
+ String remoteOriginatorAddress, int remoteOriginatorPort, String targetAddress, int targetPort)
+ {
+ this.c = c;
+ this.remoteConnectedAddress = remoteConnectedAddress;
+ this.remoteConnectedPort = remoteConnectedPort;
+ this.remoteOriginatorAddress = remoteOriginatorAddress;
+ this.remoteOriginatorPort = remoteOriginatorPort;
+ this.targetAddress = targetAddress;
+ this.targetPort = targetPort;
+
+ if (log.isEnabled())
+ log.log(20, "RemoteAcceptThread: " + remoteConnectedAddress + "/" + remoteConnectedPort + ", R: "
+ + remoteOriginatorAddress + "/" + remoteOriginatorPort);
+ }
+
+ public void run()
+ {
+ try
+ {
+ c.cm.sendOpenConfirmation(c);
+
+ s = new Socket(targetAddress, targetPort);
+
+ StreamForwarder r2l = new StreamForwarder(c, null, s, c.getStdoutStream(), s.getOutputStream(),
+ "RemoteToLocal");
+ StreamForwarder l2r = new StreamForwarder(c, null, null, s.getInputStream(), c.getStdinStream(),
+ "LocalToRemote");
+
+ /* No need to start two threads, one can be executed in the current thread */
+
+ r2l.setDaemon(true);
+ r2l.start();
+ l2r.run();
+
+ while (r2l.isAlive())
+ {
+ try
+ {
+ r2l.join();
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+
+ /* If the channel is already closed, then this is a no-op */
+
+ c.cm.closeChannel(c, "EOF on both streams reached.", true);
+ s.close();
+ }
+ catch (IOException e)
+ {
+ log.log(50, "IOException in proxy code: " + e.getMessage());
+
+ try
+ {
+ c.cm.closeChannel(c, "IOException in proxy code (" + e.getMessage() + ")", true);
+ }
+ catch (IOException e1)
+ {
+ }
+ try
+ {
+ if (s != null)
+ s.close();
+ }
+ catch (IOException e1)
+ {
+ }
+ }
+ }
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java b/sshlib/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java
new file mode 100644
index 0000000..d05378e
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java
@@ -0,0 +1,17 @@
+
+package com.trilead.ssh2.channel;
+
+/**
+ * RemoteForwardingData. Data about a requested remote forwarding.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: RemoteForwardingData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class RemoteForwardingData
+{
+ public String bindAddress;
+ public int bindPort;
+
+ String targetAddress;
+ int targetPort;
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java b/sshlib/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java
new file mode 100644
index 0000000..9f99410
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java
@@ -0,0 +1,240 @@
+
+package com.trilead.ssh2.channel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+import com.trilead.ssh2.log.Logger;
+
+
+/**
+ * RemoteX11AcceptThread.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: RemoteX11AcceptThread.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
+ */
+public class RemoteX11AcceptThread extends Thread
+{
+ private static final Logger log = Logger.getLogger(RemoteX11AcceptThread.class);
+
+ Channel c;
+
+ String remoteOriginatorAddress;
+ int remoteOriginatorPort;
+
+ Socket s;
+
+ public RemoteX11AcceptThread(Channel c, String remoteOriginatorAddress, int remoteOriginatorPort)
+ {
+ this.c = c;
+ this.remoteOriginatorAddress = remoteOriginatorAddress;
+ this.remoteOriginatorPort = remoteOriginatorPort;
+ }
+
+ public void run()
+ {
+ try
+ {
+ /* Send Open Confirmation */
+
+ c.cm.sendOpenConfirmation(c);
+
+ /* Read startup packet from client */
+
+ OutputStream remote_os = c.getStdinStream();
+ InputStream remote_is = c.getStdoutStream();
+
+ /* The following code is based on the protocol description given in:
+ * Scheifler/Gettys,
+ * X Windows System: Core and Extension Protocols:
+ * X Version 11, Releases 6 and 6.1 ISBN 1-55558-148-X
+ */
+
+ /*
+ * Client startup:
+ *
+ * 1 0X42 MSB first/0x6c lSB first - byteorder
+ * 1 - unused
+ * 2 card16 - protocol-major-version
+ * 2 card16 - protocol-minor-version
+ * 2 n - lenght of authorization-protocol-name
+ * 2 d - lenght of authorization-protocol-data
+ * 2 - unused
+ * string8 - authorization-protocol-name
+ * p - unused, p=pad(n)
+ * string8 - authorization-protocol-data
+ * q - unused, q=pad(d)
+ *
+ * pad(X) = (4 - (X mod 4)) mod 4
+ *
+ * Server response:
+ *
+ * 1 (0 failed, 2 authenticate, 1 success)
+ * ...
+ *
+ */
+
+ /* Later on we will simply forward the first 6 header bytes to the "real" X11 server */
+
+ byte[] header = new byte[6];
+
+ if (remote_is.read(header) != 6)
+ throw new IOException("Unexpected EOF on X11 startup!");
+
+ if ((header[0] != 0x42) && (header[0] != 0x6c)) // 0x42 MSB first, 0x6C LSB first
+ throw new IOException("Unknown endian format in X11 message!");
+
+ /* Yes, I came up with this myself - shall I file an application for a patent? =) */
+
+ int idxMSB = (header[0] == 0x42) ? 0 : 1;
+
+ /* Read authorization data header */
+
+ byte[] auth_buff = new byte[6];
+
+ if (remote_is.read(auth_buff) != 6)
+ throw new IOException("Unexpected EOF on X11 startup!");
+
+ int authProtocolNameLength = ((auth_buff[idxMSB] & 0xff) << 8) | (auth_buff[1 - idxMSB] & 0xff);
+ int authProtocolDataLength = ((auth_buff[2 + idxMSB] & 0xff) << 8) | (auth_buff[3 - idxMSB] & 0xff);
+
+ if ((authProtocolNameLength > 256) || (authProtocolDataLength > 256))
+ throw new IOException("Buggy X11 authorization data");
+
+ int authProtocolNamePadding = ((4 - (authProtocolNameLength % 4)) % 4);
+ int authProtocolDataPadding = ((4 - (authProtocolDataLength % 4)) % 4);
+
+ byte[] authProtocolName = new byte[authProtocolNameLength];
+ byte[] authProtocolData = new byte[authProtocolDataLength];
+
+ byte[] paddingBuffer = new byte[4];
+
+ if (remote_is.read(authProtocolName) != authProtocolNameLength)
+ throw new IOException("Unexpected EOF on X11 startup! (authProtocolName)");
+
+ if (remote_is.read(paddingBuffer, 0, authProtocolNamePadding) != authProtocolNamePadding)
+ throw new IOException("Unexpected EOF on X11 startup! (authProtocolNamePadding)");
+
+ if (remote_is.read(authProtocolData) != authProtocolDataLength)
+ throw new IOException("Unexpected EOF on X11 startup! (authProtocolData)");
+
+ if (remote_is.read(paddingBuffer, 0, authProtocolDataPadding) != authProtocolDataPadding)
+ throw new IOException("Unexpected EOF on X11 startup! (authProtocolDataPadding)");
+
+ if ("MIT-MAGIC-COOKIE-1".equals(new String(authProtocolName, "ISO-8859-1")) == false)
+ throw new IOException("Unknown X11 authorization protocol!");
+
+ if (authProtocolDataLength != 16)
+ throw new IOException("Wrong data length for X11 authorization data!");
+
+ StringBuffer tmp = new StringBuffer(32);
+ for (int i = 0; i < authProtocolData.length; i++)
+ {
+ String digit2 = Integer.toHexString(authProtocolData[i] & 0xff);
+ tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2);
+ }
+ String hexEncodedFakeCookie = tmp.toString();
+
+ /* Order is very important here - it may be that a certain x11 forwarding
+ * gets disabled right in the moment when we check and register our connection
+ * */
+
+ synchronized (c)
+ {
+ /* Please read the comment in Channel.java */
+ c.hexX11FakeCookie = hexEncodedFakeCookie;
+ }
+
+ /* Now check our fake cookie directory to see if we produced this cookie */
+
+ X11ServerData sd = c.cm.checkX11Cookie(hexEncodedFakeCookie);
+
+ if (sd == null)
+ throw new IOException("Invalid X11 cookie received.");
+
+ /* If the session which corresponds to this cookie is closed then we will
+ * detect this: the session's close code will close all channels
+ * with the session's assigned x11 fake cookie.
+ */
+
+ s = new Socket(sd.hostname, sd.port);
+
+ OutputStream x11_os = s.getOutputStream();
+ InputStream x11_is = s.getInputStream();
+
+ /* Now we are sending the startup packet to the real X11 server */
+
+ x11_os.write(header);
+
+ if (sd.x11_magic_cookie == null)
+ {
+ byte[] emptyAuthData = new byte[6];
+ /* empty auth data, hopefully you are connecting to localhost =) */
+ x11_os.write(emptyAuthData);
+ }
+ else
+ {
+ if (sd.x11_magic_cookie.length != 16)
+ throw new IOException("The real X11 cookie has an invalid length!");
+
+ /* send X11 cookie specified by client */
+ x11_os.write(auth_buff);
+ x11_os.write(authProtocolName); /* re-use */
+ x11_os.write(paddingBuffer, 0, authProtocolNamePadding);
+ x11_os.write(sd.x11_magic_cookie);
+ x11_os.write(paddingBuffer, 0, authProtocolDataPadding);
+ }
+
+ x11_os.flush();
+
+ /* Start forwarding traffic */
+
+ StreamForwarder r2l = new StreamForwarder(c, null, s, remote_is, x11_os, "RemoteToX11");
+ StreamForwarder l2r = new StreamForwarder(c, null, null, x11_is, remote_os, "X11ToRemote");
+
+ /* No need to start two threads, one can be executed in the current thread */
+
+ r2l.setDaemon(true);
+ r2l.start();
+ l2r.run();
+
+ while (r2l.isAlive())
+ {
+ try
+ {
+ r2l.join();
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+
+ /* If the channel is already closed, then this is a no-op */
+
+ c.cm.closeChannel(c, "EOF on both X11 streams reached.", true);
+ s.close();
+ }
+ catch (IOException e)
+ {
+ log.log(50, "IOException in X11 proxy code: " + e.getMessage());
+
+ try
+ {
+ c.cm.closeChannel(c, "IOException in X11 proxy code (" + e.getMessage() + ")", true);
+ }
+ catch (IOException e1)
+ {
+ }
+ try
+ {
+ if (s != null)
+ s.close();
+ }
+ catch (IOException e1)
+ {
+ }
+ }
+ }
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java b/sshlib/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java
new file mode 100644
index 0000000..e1afee8
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java
@@ -0,0 +1,113 @@
+
+package com.trilead.ssh2.channel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+/**
+ * A StreamForwarder forwards data between two given streams.
+ * If two StreamForwarder threads are used (one for each direction)
+ * then one can be configured to shutdown the underlying channel/socket
+ * if both threads have finished forwarding (EOF).
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: StreamForwarder.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class StreamForwarder extends Thread
+{
+ final OutputStream os;
+ final InputStream is;
+ final byte[] buffer = new byte[Channel.CHANNEL_BUFFER_SIZE];
+ final Channel c;
+ final StreamForwarder sibling;
+ final Socket s;
+ final String mode;
+
+ StreamForwarder(Channel c, StreamForwarder sibling, Socket s, InputStream is, OutputStream os, String mode)
+ throws IOException
+ {
+ this.is = is;
+ this.os = os;
+ this.mode = mode;
+ this.c = c;
+ this.sibling = sibling;
+ this.s = s;
+ }
+
+ public void run()
+ {
+ try
+ {
+ while (true)
+ {
+ int len = is.read(buffer);
+ if (len <= 0)
+ break;
+ os.write(buffer, 0, len);
+ os.flush();
+ }
+ }
+ catch (IOException ignore)
+ {
+ try
+ {
+ c.cm.closeChannel(c, "Closed due to exception in StreamForwarder (" + mode + "): "
+ + ignore.getMessage(), true);
+ }
+ catch (IOException e)
+ {
+ }
+ }
+ finally
+ {
+ try
+ {
+ os.close();
+ }
+ catch (IOException e1)
+ {
+ }
+ try
+ {
+ is.close();
+ }
+ catch (IOException e2)
+ {
+ }
+
+ if (sibling != null)
+ {
+ while (sibling.isAlive())
+ {
+ try
+ {
+ sibling.join();
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+
+ try
+ {
+ c.cm.closeChannel(c, "StreamForwarder (" + mode + ") is cleaning up the connection", true);
+ }
+ catch (IOException e3)
+ {
+ }
+ }
+
+ if (s != null) {
+ try
+ {
+ s.close();
+ }
+ catch (IOException e1)
+ {
+ }
+ }
+ }
+ }
+}
diff --git a/sshlib/src/main/java/com/trilead/ssh2/channel/X11ServerData.java b/sshlib/src/main/java/com/trilead/ssh2/channel/X11ServerData.java
new file mode 100644
index 0000000..041f9cb
--- /dev/null
+++ b/sshlib/src/main/java/com/trilead/ssh2/channel/X11ServerData.java
@@ -0,0 +1,16 @@
+
+package com.trilead.ssh2.channel;
+
+/**
+ * X11ServerData. Data regarding an x11 forwarding target.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: X11ServerData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ *
+ */
+public class X11ServerData
+{
+ public String hostname;
+ public int port;
+ public byte[] x11_magic_cookie; /* not the remote (fake) one, the local (real) one */
+}