diff --git a/core/function-impl/mogo-core-function-devatools-rviz/.gitignore b/core/function-impl/mogo-core-function-devatools-rviz/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/build.gradle b/core/function-impl/mogo-core-function-devatools-rviz/build.gradle
new file mode 100644
index 0000000000..6bee5136ce
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/build.gradle
@@ -0,0 +1,92 @@
+plugins {
+ id 'com.android.library'
+ id 'kotlin-android'
+ id 'kotlin-android-extensions'
+ id 'kotlin-kapt'
+}
+
+android {
+ compileSdkVersion rootProject.ext.android.compileSdkVersion
+ defaultConfig {
+ minSdkVersion rootProject.ext.android.minSdkVersion
+ targetSdkVersion rootProject.ext.android.targetSdkVersion
+ versionCode Integer.valueOf(VERSION_CODE)
+ versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles 'consumer-rules.pro'
+
+
+ javaCompileOptions {
+ annotationProcessorOptions {
+ arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
+ }
+ }
+ }
+
+ //排除包中的proto文件
+ packagingOptions {
+ exclude '**/*.proto'
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+
+ kotlinOptions {
+ jvmTarget = "1.8"
+ freeCompilerArgs += [
+ "-Xopt-in=kotlin.RequiresOptIn"
+ ]
+ }
+
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation rootProject.ext.dependencies.kotlinstdlib
+ implementation rootProject.ext.dependencies.mogologlib
+
+ implementation rootProject.ext.dependencies.androidx_datastore
+ implementation rootProject.ext.dependencies.androidxroomruntime
+ kapt rootProject.ext.dependencies.androidxroomcompiler
+ implementation rootProject.ext.dependencies.rxandroid
+ implementation rootProject.ext.dependencies.androidxcardview
+ implementation rootProject.ext.dependencies.androidxroomktx
+ implementation rootProject.ext.dependencies.androidxappcompat
+ implementation rootProject.ext.dependencies.androidxconstraintlayout
+ implementation rootProject.ext.dependencies.androidxrecyclerview
+
+ implementation rootProject.ext.dependencies.life_cycle_scope
+
+ implementation project(':core:mogo-core-function-call')
+ implementation project(':core:mogo-core-function-api')
+ implementation project(':core:mogo-core-res')
+ implementation project(':foudations:mogo-commons')
+ implementation 'io.github.h07000223:flycoTabLayout:3.0.0'
+ implementation project(':core:mogo-core-utils')
+ implementation project(':libraries:mogo-adas')
+ implementation project(':libraries:mogo-adas-data')
+
+ //----------------SSH start---------------------
+ //implementation('org.connectbot:sshlib:2.2.21')
+ //正常引用 org.connectbot:sshlib:2.2.21库由于存在冲突 故将原始代码拷贝到项目中并引用库中的第三方库
+ implementation("com.jcraft:jzlib:1.1.3")
+ implementation("org.connectbot:simplesocks:1.0.1")
+ implementation("com.google.crypto.tink:tink:1.7.0")
+ implementation("org.connectbot:jbcrypt:1.0.2")
+ //----------------SSH end---------------------
+ implementation 'org.conscrypt:conscrypt-android:2.5.2'
+}
+
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/gradle.properties b/core/function-impl/mogo-core-function-devatools-rviz/gradle.properties
new file mode 100644
index 0000000000..768f0ce1ac
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/gradle.properties
@@ -0,0 +1,3 @@
+GROUP=com.mogo.eagle.core.function.impl
+POM_ARTIFACT_ID=devatools_rviz
+VERSION_CODE=1
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/proguard-rules.pro b/core/function-impl/mogo-core-function-devatools-rviz/proguard-rules.pro
new file mode 100644
index 0000000000..3dbb226707
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/proguard-rules.pro
@@ -0,0 +1,23 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-dontwarn android.arch.persistence.**
\ No newline at end of file
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/AndroidManifest.xml b/core/function-impl/mogo-core-function-devatools-rviz/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..7d1e9d6a3e
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/AndroidManifest.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/assets/AutoPilotVisualDB.db b/core/function-impl/mogo-core-function-devatools-rviz/src/main/assets/AutoPilotVisualDB.db
new file mode 100644
index 0000000000..b3cc78585e
Binary files /dev/null and b/core/function-impl/mogo-core-function-devatools-rviz/src/main/assets/AutoPilotVisualDB.db differ
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/assets/AutoPilotVisualDB.db-shm b/core/function-impl/mogo-core-function-devatools-rviz/src/main/assets/AutoPilotVisualDB.db-shm
new file mode 100644
index 0000000000..60c4f90f28
Binary files /dev/null and b/core/function-impl/mogo-core-function-devatools-rviz/src/main/assets/AutoPilotVisualDB.db-shm differ
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/assets/AutoPilotVisualDB.db-wal b/core/function-impl/mogo-core-function-devatools-rviz/src/main/assets/AutoPilotVisualDB.db-wal
new file mode 100644
index 0000000000..813a6f51a5
Binary files /dev/null and b/core/function-impl/mogo-core-function-devatools-rviz/src/main/assets/AutoPilotVisualDB.db-wal differ
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/AuthAgentCallback.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/AuthAgentCallback.java
new file mode 100644
index 0000000000..5373db4043
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/AuthAgentCallback.java
@@ -0,0 +1,64 @@
+package com.trilead.ssh2;
+
+import java.security.KeyPair;
+import java.util.Map;
+
+/**
+ * AuthAgentCallback.
+ *
+ * @author Kenny Root
+ * @version $Id$
+ */
+public interface AuthAgentCallback {
+
+ /**
+ * @return array of blobs containing the OpenSSH-format encoded public keys
+ */
+ Map retrieveIdentities();
+
+ /**
+ * @param pair A RSAPrivateKey, ECPrivateKey, or
+ * DSAPrivateKey containing a DSA, EC, or RSA private
+ * and corresponding PublicKey.
+ * @param comment comment associated with this key
+ * @param confirmUse whether to prompt before using this key
+ * @param lifetime lifetime in seconds for key to be remembered
+ * @return success or failure
+ */
+ boolean addIdentity(KeyPair pair, String comment, boolean confirmUse, int lifetime);
+
+ /**
+ * @param publicKey byte blob containing the OpenSSH-format encoded public key
+ * @return success or failure
+ */
+ boolean removeIdentity(byte[] publicKey);
+
+ /**
+ * @return success or failure
+ */
+ boolean removeAllIdentities();
+
+ /**
+ * @param publicKey byte blob containing the OpenSSH-format encoded public key
+ * @return A RSAPrivateKey or DSAPrivateKey
+ * containing a DSA or RSA private key of
+ * the user in Trilead object format.
+ */
+ KeyPair getKeyPair(byte[] publicKey);
+
+ /**
+ * @return
+ */
+ boolean isAgentLocked();
+
+ /**
+ * @param lockPassphrase
+ */
+ boolean setAgentLock(String lockPassphrase);
+
+ /**
+ * @param unlockPassphrase
+ * @return
+ */
+ boolean requestAgentUnlock(String unlockPassphrase);
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ChannelCondition.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ChannelCondition.java
new file mode 100644
index 0000000000..ef8a5aa3e8
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ChannelCondition.java
@@ -0,0 +1,61 @@
+
+package com.trilead.ssh2;
+
+/**
+ * Contains constants that can be used to specify what conditions to wait for on
+ * a SSH-2 channel (e.g., represented by a {@link Session}).
+ *
+ * @see Session#waitForCondition(int, long)
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: ChannelCondition.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public interface ChannelCondition
+{
+ /**
+ * A timeout has occurred, none of your requested conditions is fulfilled.
+ * However, other conditions may be true - therefore, NEVER use the "=="
+ * operator to test for this (or any other) condition. Always use
+ * something like ((cond & ChannelCondition.CLOSED) != 0).
+ */
+ int TIMEOUT = 1;
+
+ /**
+ * The underlying SSH-2 channel, however not necessarily the whole connection,
+ * has been closed. This implies EOF. Note that there may still
+ * be unread stdout or stderr data in the local window, i.e, STDOUT_DATA
+ * or/and STDERR_DATA may be set at the same time.
+ */
+ int CLOSED = 2;
+
+ /**
+ * There is stdout data available that is ready to be consumed.
+ */
+ int STDOUT_DATA = 4;
+
+ /**
+ * There is stderr data available that is ready to be consumed.
+ */
+ int STDERR_DATA = 8;
+
+ /**
+ * EOF on has been reached, no more _new_ stdout or stderr data will arrive
+ * from the remote server. However, there may be unread stdout or stderr
+ * data, i.e, STDOUT_DATA or/and STDERR_DATA
+ * may be set at the same time.
+ */
+ int EOF = 16;
+
+ /**
+ * The exit status of the remote process is available.
+ * Some servers never send the exist status, or occasionally "forget" to do so.
+ */
+ int EXIT_STATUS = 32;
+
+ /**
+ * The exit signal of the remote process is available.
+ */
+ int EXIT_SIGNAL = 64;
+
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/Connection.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/Connection.java
new file mode 100644
index 0000000000..64e3fe0d45
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/Connection.java
@@ -0,0 +1,1564 @@
+
+package com.trilead.ssh2;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketTimeoutException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.util.Vector;
+
+import com.trilead.ssh2.auth.AuthenticationManager;
+import com.trilead.ssh2.auth.SignatureProxy;
+import com.trilead.ssh2.channel.ChannelManager;
+import com.trilead.ssh2.crypto.CryptoWishList;
+import com.trilead.ssh2.crypto.cipher.BlockCipherFactory;
+import com.trilead.ssh2.crypto.digest.MACs;
+import com.trilead.ssh2.log.Logger;
+import com.trilead.ssh2.packets.PacketIgnore;
+import com.trilead.ssh2.transport.KexManager;
+import com.trilead.ssh2.transport.TransportManager;
+import com.trilead.ssh2.util.TimeoutService;
+import com.trilead.ssh2.util.TimeoutService.TimeoutToken;
+
+/**
+ * A Connection is used to establish an encrypted TCP/IP
+ * connection to a SSH-2 server.
+ *
+ * Typically, one
+ *
+ *
creates a {@link #Connection(String) Connection} object.
+ *
calls the {@link #connect() connect()} method.
+ *
calls some of the authentication methods (e.g.,
+ * {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}).
+ *
calls one or several times the {@link #openSession() openSession()}
+ * method.
+ *
finally, one must close the connection and release resources with the
+ * {@link #close() close()} method.
+ *
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: Connection.java,v 1.3 2008/04/01 12:38:09 cplattne Exp $
+ */
+
+public class Connection implements AutoCloseable
+{
+ /**
+ * The identifier presented to the SSH-2 server.
+ */
+ public final static String identification = "TrileadSSH2Java_213";
+
+ /**
+ * Will be used to generate all random data needed for the current
+ * connection. Note: SecureRandom.nextBytes() is thread safe.
+ */
+ private SecureRandom generator;
+
+ /**
+ * Unless you know what you are doing, you will never need this.
+ *
+ * @return The list of supported cipher algorithms by this implementation.
+ */
+ public static synchronized String[] getAvailableCiphers()
+ {
+ return BlockCipherFactory.getDefaultCipherList();
+ }
+
+ /**
+ * Unless you know what you are doing, you will never need this.
+ *
+ * @return The list of supported MAC algorthims by this implementation.
+ */
+ public static synchronized String[] getAvailableMACs()
+ {
+ return MACs.getMacList();
+ }
+
+ /**
+ * Unless you know what you are doing, you will never need this.
+ *
+ * @return The list of supported server host key algorthims by this
+ * implementation.
+ */
+ public static synchronized String[] getAvailableServerHostKeyAlgorithms()
+ {
+ return KexManager.getDefaultServerHostkeyAlgorithmList();
+ }
+
+ private AuthenticationManager am;
+
+ private boolean authenticated = false;
+ private boolean compression = false;
+ private ChannelManager cm;
+
+ private CryptoWishList cryptoWishList = new CryptoWishList();
+
+ private DHGexParameters dhgexpara = new DHGexParameters();
+
+ private final String hostname;
+
+ private final int port;
+
+ private TransportManager tm;
+
+ private ProxyData proxyData = null;
+
+ private Vector connectionMonitors = new Vector();
+
+ /**
+ * Prepares a fresh Connection object which can then be used
+ * to establish a connection to the specified SSH-2 server.
+ *
+ * Same as {@link #Connection(String, int) Connection(hostname, 22)}.
+ *
+ * @param hostname
+ * the hostname of the SSH-2 server.
+ */
+ public Connection(String hostname)
+ {
+ this(hostname, 22);
+ }
+
+ /**
+ * Prepares a fresh Connection object which can then be used
+ * to establish a connection to the specified SSH-2 server.
+ *
+ * @param hostname
+ * the host where we later want to connect to.
+ * @param port
+ * port on the server, normally 22.
+ */
+ public Connection(String hostname, int port)
+ {
+ this.hostname = hostname;
+ this.port = port;
+ }
+
+ /**
+ * A wrapper that calls
+ * {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback)
+ * authenticateWithKeyboardInteractivewith} a null submethod
+ * list.
+ *
+ * @param user
+ * A String holding the username.
+ * @param cb
+ * An InteractiveCallback which will be used to
+ * determine the responses to the questions asked by the server.
+ * @return whether the connection is now authenticated.
+ * @throws IOException
+ */
+ public synchronized boolean authenticateWithKeyboardInteractive(String user, InteractiveCallback cb)
+ throws IOException
+ {
+ return authenticateWithKeyboardInteractive(user, null, cb);
+ }
+
+ /**
+ * After a successful connect, one has to authenticate oneself. This method
+ * is based on "keyboard-interactive", specified in
+ * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a
+ * callback object which will be feeded with challenges generated by the
+ * server. Answers are then sent back to the server. It is possible that the
+ * callback will be called several times during the invocation of this
+ * method (e.g., if the server replies to the callback's answer(s) with
+ * another challenge...)
+ *
+ * If the authentication phase is complete, true will be
+ * returned. If the server does not accept the request (or if further
+ * authentication steps are needed), false is returned and
+ * one can retry either by using this or any other authentication method
+ * (use the getRemainingAuthMethods method to get a list of
+ * the remaining possible methods).
+ *
+ * Note: some SSH servers advertise "keyboard-interactive", however, any
+ * interactive request will be denied (without having sent any challenge to
+ * the client).
+ *
+ * @param user
+ * A String holding the username.
+ * @param submethods
+ * An array of submethod names, see
+ * draft-ietf-secsh-auth-kbdinteract-XX. May be null
+ * to indicate an empty list.
+ * @param cb
+ * An InteractiveCallback which will be used to
+ * determine the responses to the questions asked by the server.
+ *
+ * @return whether the connection is now authenticated.
+ * @throws IOException
+ */
+ public synchronized boolean authenticateWithKeyboardInteractive(String user, String[] submethods,
+ InteractiveCallback cb) throws IOException
+ {
+ if (cb == null)
+ throw new IllegalArgumentException("Callback may not ne NULL!");
+
+ checkRequirements(user);
+
+ authenticated = am.authenticateInteractive(user, submethods, cb);
+
+ return authenticated;
+ }
+
+ /**
+ * After a successful connect, one has to authenticate oneself. This method
+ * sends username and password to the server.
+ *
+ * If the authentication phase is complete, true will be
+ * returned. If the server does not accept the request (or if further
+ * authentication steps are needed), false is returned and
+ * one can retry either by using this or any other authentication method
+ * (use the getRemainingAuthMethods method to get a list of
+ * the remaining possible methods).
+ *
+ * Note: if this method fails, then please double-check that it is actually
+ * offered by the server (use
+ * {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}.
+ *
+ * Often, password authentication is disabled, but users are not aware of
+ * it. Many servers only offer "publickey" and "keyboard-interactive".
+ * However, even though "keyboard-interactive" *feels* like password
+ * authentication (e.g., when using the putty or openssh clients) it is
+ * *not* the same mechanism.
+ *
+ * @param user
+ * @param password
+ * @return if the connection is now authenticated.
+ * @throws IOException
+ */
+ public synchronized boolean authenticateWithPassword(String user, String password) throws IOException
+ {
+ if (password == null)
+ throw new IllegalArgumentException("password argument is null");
+
+ checkRequirements(user);
+
+ authenticated = am.authenticatePassword(user, password);
+
+ return authenticated;
+ }
+
+ /**
+ * After a successful connect, one has to authenticate oneself. This method
+ * can be used to explicitly use the special "none" authentication method
+ * (where only a username has to be specified).
+ *
+ * Note 1: The "none" method may always be tried by clients, however as by
+ * the specs, the server will not explicitly announce it. In other words,
+ * the "none" token will never show up in the list returned by
+ * {@link #getRemainingAuthMethods(String)}.
+ *
+ * Note 2: no matter which one of the authenticateWithXXX() methods you
+ * call, the library will always issue exactly one initial "none"
+ * authentication request to retrieve the initially allowed list of
+ * authentication methods by the server. Please read RFC 4252 for the
+ * details.
+ *
+ * If the authentication phase is complete, true will be
+ * returned. If further authentication steps are needed, false
+ * is returned and one can retry by any other authentication method (use the
+ * getRemainingAuthMethods method to get a list of the
+ * remaining possible methods).
+ *
+ * @param user the username to attempt to log in as
+ * @return if the connection is now authenticated.
+ * @throws IOException
+ */
+ public synchronized boolean authenticateWithNone(String user) throws IOException
+ {
+ checkRequirements(user);
+
+ /* Trigger the sending of the PacketUserauthRequestNone packet */
+ /* (if not already done) */
+
+ authenticated = am.authenticateNone(user);
+
+ return authenticated;
+ }
+
+ /**
+ * After a successful connect, one has to authenticate oneself. The
+ * authentication method "publickey" works by signing a challenge sent by
+ * the server. The signature is either DSA, EC, or RSA based - it just depends
+ * on the type of private key you specify, either a DSA or RSA private key in
+ * PEM format. And yes, this is may seem to be a little confusing, the
+ * method is called "publickey" in the SSH-2 protocol specification, however
+ * since we need to generate a signature, you actually have to supply a
+ * private key =).
+ *
+ * The private key contained in the PEM file may also be encrypted
+ * ("Proc-Type: 4,ENCRYPTED"). The library supports DES-CBC and DES-EDE3-CBC
+ * encryption, as well as the more exotic PEM encryption AES-128-CBC,
+ * AES-192-CBC and AES-256-CBC.
+ *
+ * If the authentication phase is complete, true will be
+ * returned. If the server does not accept the request (or if further
+ * authentication steps are needed), false is returned and
+ * one can retry either by using this or any other authentication method
+ * (use the getRemainingAuthMethods method to get a list of
+ * the remaining possible methods).
+ *
+ * NOTE PUTTY USERS: Event though your key file may start with
+ * "-----BEGIN..." it is not in the expected format. You have to convert it
+ * to the OpenSSH key format by using the "puttygen" tool (can be downloaded
+ * from the Putty website). Simply load your key and then use the
+ * "Conversions/Export OpenSSH key" functionality to get a proper PEM file.
+ *
+ * @param user
+ * A String holding the username.
+ * @param pemPrivateKey
+ * A char[] containing a DSA or RSA private key of
+ * the user in OpenSSH key format (PEM, you can't miss the
+ * "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE
+ * KEY-----" tag). The char array may contain
+ * linebreaks/linefeeds.
+ * @param password
+ * If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED")
+ * then you must specify a password. Otherwise, this argument
+ * will be ignored and can be set to null.
+ *
+ * @return whether the connection is now authenticated.
+ * @throws IOException
+ */
+ public synchronized boolean authenticateWithPublicKey(String user, char[] pemPrivateKey, String password)
+ throws IOException
+ {
+ if (pemPrivateKey == null)
+ throw new IllegalArgumentException("pemPrivateKey argument is null");
+
+ checkRequirements(user);
+
+ authenticated = am.authenticatePublicKey(user, pemPrivateKey, password, getOrCreateSecureRND());
+
+ return authenticated;
+ }
+
+ /**
+ * After a successful connect, one has to authenticate oneself. The
+ * authentication method "publickey" works by signing a challenge sent by
+ * the server. The signature is either DSA, EC, or RSA based - it just depends
+ * on the type of private key you specify, either a DSA, EC, or RSA private key
+ * in PEM format. And yes, this is may seem to be a little confusing, the
+ * method is called "publickey" in the SSH-2 protocol specification, however
+ * since we need to generate a signature, you actually have to supply a
+ * private key =).
+ *
+ * If the authentication phase is complete, true will be
+ * returned. If the server does not accept the request (or if further
+ * authentication steps are needed), false is returned and
+ * one can retry either by using this or any other authentication method
+ * (use the getRemainingAuthMethods method to get a list of
+ * the remaining possible methods).
+ *
+ * @param user
+ * A String holding the username.
+ * @param pair
+ * A KeyPair containing a RSAPrivateKey,
+ * DSAPrivateKey, or ECPrivateKey and
+ * corresponding PublicKey.
+ *
+ * @return whether the connection is now authenticated.
+ * @throws IOException
+ */
+ public synchronized boolean authenticateWithPublicKey(String user, KeyPair pair)
+ throws IOException
+ {
+ if (pair == null)
+ throw new IllegalArgumentException("Key pair argument is null");
+
+ checkRequirements(user);
+
+ authenticated = am.authenticatePublicKey(user, pair, getOrCreateSecureRND());
+
+ return authenticated;
+ }
+ /**
+ * A convenience wrapper function which reads in a private key (PEM format,
+ * either DSA, EC, or RSA) and then calls
+ * authenticateWithPublicKey(String, char[], String).
+ *
+ * NOTE PUTTY USERS: Event though your key file may start with
+ * "-----BEGIN..." it is not in the expected format. You have to convert it
+ * to the OpenSSH key format by using the "puttygen" tool (can be downloaded
+ * from the Putty website). Simply load your key and then use the
+ * "Conversions/Export OpenSSH key" functionality to get a proper PEM file.
+ *
+ * @param user
+ * A String holding the username.
+ * @param pemFile
+ * A File object pointing to a file containing a
+ * DSA, EC, or RSA private key of the user in OpenSSH key format
+ * (PEM, you can't miss the "-----BEGIN DSA PRIVATE KEY-----",
+ * "-----BEGIN EC PRIVATE KEY-----", or
+ * "-----BEGIN RSA PRIVATE KEY-----" tag).
+ * @param password
+ * If the PEM file is encrypted then you must specify the
+ * password. Otherwise, this argument will be ignored and can be
+ * set to null.
+ *
+ * @return whether the connection is now authenticated.
+ * @throws IOException
+ */
+ public synchronized boolean authenticateWithPublicKey(String user, File pemFile, String password)
+ throws IOException
+ {
+ if (pemFile == null)
+ throw new IllegalArgumentException("pemFile argument is null");
+
+ char[] buff = new char[256];
+
+ CharArrayWriter cw = new CharArrayWriter();
+
+ FileReader fr = new FileReader(pemFile);
+
+ while (true)
+ {
+ int len = fr.read(buff);
+ if (len < 0)
+ break;
+ cw.write(buff, 0, len);
+ }
+
+ fr.close();
+
+ return authenticateWithPublicKey(user, cw.toCharArray(), password);
+ }
+
+ /**
+ * After a successful connect, one has to authenticate oneself. The
+ * authentication method "publickey" works by signing a challenge sent by
+ * the server. The signature is either DSA, EC, EdDSA or RSA based depending
+ * on the SignatureProxy specified. The SignatureProxy should sign the
+ * server's challenge in order to authenticate the user. The user of this library
+ * is no longer forced to pass the private key.
+ *
+ * If the authentication phase is complete, true will be
+ * returned. If the server does not accept the request (or if further
+ * authentication steps are needed), false is returned and
+ * one can retry either by using this or any other authentication method
+ * (use the getRemainingAuthMethods method to get a list of
+ * the remaining possible methods).
+ *
+ * @param user
+ * A String holding the username.
+ * @param signatureProxy
+ * A SignatureProxy containing a public key and implementing
+ * a sign method.
+ *
+ * @return Whether the connection is now authenticated.
+ * @throws IOException Might be thrown if the authentication process threw an error.
+ */
+ public synchronized boolean authenticateWithPublicKey(String user, SignatureProxy signatureProxy)
+ throws IOException
+ {
+ checkRequirements(user);
+
+ if (signatureProxy.getPublicKey() == null)
+ {
+ throw new IllegalArgumentException("Signature manager does not contain a public key.");
+ }
+
+ authenticated = am.authenticatePublicKey(user, signatureProxy);
+
+ return authenticated;
+ }
+
+ private void checkRequirements(String user)
+ {
+ if (tm == null)
+ {
+ throw new IllegalStateException("Connection is not established!");
+ }
+
+ if (authenticated)
+ {
+ throw new IllegalStateException("Connection is already authenticated!");
+ }
+
+ if (am == null)
+ {
+ am = new AuthenticationManager(tm);
+ }
+
+ if (cm == null)
+ {
+ cm = new ChannelManager(tm);
+ }
+
+ if (user == null)
+ {
+ throw new IllegalArgumentException("user argument is null");
+ }
+ }
+
+ /**
+ * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any
+ * time, but it is best to add connection monitors before invoking
+ * connect() to avoid glitches (e.g., you add a connection
+ * monitor after a successful connect(), but the connection has died in the
+ * mean time. Then, your connection monitor won't be notified.)
+ *
+ * You can add as many monitors as you like.
+ *
+ * @see ConnectionMonitor
+ *
+ * @param cmon
+ * An object implementing the ConnectionMonitor
+ * interface.
+ */
+ public synchronized void addConnectionMonitor(ConnectionMonitor cmon)
+ {
+ if (cmon == null)
+ throw new IllegalArgumentException("cmon argument is null");
+
+ connectionMonitors.addElement(cmon);
+
+ if (tm != null)
+ tm.setConnectionMonitors(connectionMonitors);
+ }
+
+ /**
+ * Controls whether compression is used on the link or not.
+ *
+ * Note: This can only be called before connect()
+ * @param enabled whether to enable compression
+ * @throws IOException
+ */
+ public synchronized void setCompression(boolean enabled) throws IOException {
+ if (tm != null)
+ throw new IOException("Connection to " + hostname + " is already in connected state!");
+
+ compression = enabled;
+ }
+
+ /**
+ * Close the connection to the SSH-2 server. All assigned sessions will be
+ * closed, too. Can be called at any time. Don't forget to call this once
+ * you don't need a connection anymore - otherwise the receiver thread may
+ * run forever.
+ */
+ public synchronized void close()
+ {
+ Throwable t = new Throwable("Closed due to user request.");
+ close(t, false);
+ }
+
+ private void close(Throwable t, boolean hard)
+ {
+ if (cm != null)
+ cm.closeAllChannels();
+
+ if (tm != null)
+ {
+ tm.close(t, !hard);
+ tm = null;
+ }
+ am = null;
+ cm = null;
+ authenticated = false;
+ }
+
+ /**
+ * Same as
+ * {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}.
+ *
+ * @return see comments for the
+ * {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)}
+ * method.
+ * @throws IOException
+ */
+ public synchronized ConnectionInfo connect() throws IOException
+ {
+ return connect(null, 0, 0);
+ }
+
+ /**
+ * Same as
+ * {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}.
+ *
+ * @return see comments for the
+ * {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)}
+ * method.
+ * @throws IOException
+ */
+ public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException
+ {
+ return connect(verifier, 0, 0);
+ }
+
+ /**
+ * Connect to the SSH-2 server and, as soon as the server has presented its
+ * host key, use the
+ * {@link ServerHostKeyVerifier#verifyServerHostKey(String, int, String,
+ * byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method of the
+ * verifier to ask for permission to proceed. If
+ * verifier is null, then any host key will
+ * be accepted - this is NOT recommended, since it makes man-in-the-middle
+ * attackes VERY easy (somebody could put a proxy SSH server between you and
+ * the real server).
+ *
+ * Note: The verifier will be called before doing any crypto calculations
+ * (i.e., diffie-hellman). Therefore, if you don't like the presented host
+ * key then no CPU cycles are wasted (and the evil server has less
+ * information about us).
+ *
+ * However, it is still possible that the server presented a fake host key:
+ * the server cheated (typically a sign for a man-in-the-middle attack) and
+ * is not able to generate a signature that matches its host key. Don't
+ * worry, the library will detect such a scenario later when checking the
+ * signature (the signature cannot be checked before having completed the
+ * diffie-hellman exchange).
+ *
+ * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String, int,
+ * String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method will
+ * *NOT* be called from the current thread, the call is being made from a
+ * background thread (there is a background dispatcher thread for every
+ * established connection).
+ *
+ * Note 3: This method will block as long as the key exchange of the
+ * underlying connection has not been completed (and you have not specified
+ * any timeouts).
+ *
+ * Note 4: If you want to re-use a connection object that was successfully
+ * connected, then you must call the {@link #close()} method before invoking
+ * connect() again.
+ *
+ * @param verifier
+ * An object that implements the {@link ServerHostKeyVerifier}
+ * interface. Pass null to accept any server host
+ * key - NOT recommended.
+ *
+ * @param connectTimeout
+ * Connect the underlying TCP socket to the server with the given
+ * timeout value (non-negative, in milliseconds). Zero means no
+ * timeout. If a proxy is being used (see
+ * {@link #setProxyData(ProxyData)}), then this timeout is used
+ * for the connection establishment to the proxy.
+ *
+ * @param kexTimeout
+ * Timeout for complete connection establishment (non-negative,
+ * in milliseconds). Zero means no timeout. The timeout counts
+ * from the moment you invoke the connect() method and is
+ * cancelled as soon as the first key-exchange round has
+ * finished. It is possible that the timeout event will be fired
+ * during the invocation of the verifier callback,
+ * but it will only have an effect after the
+ * verifier returns.
+ *
+ * @return A {@link ConnectionInfo} object containing the details of the
+ * established connection.
+ *
+ * @throws IOException
+ * If any problem occurs, e.g., the server's host key is not
+ * accepted by the verifier or there is problem
+ * during the initial crypto setup (e.g., the signature sent by
+ * the server is wrong).
+ *
+ * In case of a timeout (either connectTimeout or kexTimeout) a
+ * SocketTimeoutException is thrown.
+ *
+ * An exception may also be thrown if the connection was already
+ * successfully connected (no matter if the connection broke in
+ * the mean time) and you invoke connect() again
+ * without having called {@link #close()} first.
+ *
+ * If a HTTP proxy is being used and the proxy refuses the
+ * connection, then a {@link HTTPProxyException} may be thrown,
+ * which contains the details returned by the proxy. If the
+ * proxy is buggy and does not return a proper HTTP response,
+ * then a normal IOException is thrown instead.
+ */
+ public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout)
+ throws IOException
+ {
+ final class TimeoutState
+ {
+ boolean isCancelled = false;
+ boolean timeoutSocketClosed = false;
+ }
+
+ if (tm != null)
+ throw new IOException("Connection to " + hostname + " is already in connected state!");
+
+ if (connectTimeout < 0)
+ throw new IllegalArgumentException("connectTimeout must be non-negative!");
+
+ if (kexTimeout < 0)
+ throw new IllegalArgumentException("kexTimeout must be non-negative!");
+
+ final TimeoutState state = new TimeoutState();
+
+ tm = new TransportManager(hostname, port);
+
+ tm.setConnectionMonitors(connectionMonitors);
+
+ // Don't offer compression if not requested
+ if (!compression) {
+ cryptoWishList.c2s_comp_algos = new String[] { "none" };
+ cryptoWishList.s2c_comp_algos = new String[] { "none" };
+ }
+
+ /*
+ * Make sure that the runnable below will observe the new value of "tm"
+ * and "state" (the runnable will be executed in a different thread,
+ * which may be already running, that is why we need a memory barrier
+ * here). See also the comment in Channel.java if you are interested in
+ * the details.
+ *
+ * OKOK, this is paranoid since adding the runnable to the todo list of
+ * the TimeoutService will ensure that all writes have been flushed
+ * before the Runnable reads anything (there is a synchronized block in
+ * TimeoutService.addTimeoutHandler).
+ */
+
+ synchronized (tm)
+ {
+ /* We could actually synchronize on anything. */
+ }
+
+ try
+ {
+ TimeoutToken token = null;
+
+ if (kexTimeout > 0)
+ {
+ final Runnable timeoutHandler = new Runnable()
+ {
+ public void run()
+ {
+ synchronized (state)
+ {
+ if (state.isCancelled)
+ return;
+ state.timeoutSocketClosed = true;
+ tm.close(new SocketTimeoutException("The connect timeout expired"), false);
+ }
+ }
+ };
+
+ long timeoutHorizont = System.currentTimeMillis() + kexTimeout;
+
+ token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler);
+ }
+
+ try
+ {
+ tm.initialize(cryptoWishList, verifier, dhgexpara, connectTimeout, getOrCreateSecureRND(), proxyData);
+ }
+ catch (SocketTimeoutException se)
+ {
+ throw (SocketTimeoutException) new SocketTimeoutException(
+ "The connect() operation on the socket timed out.").initCause(se);
+ }
+
+ /* Wait until first KEX has finished */
+
+ ConnectionInfo ci = tm.getConnectionInfo(1);
+
+ /* Now try to cancel the timeout, if needed */
+
+ if (token != null)
+ {
+ TimeoutService.cancelTimeoutHandler(token);
+
+ /* Were we too late? */
+
+ synchronized (state)
+ {
+ if (state.timeoutSocketClosed)
+ throw new IOException("This exception will be replaced by the one below =)");
+ /*
+ * Just in case the "cancelTimeoutHandler" invocation came
+ * just a little bit too late but the handler did not enter
+ * the semaphore yet - we can still stop it.
+ */
+ state.isCancelled = true;
+ }
+ }
+
+ return ci;
+ }
+ catch (SocketTimeoutException ste)
+ {
+ throw ste;
+ }
+ catch (IOException e1)
+ {
+ /* This will also invoke any registered connection monitors */
+ close(new Throwable("There was a problem during connect."), false);
+
+ synchronized (state)
+ {
+ /*
+ * Show a clean exception, not something like "the socket is
+ * closed!?!"
+ */
+ if (state.timeoutSocketClosed)
+ throw new SocketTimeoutException("The kexTimeout (" + kexTimeout + " ms) expired.");
+ }
+
+ /* Do not wrap a HTTPProxyException */
+ if (e1 instanceof HTTPProxyException)
+ throw e1;
+
+ throw new IOException("There was a problem while connecting to " + hostname + ":" + port, e1);
+ }
+ }
+
+ /**
+ * Creates a new {@link LocalPortForwarder}. A
+ * LocalPortForwarder forwards TCP/IP connections that arrive
+ * at a local port via the secure tunnel to another host (which may or may
+ * not be identical to the remote SSH-2 server).
+ *
+ * This method must only be called after one has passed successfully the
+ * authentication step. There is no limit on the number of concurrent
+ * forwardings.
+ *
+ * @param local_port
+ * the local port the LocalPortForwarder shall bind to.
+ * @param host_to_connect
+ * target address (IP or hostname)
+ * @param port_to_connect
+ * target port
+ * @return A {@link LocalPortForwarder} object.
+ * @throws IOException
+ */
+ public synchronized LocalPortForwarder createLocalPortForwarder(int local_port, String host_to_connect,
+ int port_to_connect) throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
+
+ if (!authenticated)
+ throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
+
+ return new LocalPortForwarder(cm, local_port, host_to_connect, port_to_connect);
+ }
+
+ /**
+ * Creates a new {@link LocalPortForwarder}. A
+ * LocalPortForwarder forwards TCP/IP connections that arrive
+ * at a local port via the secure tunnel to another host (which may or may
+ * not be identical to the remote SSH-2 server).
+ *
+ * This method must only be called after one has passed successfully the
+ * authentication step. There is no limit on the number of concurrent
+ * forwardings.
+ *
+ * @param addr
+ * specifies the InetSocketAddress where the local socket shall
+ * be bound to.
+ * @param host_to_connect
+ * target address (IP or hostname)
+ * @param port_to_connect
+ * target port
+ * @return A {@link LocalPortForwarder} object.
+ * @throws IOException
+ */
+ public synchronized LocalPortForwarder createLocalPortForwarder(InetSocketAddress addr, String host_to_connect,
+ int port_to_connect) throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
+
+ if (!authenticated)
+ throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
+
+ return new LocalPortForwarder(cm, addr, host_to_connect, port_to_connect);
+ }
+
+ /**
+ * Creates a new {@link LocalStreamForwarder}. A
+ * LocalStreamForwarder manages an Input/Outputstream pair
+ * that is being forwarded via the secure tunnel into a TCP/IP connection to
+ * another host (which may or may not be identical to the remote SSH-2
+ * server).
+ *
+ * @param host_to_connect
+ * @param port_to_connect
+ * @return A {@link LocalStreamForwarder} object.
+ * @throws IOException
+ */
+ public synchronized LocalStreamForwarder createLocalStreamForwarder(String host_to_connect, int port_to_connect)
+ throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException("Cannot forward, you need to establish a connection first.");
+
+ if (!authenticated)
+ throw new IllegalStateException("Cannot forward, connection is not authenticated.");
+
+ return new LocalStreamForwarder(cm, host_to_connect, port_to_connect);
+ }
+
+ /**
+ * Creates a new {@link DynamicPortForwarder}. A
+ * DynamicPortForwarder forwards TCP/IP connections that arrive
+ * at a local port via the secure tunnel to another host that is chosen via
+ * the SOCKS protocol.
+ *
+ * This method must only be called after one has passed successfully the
+ * authentication step. There is no limit on the number of concurrent
+ * forwardings.
+ *
+ * @param local_port
+ * @return A {@link DynamicPortForwarder} object.
+ * @throws IOException
+ */
+ public synchronized DynamicPortForwarder createDynamicPortForwarder(int local_port) throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
+
+ if (!authenticated)
+ throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
+
+ return new DynamicPortForwarder(cm, local_port);
+ }
+
+ /**
+ * Creates a new {@link DynamicPortForwarder}. A
+ * DynamicPortForwarder forwards TCP/IP connections that arrive
+ * at a local port via the secure tunnel to another host that is chosen via
+ * the SOCKS protocol.
+ *
+ * This method must only be called after one has passed successfully the
+ * authentication step. There is no limit on the number of concurrent
+ * forwardings.
+ *
+ * @param addr
+ * specifies the InetSocketAddress where the local socket shall
+ * be bound to.
+ * @return A {@link DynamicPortForwarder} object.
+ * @throws IOException
+ */
+ public synchronized DynamicPortForwarder createDynamicPortForwarder(InetSocketAddress addr) throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");
+
+ if (!authenticated)
+ throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");
+
+ return new DynamicPortForwarder(cm, addr);
+ }
+
+ /**
+ * Create a very basic {@link SCPClient} that can be used to copy files
+ * from/to the SSH-2 server.
+ *
+ * Works only after one has passed successfully the authentication step.
+ * There is no limit on the number of concurrent SCP clients.
+ *
+ * Note: This factory method will probably disappear in the future.
+ *
+ * @return A {@link SCPClient} object.
+ */
+ public synchronized SCPClient createSCPClient() {
+ if (tm == null)
+ throw new IllegalStateException("Cannot create SCP client, you need to establish a connection first.");
+
+ if (!authenticated)
+ throw new IllegalStateException("Cannot create SCP client, connection is not authenticated.");
+
+ return new SCPClient(this);
+ }
+
+ /**
+ * Force an asynchronous key re-exchange (the call does not block). The
+ * latest values set for MAC, Cipher and DH group exchange parameters will
+ * be used. If a key exchange is currently in progress, then this method has
+ * the only effect that the so far specified parameters will be used for the
+ * next (server driven) key exchange.
+ *
+ * Note: This implementation will never start a key exchange (other than the
+ * initial one) unless you or the SSH-2 server ask for it.
+ *
+ * @throws IOException
+ * In case of any failure behind the scenes.
+ */
+ public synchronized void forceKeyExchange() throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException("You need to establish a connection first.");
+
+ tm.forceKeyExchange(cryptoWishList, dhgexpara);
+ }
+
+ /**
+ * Returns the hostname that was passed to the constructor.
+ *
+ * @return the hostname
+ */
+ public synchronized String getHostname()
+ {
+ return hostname;
+ }
+
+ /**
+ * Returns the port that was passed to the constructor.
+ *
+ * @return the TCP port
+ */
+ public synchronized int getPort()
+ {
+ return port;
+ }
+
+ /**
+ * Returns a {@link ConnectionInfo} object containing the details of the
+ * connection. Can be called as soon as the connection has been established
+ * (successfully connected).
+ *
+ * @return A {@link ConnectionInfo} object.
+ * @throws IOException
+ * In case of any failure behind the scenes.
+ */
+ public synchronized ConnectionInfo getConnectionInfo() throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException(
+ "Cannot get details of connection, you need to establish a connection first.");
+ return tm.getConnectionInfo(1);
+ }
+
+ /**
+ * After a successful connect, one has to authenticate oneself. This method
+ * can be used to tell which authentication methods are supported by the
+ * server at a certain stage of the authentication process (for the given
+ * username).
+ *
+ * Note 1: the username will only be used if no authentication step was done
+ * so far (it will be used to ask the server for a list of possible
+ * authentication methods by sending the initial "none" request). Otherwise,
+ * this method ignores the user name and returns a cached method list (which
+ * is based on the information contained in the last negative server
+ * response).
+ *
+ * Note 2: the server may return method names that are not supported by this
+ * implementation.
+ *
+ * After a successful authentication, this method must not be called
+ * anymore.
+ *
+ * @param user
+ * A String holding the username.
+ *
+ * @return a (possibly emtpy) array holding authentication method names.
+ * @throws IOException
+ */
+ public synchronized String[] getRemainingAuthMethods(String user) throws IOException
+ {
+ if (user == null)
+ throw new IllegalArgumentException("user argument may not be NULL!");
+
+ if (tm == null)
+ throw new IllegalStateException("Connection is not established!");
+
+ if (authenticated)
+ throw new IllegalStateException("Connection is already authenticated!");
+
+ if (am == null)
+ am = new AuthenticationManager(tm);
+
+ if (cm == null)
+ cm = new ChannelManager(tm);
+
+ return am.getRemainingMethods(user);
+ }
+
+ /**
+ * Determines if the authentication phase is complete. Can be called at any
+ * time.
+ *
+ * @return true if no further authentication steps are
+ * needed.
+ */
+ public synchronized boolean isAuthenticationComplete()
+ {
+ return authenticated;
+ }
+
+ /**
+ * Returns true if there was at least one failed authentication request and
+ * the last failed authentication request was marked with "partial success"
+ * by the server. This is only needed in the rare case of SSH-2 server
+ * setups that cannot be satisfied with a single successful authentication
+ * request (i.e., multiple authentication steps are needed.)
+ *
+ * If you are interested in the details, then have a look at RFC4252.
+ *
+ * @return if the there was a failed authentication step and the last one
+ * was marked as a "partial success".
+ */
+ public synchronized boolean isAuthenticationPartialSuccess()
+ {
+ if (am == null)
+ return false;
+
+ return am.getPartialSuccess();
+ }
+
+ /**
+ * Checks if a specified authentication method is available. This method is
+ * actually just a wrapper for {@link #getRemainingAuthMethods(String)
+ * getRemainingAuthMethods()}.
+ *
+ * @param user
+ * A String holding the username.
+ * @param method
+ * An authentication method name (e.g., "publickey", "password",
+ * "keyboard-interactive") as specified by the SSH-2 standard.
+ * @return if the specified authentication method is currently available.
+ * @throws IOException
+ */
+ public synchronized boolean isAuthMethodAvailable(String user, String method) throws IOException
+ {
+ if (method == null)
+ throw new IllegalArgumentException("method argument may not be NULL!");
+
+ String methods[] = getRemainingAuthMethods(user);
+
+ for (int i = 0; i < methods.length; i++)
+ {
+ if (methods[i].compareTo(method) == 0)
+ return true;
+ }
+
+ return false;
+ }
+
+ private final SecureRandom getOrCreateSecureRND()
+ {
+ if (generator == null)
+ generator = new SecureRandom();
+
+ return generator;
+ }
+
+ /**
+ * Open a new {@link Session} on this connection. Works only after one has
+ * passed successfully the authentication step. There is no limit on the
+ * number of concurrent sessions.
+ *
+ * @return A {@link Session} object.
+ * @throws IOException
+ */
+ public synchronized Session openSession() throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException("Cannot open session, you need to establish a connection first.");
+
+ if (!authenticated)
+ throw new IllegalStateException("Cannot open session, connection is not authenticated.");
+
+ return new Session(cm, getOrCreateSecureRND());
+ }
+
+ /**
+ * Send an SSH_MSG_IGNORE packet. This method will generate a random data
+ * attribute (length between 0 (invlusive) and 16 (exclusive) bytes,
+ * contents are random bytes).
+ *
+ * This method must only be called once the connection is established.
+ *
+ * @throws IOException
+ */
+ public synchronized void sendIgnorePacket() throws IOException
+ {
+ SecureRandom rnd = getOrCreateSecureRND();
+
+ byte[] data = new byte[rnd.nextInt(16)];
+ rnd.nextBytes(data);
+
+ sendIgnorePacket(data);
+ }
+
+ /**
+ * Send an SSH_MSG_IGNORE packet with the given data attribute.
+ *
+ * This method must only be called once the connection is established.
+ *
+ * @throws IOException
+ */
+ public synchronized void sendIgnorePacket(byte[] data) throws IOException
+ {
+ if (data == null)
+ throw new IllegalArgumentException("data argument must not be null.");
+
+ if (tm == null)
+ throw new IllegalStateException(
+ "Cannot send SSH_MSG_IGNORE packet, you need to establish a connection first.");
+
+ PacketIgnore pi = new PacketIgnore();
+ pi.setData(data);
+
+ tm.sendMessage(pi.getPayload());
+ }
+
+ /**
+ * Removes duplicates from a String array, keeps only first occurence of
+ * each element. Does not destroy order of elements; can handle nulls. Uses
+ * a very efficient O(N^2) algorithm =)
+ *
+ * @param list
+ * a String array.
+ * @return a cleaned String array.
+ */
+ private String[] removeDuplicates(String[] list)
+ {
+ if ((list == null) || (list.length < 2))
+ return list;
+
+ String[] list2 = new String[list.length];
+
+ int count = 0;
+
+ for (int i = 0; i < list.length; i++)
+ {
+ boolean duplicate = false;
+
+ String element = list[i];
+
+ for (int j = 0; j < count; j++)
+ {
+ if (element == null ? list2[j] == null : element.equals(list2[j]))
+ {
+ duplicate = true;
+ break;
+ }
+ }
+
+ if (duplicate)
+ continue;
+
+ list2[count++] = list[i];
+ }
+
+ if (count == list2.length)
+ return list2;
+
+ String[] tmp = new String[count];
+ System.arraycopy(list2, 0, tmp, 0, count);
+
+ return tmp;
+ }
+
+ /**
+ * Unless you know what you are doing, you will never need this.
+ *
+ * @param ciphers
+ */
+ public synchronized void setClient2ServerCiphers(String[] ciphers)
+ {
+ if ((ciphers == null) || (ciphers.length == 0))
+ throw new IllegalArgumentException();
+ ciphers = removeDuplicates(ciphers);
+ BlockCipherFactory.checkCipherList(ciphers);
+ cryptoWishList.c2s_enc_algos = ciphers;
+ }
+
+ /**
+ * Unless you know what you are doing, you will never need this.
+ *
+ * @param macs
+ */
+ public synchronized void setClient2ServerMACs(String[] macs)
+ {
+ if ((macs == null) || (macs.length == 0))
+ throw new IllegalArgumentException();
+ macs = removeDuplicates(macs);
+ MACs.checkMacList(macs);
+ cryptoWishList.c2s_mac_algos = macs;
+ }
+
+ /**
+ * Sets the parameters for the diffie-hellman group exchange. Unless you
+ * know what you are doing, you will never need this. Default values are
+ * defined in the {@link DHGexParameters} class.
+ *
+ * @param dgp
+ * {@link DHGexParameters}, non null.
+ *
+ */
+ public synchronized void setDHGexParameters(DHGexParameters dgp)
+ {
+ if (dgp == null)
+ throw new IllegalArgumentException();
+
+ dhgexpara = dgp;
+ }
+
+ /**
+ * Unless you know what you are doing, you will never need this.
+ *
+ * @param ciphers
+ */
+ public synchronized void setServer2ClientCiphers(String[] ciphers)
+ {
+ if ((ciphers == null) || (ciphers.length == 0))
+ throw new IllegalArgumentException();
+ ciphers = removeDuplicates(ciphers);
+ BlockCipherFactory.checkCipherList(ciphers);
+ cryptoWishList.s2c_enc_algos = ciphers;
+ }
+
+ /**
+ * Unless you know what you are doing, you will never need this.
+ *
+ * @param macs
+ */
+ public synchronized void setServer2ClientMACs(String[] macs)
+ {
+ if ((macs == null) || (macs.length == 0))
+ throw new IllegalArgumentException();
+
+ macs = removeDuplicates(macs);
+ MACs.checkMacList(macs);
+ cryptoWishList.s2c_mac_algos = macs;
+ }
+
+ /**
+ * Define the set of allowed server host key algorithms to be used for the
+ * following key exchange operations.
+ *
+ * Unless you know what you are doing, you will never need this.
+ *
+ * @param algos
+ * An array of allowed server host key algorithms. SSH-2 defines
+ * ssh-dss and ssh-rsa. The
+ * entries of the array must be ordered after preference, i.e.,
+ * the entry at index 0 is the most preferred one. You must
+ * specify at least one entry.
+ */
+ public synchronized void setServerHostKeyAlgorithms(String[] algos)
+ {
+ if ((algos == null) || (algos.length == 0))
+ throw new IllegalArgumentException();
+
+ algos = removeDuplicates(algos);
+ KexManager.checkServerHostkeyAlgorithmsList(algos);
+ cryptoWishList.serverHostKeyAlgorithms = algos;
+ }
+
+ /**
+ * Define the set of allowed Key Exchange algorithms to be used for the
+ * following key exchange operations.
+ *
+ * Unless you know what you are doing, you will never need this.
+ *
+ * @param algos
+ * An array of allowed key exchange algorithms.
+ */
+ public synchronized void setKeyExchangeAlgorithms(String[] algos)
+ {
+ if (algos == null || algos.length == 0)
+ throw new IllegalArgumentException();
+
+ algos = removeDuplicates(algos);
+ KexManager.checkKexAlgorithmList(algos);
+ cryptoWishList.kexAlgorithms = algos;
+ }
+
+ /**
+ * Used to tell the library that the connection shall be established through
+ * a proxy server. It only makes sense to call this method before calling
+ * the {@link #connect() connect()} method.
+ *
+ * At the moment, only HTTP proxies are supported.
+ *
+ * Note: This method can be called any number of times. The
+ * {@link #connect() connect()} method will use the value set in the last
+ * preceding invocation of this method.
+ *
+ * @see HTTPProxyData
+ *
+ * @param proxyData
+ * Connection information about the proxy. If null,
+ * then no proxy will be used (non surprisingly, this is also the
+ * default).
+ */
+ public synchronized void setProxyData(ProxyData proxyData)
+ {
+ this.proxyData = proxyData;
+ }
+
+ /**
+ * Request a remote port forwarding. If successful, then forwarded
+ * connections will be redirected to the given target address. You can
+ * cancle a requested remote port forwarding by calling
+ * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}.
+ *
+ * A call of this method will block until the peer either agreed or
+ * disagreed to your request-
+ *
+ * Note 1: this method typically fails if you
+ *
+ *
pass a port number for which the used remote user has not enough
+ * permissions (i.e., port < 1024)
+ *
or pass a port number that is already in use on the remote server
+ *
or if remote port forwarding is disabled on the server.
+ *
+ *
+ * Note 2: (from the openssh man page): By default, the listening socket on
+ * the server will be bound to the loopback interface only. This may be
+ * overriden by specifying a bind address. Specifying a remote bind address
+ * will only succeed if the server's GatewayPorts option is enabled
+ * (see sshd_config(5)).
+ *
+ * @param bindAddress
+ * address to bind to on the server:
+ *
+ *
"" means that connections are to be accepted on all
+ * protocol families supported by the SSH implementation
+ *
"0.0.0.0" means to listen on all IPv4 addresses
+ *
"::" means to listen on all IPv6 addresses
+ *
"localhost" means to listen on all protocol families
+ * supported by the SSH implementation on loopback addresses
+ * only, [RFC3330] and RFC3513]
+ *
"127.0.0.1" and "::1" indicate listening on the loopback
+ * interfaces for IPv4 and IPv6 respectively
+ *
+ * @param bindPort
+ * port number to bind on the server (must be > 0)
+ * @param targetAddress
+ * the target address (IP or hostname)
+ * @param targetPort
+ * the target port
+ * @throws IOException
+ */
+ public synchronized void requestRemotePortForwarding(String bindAddress, int bindPort, String targetAddress,
+ int targetPort) throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException("You need to establish a connection first.");
+
+ if (!authenticated)
+ throw new IllegalStateException("The connection is not authenticated.");
+
+ if ((bindAddress == null) || (targetAddress == null) || (bindPort <= 0) || (targetPort <= 0))
+ throw new IllegalArgumentException();
+
+ cm.requestGlobalForward(bindAddress, bindPort, targetAddress, targetPort);
+ }
+
+ /**
+ * Cancel an earlier requested remote port forwarding. Currently active
+ * forwardings will not be affected (e.g., disrupted). Note that further
+ * connection forwarding requests may be received until this method has
+ * returned.
+ *
+ * @param bindPort
+ * the allocated port number on the server
+ * @throws IOException
+ * if the remote side refuses the cancel request or another low
+ * level error occurs (e.g., the underlying connection is
+ * closed)
+ */
+ public synchronized void cancelRemotePortForwarding(int bindPort) throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException("You need to establish a connection first.");
+
+ if (!authenticated)
+ throw new IllegalStateException("The connection is not authenticated.");
+
+ cm.requestCancelGlobalForward(bindPort);
+ }
+
+ /**
+ * Provide your own instance of SecureRandom. Can be used, e.g., if you want
+ * to seed the used SecureRandom generator manually.
+ *
+ * The SecureRandom instance is used during key exchanges, public key
+ * authentication, x11 cookie generation and the like.
+ *
+ * @param rnd
+ * a SecureRandom instance
+ */
+ public synchronized void setSecureRandom(SecureRandom rnd)
+ {
+ if (rnd == null)
+ throw new IllegalArgumentException();
+
+ this.generator = rnd;
+ }
+
+ /**
+ * Enable/disable debug logging. Only do this when requested by Trilead
+ * support.
+ *
+ * For speed reasons, some static variables used to check whether debugging
+ * is enabled are not protected with locks. In other words, if you
+ * dynamicaly enable/disable debug logging, then some threads may still use
+ * the old setting. To be on the safe side, enable debugging before doing
+ * the connect() call.
+ *
+ * @param enable
+ * on/off
+ * @param logger
+ * a {@link DebugLogger DebugLogger} instance, null
+ * means logging using the simple logger which logs all messages
+ * to to stderr. Ignored if enabled is false
+ */
+ public synchronized void enableDebugging(boolean enable, DebugLogger logger)
+ {
+ Logger.enabled = enable;
+
+ if (!enable)
+ {
+ Logger.logger = null;
+ }
+ else
+ {
+ if (logger == null)
+ {
+ logger = new DebugLogger()
+ {
+
+ public void log(int level, String className, String message)
+ {
+ long now = System.currentTimeMillis();
+ System.err.println(now + " : " + className + ": " + message);
+ }
+ };
+ }
+
+ Logger.logger = logger;
+ }
+ }
+
+ /**
+ * This method can be used to perform end-to-end connection testing. It
+ * sends a 'ping' message to the server and waits for the 'pong' from the
+ * server.
+ *
+ * When this method throws an exception, then you can assume that the
+ * connection should be abandoned.
+ *
+ * Note: Works only after one has passed successfully the authentication
+ * step.
+ *
+ * Implementation details: this method sends a SSH_MSG_GLOBAL_REQUEST
+ * request ('trilead-ping') to the server and waits for the
+ * SSH_MSG_REQUEST_FAILURE reply packet from the server.
+ *
+ * @throws IOException
+ * in case of any problem
+ */
+ public synchronized void ping() throws IOException
+ {
+ if (tm == null)
+ throw new IllegalStateException("You need to establish a connection first.");
+
+ if (!authenticated)
+ throw new IllegalStateException("The connection is not authenticated.");
+
+ cm.requestGlobalTrileadPing();
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ConnectionInfo.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ConnectionInfo.java
new file mode 100644
index 0000000000..d508acf514
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ConnectionInfo.java
@@ -0,0 +1,65 @@
+
+package com.trilead.ssh2;
+
+/**
+ * In most cases you probably do not need the information contained in here.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: ConnectionInfo.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class ConnectionInfo
+{
+ /**
+ * The used key exchange (KEX) algorithm in the latest key exchange.
+ */
+ public String keyExchangeAlgorithm;
+
+ /**
+ * The currently used crypto algorithm for packets from to the client to the
+ * server.
+ */
+ public String clientToServerCryptoAlgorithm;
+ /**
+ * The currently used crypto algorithm for packets from to the server to the
+ * client.
+ */
+ public String serverToClientCryptoAlgorithm;
+
+ /**
+ * The currently used MAC algorithm for packets from to the client to the
+ * server.
+ */
+ public String clientToServerMACAlgorithm;
+ /**
+ * The currently used MAC algorithm for packets from to the server to the
+ * client.
+ */
+ public String serverToClientMACAlgorithm;
+
+ /**
+ * The type of the server host key (currently either "ssh-dss" or
+ * "ssh-rsa").
+ */
+ public String serverHostKeyAlgorithm;
+ /**
+ * The server host key that was sent during the latest key exchange.
+ */
+ public byte[] serverHostKey;
+
+ /**
+ * Number of kex exchanges performed on this connection so far.
+ */
+ public int keyExchangeCounter = 0;
+
+ /**
+ * The currently used compression algorithm for packets from the client to
+ * the server.
+ */
+ public String clientToServerCompressionAlgorithm;
+
+ /**
+ * The currently used compression algorithm for packets from the server to
+ * the client.
+ */
+ public String serverToClientCompressionAlgorithm;
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ConnectionMonitor.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ConnectionMonitor.java
new file mode 100644
index 0000000000..59a9766d25
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ConnectionMonitor.java
@@ -0,0 +1,34 @@
+
+package com.trilead.ssh2;
+
+/**
+ * A ConnectionMonitor is used to get notified when the
+ * underlying socket of a connection is closed.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: ConnectionMonitor.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public interface ConnectionMonitor
+{
+ /**
+ * This method is called after the connection's underlying
+ * socket has been closed. E.g., due to the {@link Connection#close()} request of the
+ * user, if the peer closed the connection, due to a fatal error during connect()
+ * (also if the socket cannot be established) or if a fatal error occured on
+ * an established connection.
+ *
+ * This is an experimental feature.
+ *
+ * You MUST NOT make any assumption about the thread that invokes this method.
+ *
+ * Please note: if the connection is not connected (e.g., there was no successful
+ * connect() call), then the invocation of {@link Connection#close()} will NOT trigger
+ * this method.
+ *
+ * @see Connection#addConnectionMonitor(ConnectionMonitor)
+ *
+ * @param reason Includes an indication why the socket was closed.
+ */
+ void connectionLost(Throwable reason);
+}
\ No newline at end of file
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/DHGexParameters.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/DHGexParameters.java
new file mode 100644
index 0000000000..e779600e20
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/DHGexParameters.java
@@ -0,0 +1,121 @@
+
+package com.trilead.ssh2;
+
+/**
+ * A DHGexParameters object can be used to specify parameters for
+ * the diffie-hellman group exchange.
+ *
+ * Depending on which constructor is used, either the use of a
+ * SSH_MSG_KEX_DH_GEX_REQUEST or SSH_MSG_KEX_DH_GEX_REQUEST_OLD
+ * can be forced.
+ *
+ * @see Connection#setDHGexParameters(DHGexParameters)
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: DHGexParameters.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public class DHGexParameters
+{
+ private final int min_group_len;
+ private final int pref_group_len;
+ private final int max_group_len;
+
+ private static final int MIN_ALLOWED = 1024;
+ private static final int MAX_ALLOWED = 8192;
+
+ /**
+ * Same as calling {@link #DHGexParameters(int, int, int) DHGexParameters(1024, 1024, 4096)}.
+ * This is also the default used by the Connection class.
+ *
+ */
+ public DHGexParameters()
+ {
+ this(1024, 1024, 4096);
+ }
+
+ /**
+ * This constructor can be used to force the sending of a
+ * SSH_MSG_KEX_DH_GEX_REQUEST_OLD request.
+ * Internally, the minimum and maximum group lengths will
+ * be set to zero.
+ *
+ * @param pref_group_len has to be >= 1024 and <= 8192
+ */
+ public DHGexParameters(int pref_group_len)
+ {
+ if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED))
+ throw new IllegalArgumentException("pref_group_len out of range!");
+
+ this.pref_group_len = pref_group_len;
+ this.min_group_len = 0;
+ this.max_group_len = 0;
+ }
+
+ /**
+ * This constructor can be used to force the sending of a
+ * SSH_MSG_KEX_DH_GEX_REQUEST request.
+ *
+ * Note: older OpenSSH servers don't understand this request, in which
+ * case you should use the {@link #DHGexParameters(int)} constructor.
+ *
+ * All values have to be >= 1024 and <= 8192. Furthermore,
+ * min_group_len <= pref_group_len <= max_group_len.
+ *
+ * @param min_group_len
+ * @param pref_group_len
+ * @param max_group_len
+ */
+ public DHGexParameters(int min_group_len, int pref_group_len, int max_group_len)
+ {
+ if ((min_group_len < MIN_ALLOWED) || (min_group_len > MAX_ALLOWED))
+ throw new IllegalArgumentException("min_group_len out of range!");
+
+ if ((pref_group_len < MIN_ALLOWED) || (pref_group_len > MAX_ALLOWED))
+ throw new IllegalArgumentException("pref_group_len out of range!");
+
+ if ((max_group_len < MIN_ALLOWED) || (max_group_len > MAX_ALLOWED))
+ throw new IllegalArgumentException("max_group_len out of range!");
+
+ if ((pref_group_len < min_group_len) || (pref_group_len > max_group_len))
+ throw new IllegalArgumentException("pref_group_len is incompatible with min and max!");
+
+ if (max_group_len < min_group_len)
+ throw new IllegalArgumentException("max_group_len must not be smaller than min_group_len!");
+
+ this.min_group_len = min_group_len;
+ this.pref_group_len = pref_group_len;
+ this.max_group_len = max_group_len;
+ }
+
+ /**
+ * Get the maximum group length.
+ *
+ * @return the maximum group length, may be zero if
+ * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested
+ */
+ public int getMax_group_len()
+ {
+ return max_group_len;
+ }
+
+ /**
+ * Get the minimum group length.
+ *
+ * @return minimum group length, may be zero if
+ * SSH_MSG_KEX_DH_GEX_REQUEST_OLD should be requested
+ */
+ public int getMin_group_len()
+ {
+ return min_group_len;
+ }
+
+ /**
+ * Get the preferred group length.
+ *
+ * @return the preferred group length
+ */
+ public int getPref_group_len()
+ {
+ return pref_group_len;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/DebugLogger.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/DebugLogger.java
new file mode 100644
index 0000000000..f1ede720d0
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/DebugLogger.java
@@ -0,0 +1,23 @@
+package com.trilead.ssh2;
+
+/**
+ * An interface which needs to be implemented if you
+ * want to capture debugging messages.
+ *
+ * @see Connection#enableDebugging(boolean, DebugLogger)
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: DebugLogger.java,v 1.1 2008/03/03 07:01:36 cplattne Exp $
+ */
+public interface DebugLogger
+{
+
+/**
+ * Log a debug message.
+ *
+ * @param level 0-99, 99 is a the most verbose level
+ * @param className the class that generated the message
+ * @param message the debug message
+ */
+void log(int level, String className, String message);
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/DynamicPortForwarder.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/DynamicPortForwarder.java
new file mode 100644
index 0000000000..db3a0682c9
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/DynamicPortForwarder.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * a.) Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * b.) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * c.) Neither the name of Trilead nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.trilead.ssh2;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import com.trilead.ssh2.channel.ChannelManager;
+import com.trilead.ssh2.channel.DynamicAcceptThread;
+
+/**
+ * A DynamicPortForwarder forwards TCP/IP connections to a local
+ * port via the secure tunnel to another host which is selected via the
+ * SOCKS protocol. Checkout {@link Connection#createDynamicPortForwarder(int)}
+ * on how to create one.
+ *
+ * @author Kenny Root
+ * @version $Id: $
+ */
+public class DynamicPortForwarder {
+ ChannelManager cm;
+
+ DynamicAcceptThread dat;
+
+ DynamicPortForwarder(ChannelManager cm, int local_port)
+ throws IOException
+ {
+ this.cm = cm;
+
+ dat = new DynamicAcceptThread(cm, local_port);
+ dat.setDaemon(true);
+ dat.start();
+ }
+
+ DynamicPortForwarder(ChannelManager cm, InetSocketAddress addr) throws IOException {
+ this.cm = cm;
+
+ dat = new DynamicAcceptThread(cm, addr);
+ dat.setDaemon(true);
+ dat.start();
+ }
+
+ /**
+ * Stop TCP/IP forwarding of newly arriving connections.
+ *
+ */
+ public void close() {
+ dat.stopWorking();
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ExtendedServerHostKeyVerifier.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ExtendedServerHostKeyVerifier.java
new file mode 100644
index 0000000000..f757aa6b97
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ExtendedServerHostKeyVerifier.java
@@ -0,0 +1,47 @@
+package com.trilead.ssh2;
+
+import java.util.List;
+
+/**
+ * This extends the {@link ServerHostKeyVerifier} interface by allowing the remote server to indicate it has multiple
+ * server key algorithms available. After authentication, the {@link #getKnownKeyAlgorithmsForHost(String, int)} method
+ * may be called and compared against the list of server-controller keys. If a key algorithm has been added then
+ * {@link #addServerHostKey(String, int, String, byte[])} will be called. If a key algorithm has been removed, then
+ * {@link #removeServerHostKey(String, int, String, byte[])} will be called.
+ *
+ * @author Kenny Root
+ */
+public abstract class ExtendedServerHostKeyVerifier implements ServerHostKeyVerifier {
+ /**
+ * Called during connection to determine which keys are known for this host.
+ *
+ * @param hostname the hostname used to create the {@link Connection} object
+ * @param port the server's remote TCP port
+ * @return list of hostkey algorithms for the given hostname and port combination
+ * or {@code null} if none are known.
+ */
+ public abstract List getKnownKeyAlgorithmsForHost(String hostname, int port);
+
+ /**
+ * After authentication, if the server indicates it no longer uses this key, this method will be called
+ * for the app to remove its record of it.
+ *
+ * @param hostname the hostname used to create the {@link Connection} object
+ * @param port the server's remote TCP port
+ * @param serverHostKeyAlgorithm key algorithm of removed key
+ * @param serverHostKey key data of removed key
+ */
+ public abstract void removeServerHostKey(String hostname, int port, String serverHostKeyAlgorithm,
+ byte[] serverHostKey);
+
+ /**
+ * After authentication, if the server indicates it has another keyAlgorithm, this method will be
+ * called for the app to add it to its record of known keys for this hostname.
+ *
+ * @param hostname the hostname used to create the {@link Connection} object
+ * @param port the server's remote TCP port
+ * @param keyAlgorithm SSH standard name for the key to be added
+ * @param serverHostKey SSH encoding of the key data for the key to be added
+ */
+ public abstract void addServerHostKey(String hostname, int port, String keyAlgorithm, byte[] serverHostKey);
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ExtensionInfo.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ExtensionInfo.java
new file mode 100644
index 0000000000..58e602765d
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ExtensionInfo.java
@@ -0,0 +1,47 @@
+package com.trilead.ssh2;
+
+import com.trilead.ssh2.packets.PacketExtInfo;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * SSH extensions reported by the server
+ *
+ * https://tools.ietf.org/html/draft-ietf-curdle-ssh-ext-info-15
+ */
+public class ExtensionInfo
+{
+ private final Set signatureAlgorithmsAccepted;
+
+ /**
+ * @return Signature algorithms that server will accept. If empty, this extension was absent.
+ */
+ public Set getSignatureAlgorithmsAccepted()
+ {
+ return signatureAlgorithmsAccepted;
+ }
+
+ public static ExtensionInfo fromPacketExtInfo(PacketExtInfo packetExtInfo)
+ {
+ String rawAlgs = packetExtInfo.getExtNameToValue().get("server-sig-algs");
+ if (rawAlgs == null)
+ {
+ return new ExtensionInfo(Collections.emptySet());
+ }
+
+ Set algsSet = new HashSet<>();
+ Collections.addAll(algsSet, rawAlgs.split(","));
+ return new ExtensionInfo(algsSet);
+ }
+
+ public static ExtensionInfo noExtInfoSeen()
+ {
+ return new ExtensionInfo(Collections.emptySet());
+ }
+
+ private ExtensionInfo(Set signatureAlgorithmsAccepted)
+ {
+ this.signatureAlgorithmsAccepted = Collections.unmodifiableSet(signatureAlgorithmsAccepted);
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/HTTPProxyData.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/HTTPProxyData.java
new file mode 100644
index 0000000000..3be55a4160
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/HTTPProxyData.java
@@ -0,0 +1,202 @@
+
+package com.trilead.ssh2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+import com.trilead.ssh2.crypto.Base64;
+import com.trilead.ssh2.transport.ClientServerHello;
+
+/**
+ * A HTTPProxyData object is used to specify the needed connection data
+ * to connect through a HTTP proxy.
+ *
+ * @see Connection#setProxyData(ProxyData)
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: HTTPProxyData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public class HTTPProxyData implements ProxyData
+{
+ private final String proxyHost;
+ private final int proxyPort;
+ private final String proxyUser;
+ private final String proxyPass;
+ private final String[] requestHeaderLines;
+
+ /**
+ * Same as calling {@link #HTTPProxyData(String, int, String, String) HTTPProxyData(proxyHost, proxyPort, null, null)}
+ *
+ * @param proxyHost Proxy hostname.
+ * @param proxyPort Proxy port.
+ */
+ public HTTPProxyData(String proxyHost, int proxyPort)
+ {
+ this(proxyHost, proxyPort, null, null);
+ }
+
+ /**
+ * Same as calling {@link #HTTPProxyData(String, int, String, String, String[]) HTTPProxyData(proxyHost, proxyPort, null, null, null)}
+ *
+ * @param proxyHost Proxy hostname.
+ * @param proxyPort Proxy port.
+ * @param proxyUser Username for basic authentication (null if no authentication is needed).
+ * @param proxyPass Password for basic authentication (null if no authentication is needed).
+ */
+ public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass)
+ {
+ this(proxyHost, proxyPort, proxyUser, proxyPass, null);
+ }
+
+ /**
+ * Connection data for a HTTP proxy. It is possible to specify a username and password
+ * if the proxy requires basic authentication. Also, additional request header lines can
+ * be specified (e.g., "User-Agent: CERN-LineMode/2.15 libwww/2.17b3").
+ *
+ * Please note: if you want to use basic authentication, then both proxyUser
+ * and proxyPass must be non-null.
+ *
+ * Here is an example:
+ *
+ *
+ * new HTTPProxyData("192.168.1.1", "3128", "proxyuser", "secret", new String[] {"User-Agent: TrileadBasedClient/1.0", "X-My-Proxy-Option: something"});
+ *
+ *
+ * @param proxyHost Proxy hostname.
+ * @param proxyPort Proxy port.
+ * @param proxyUser Username for basic authentication (null if no authentication is needed).
+ * @param proxyPass Password for basic authentication (null if no authentication is needed).
+ * @param requestHeaderLines An array with additional request header lines (without end-of-line markers)
+ * that have to be sent to the server. May be null.
+ */
+
+ public HTTPProxyData(String proxyHost, int proxyPort, String proxyUser, String proxyPass,
+ String[] requestHeaderLines)
+ {
+ if (proxyHost == null)
+ throw new IllegalArgumentException("proxyHost must be non-null");
+
+ if (proxyPort < 0)
+ throw new IllegalArgumentException("proxyPort must be non-negative");
+
+ this.proxyHost = proxyHost;
+ this.proxyPort = proxyPort;
+ this.proxyUser = proxyUser;
+ this.proxyPass = proxyPass;
+ this.requestHeaderLines = requestHeaderLines;
+ }
+
+ @Override
+ public Socket openConnection(String hostname, int port, int connectTimeout) throws IOException {
+ Socket sock = new Socket();
+
+ InetAddress addr = InetAddress.getByName(proxyHost);
+ sock.connect(new InetSocketAddress(addr, proxyPort), connectTimeout);
+ sock.setSoTimeout(0);
+
+ /* OK, now tell the proxy where we actually want to connect to */
+
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("CONNECT ");
+ sb.append(hostname);
+ sb.append(':');
+ sb.append(port);
+ sb.append(" HTTP/1.0\r\n");
+
+ if ((proxyUser != null) && (proxyPass != null))
+ {
+ String credentials = proxyUser + ":" + proxyPass;
+ char[] encoded;
+ try {
+ encoded = Base64.encode(credentials.getBytes("ISO-8859-1"));
+ } catch (UnsupportedEncodingException e) {
+ encoded = Base64.encode(credentials.getBytes());
+ }
+ sb.append("Proxy-Authorization: Basic ");
+ sb.append(encoded);
+ sb.append("\r\n");
+ }
+
+ if (requestHeaderLines != null)
+ {
+ for (int i = 0; i < requestHeaderLines.length; i++)
+ {
+ if (requestHeaderLines[i] != null)
+ {
+ sb.append(requestHeaderLines[i]);
+ sb.append("\r\n");
+ }
+ }
+ }
+
+ sb.append("\r\n");
+
+ OutputStream out = sock.getOutputStream();
+
+ try {
+ out.write(sb.toString().getBytes("ISO-8859-1"));
+ } catch (UnsupportedEncodingException e) {
+ out.write(sb.toString().getBytes());
+ }
+ out.flush();
+
+ /* Now parse the HTTP response */
+
+ byte[] buffer = new byte[1024];
+ InputStream in = sock.getInputStream();
+
+ int len = ClientServerHello.readLineRN(in, buffer);
+
+ String httpReponse;
+ try {
+ httpReponse = new String(buffer, 0, len, "ISO-8859-1");
+ } catch (UnsupportedEncodingException e) {
+ httpReponse = new String(buffer, 0, len);
+ }
+
+ if (!httpReponse.startsWith("HTTP/"))
+ throw new IOException("The proxy did not send back a valid HTTP response.");
+
+ /* "HTTP/1.X XYZ X" => 14 characters minimum */
+
+ if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' '))
+ throw new IOException("The proxy did not send back a valid HTTP response.");
+
+ int errorCode = 0;
+
+ try
+ {
+ errorCode = Integer.parseInt(httpReponse.substring(9, 12));
+ }
+ catch (NumberFormatException ignore)
+ {
+ throw new IOException("The proxy did not send back a valid HTTP response.");
+ }
+
+ if ((errorCode < 0) || (errorCode > 999))
+ throw new IOException("The proxy did not send back a valid HTTP response.");
+
+ if (errorCode != 200)
+ {
+ throw new HTTPProxyException(httpReponse.substring(13), errorCode);
+ }
+
+ /* OK, read until empty line */
+
+ while (true)
+ {
+ len = ClientServerHello.readLineRN(in, buffer);
+ if (len == 0)
+ break;
+ }
+
+ return sock;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/HTTPProxyException.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/HTTPProxyException.java
new file mode 100644
index 0000000000..2d2d019b52
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/HTTPProxyException.java
@@ -0,0 +1,29 @@
+
+package com.trilead.ssh2;
+
+import java.io.IOException;
+
+/**
+ * May be thrown upon connect() if a HTTP proxy is being used.
+ *
+ * @see Connection#connect()
+ * @see Connection#setProxyData(ProxyData)
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: HTTPProxyException.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public class HTTPProxyException extends IOException
+{
+ private static final long serialVersionUID = 2241537397104426186L;
+
+ public final String httpResponse;
+ public final int httpErrorCode;
+
+ public HTTPProxyException(String httpResponse, int httpErrorCode)
+ {
+ super("HTTP Proxy Error (" + httpErrorCode + " " + httpResponse + ")");
+ this.httpResponse = httpResponse;
+ this.httpErrorCode = httpErrorCode;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/InteractiveCallback.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/InteractiveCallback.java
new file mode 100644
index 0000000000..06186efc7b
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/InteractiveCallback.java
@@ -0,0 +1,55 @@
+
+package com.trilead.ssh2;
+
+/**
+ * An InteractiveCallback is used to respond to challenges sent
+ * by the server if authentication mode "keyboard-interactive" is selected.
+ *
+ * @see Connection#authenticateWithKeyboardInteractive(String,
+ * String[], InteractiveCallback)
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: InteractiveCallback.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public interface InteractiveCallback
+{
+ /**
+ * This callback interface is used during a "keyboard-interactive"
+ * authentication. Every time the server sends a set of challenges (however,
+ * most often just one challenge at a time), this callback function will be
+ * called to give your application a chance to talk to the user and to
+ * determine the response(s).
+ *
+ * Some copy-paste information from the standard: a command line interface
+ * (CLI) client SHOULD print the name and instruction (if non-empty), adding
+ * newlines. Then for each prompt in turn, the client SHOULD display the
+ * prompt and read the user input. The name and instruction fields MAY be
+ * empty strings, the client MUST be prepared to handle this correctly. The
+ * prompt field(s) MUST NOT be empty strings.
+ *
+ * Please refer to draft-ietf-secsh-auth-kbdinteract-XX.txt for the details.
+ *
+ * Note: clients SHOULD use control character filtering as discussed in
+ * RFC4251 to avoid attacks by including
+ * terminal control characters in the fields to be displayed.
+ *
+ * @param name
+ * the name String sent by the server.
+ * @param instruction
+ * the instruction String sent by the server.
+ * @param numPrompts
+ * number of prompts - may be zero (in this case, you should just
+ * return a String array of length zero).
+ * @param prompt
+ * an array (length numPrompts) of Strings
+ * @param echo
+ * an array (length numPrompts) of booleans. For
+ * each prompt, the corresponding echo field indicates whether or
+ * not the user input should be echoed as characters are typed.
+ * @return an array of reponses - the array size must match the parameter
+ * numPrompts.
+ */
+ String[] replyToChallenge(String name, String instruction, int numPrompts, String[] prompt, boolean[] echo)
+ throws Exception;
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/KnownHosts.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/KnownHosts.java
new file mode 100644
index 0000000000..030ad70371
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/KnownHosts.java
@@ -0,0 +1,894 @@
+
+package com.trilead.ssh2;
+
+import java.io.BufferedReader;
+import java.io.CharArrayReader;
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import com.trilead.ssh2.crypto.Base64;
+import com.trilead.ssh2.crypto.keys.Ed25519PublicKey;
+import com.trilead.ssh2.signature.DSASHA1Verify;
+import com.trilead.ssh2.signature.ECDSASHA2Verify;
+import com.trilead.ssh2.signature.Ed25519Verify;
+import com.trilead.ssh2.signature.RSASHA1Verify;
+import com.trilead.ssh2.signature.RSASHA256Verify;
+import com.trilead.ssh2.signature.RSASHA512Verify;
+import com.trilead.ssh2.transport.KexManager;
+
+/**
+ * The KnownHosts class is a handy tool to verify received server hostkeys
+ * based on the information in known_hosts files (the ones used by OpenSSH).
+ *
+ * It offers basically an in-memory database for known_hosts entries, as well as some
+ * helper functions. Entries from a known_hosts file can be loaded at construction time.
+ * It is also possible to add more keys later (e.g., one can parse different
+ * known_hosts files).
+ *
+ * It is a thread safe implementation, therefore, you need only to instantiate one
+ * KnownHosts for your whole application.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: KnownHosts.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
+ */
+
+public class KnownHosts
+{
+ public static final int HOSTKEY_IS_OK = 0;
+ public static final int HOSTKEY_IS_NEW = 1;
+ public static final int HOSTKEY_HAS_CHANGED = 2;
+
+ protected class KnownHostsEntry
+ {
+ String[] patterns;
+ PublicKey key;
+
+ KnownHostsEntry(String[] patterns, PublicKey key)
+ {
+ this.patterns = patterns;
+ this.key = key;
+ }
+
+ @Override
+ public String toString() {
+ return "KnownHostsEntry{keyType=" + key.getAlgorithm() + "}";
+ }
+ }
+
+ protected final LinkedList publicKeys = new LinkedList<>();
+
+ public KnownHosts()
+ {
+ }
+
+ public KnownHosts(char[] knownHostsData) throws IOException
+ {
+ initialize(knownHostsData);
+ }
+
+ public KnownHosts(File knownHosts) throws IOException
+ {
+ initialize(knownHosts);
+ }
+
+ /**
+ * Adds a single public key entry to the database. Note: this will NOT add the public key
+ * to any physical file (e.g., "~/.ssh/known_hosts") - use addHostkeyToFile() for that purpose.
+ * This method is designed to be used in a {@link ServerHostKeyVerifier}.
+ *
+ * @param hostnames a list of hostname patterns - at least one most be specified. Check out the
+ * OpenSSH sshd man page for a description of the pattern matching algorithm.
+ * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}.
+ * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}.
+ * @throws IOException
+ */
+ public void addHostkey(String[] hostnames, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException
+ {
+ if (hostnames == null)
+ throw new IllegalArgumentException("hostnames may not be null");
+
+ if (RSASHA1Verify.ID_SSH_RSA.equals(serverHostKeyAlgorithm) ||
+ RSASHA512Verify.ID_RSA_SHA_2_512.equals(serverHostKeyAlgorithm) ||
+ RSASHA256Verify.ID_RSA_SHA_2_256.equals(serverHostKeyAlgorithm))
+ {
+ PublicKey rpk = RSASHA1Verify.get().decodePublicKey(serverHostKey);
+
+ synchronized (publicKeys)
+ {
+ publicKeys.add(new KnownHostsEntry(hostnames, rpk));
+ }
+ } else if (serverHostKeyAlgorithm.equals(DSASHA1Verify.ID_SSH_DSS)) {
+ PublicKey dpk = DSASHA1Verify.get().decodePublicKey(serverHostKey);
+
+ synchronized (publicKeys)
+ {
+ publicKeys.add(new KnownHostsEntry(hostnames, dpk));
+ }
+ } else if (serverHostKeyAlgorithm.equals(ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().getKeyFormat())) {
+ PublicKey epk = ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().decodePublicKey(serverHostKey);
+
+ synchronized (publicKeys)
+ {
+ publicKeys.add(new KnownHostsEntry(hostnames, epk));
+ }
+ } else if (serverHostKeyAlgorithm.equals(ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().getKeyFormat())) {
+ PublicKey epk = ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().decodePublicKey(serverHostKey);
+
+ synchronized (publicKeys)
+ {
+ publicKeys.add(new KnownHostsEntry(hostnames, epk));
+ }
+ } else if (serverHostKeyAlgorithm.equals(ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().getKeyFormat())) {
+ PublicKey epk = ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().decodePublicKey(serverHostKey);
+
+ synchronized (publicKeys)
+ {
+ publicKeys.add(new KnownHostsEntry(hostnames, epk));
+ }
+ } else if (Ed25519Verify.ED25519_ID.equals(serverHostKeyAlgorithm)) {
+ PublicKey edpk = Ed25519Verify.get().decodePublicKey(serverHostKey);
+
+ synchronized (publicKeys)
+ {
+ publicKeys.add(new KnownHostsEntry(hostnames, edpk));
+ }
+ } else {
+ throw new IOException("Unknown host key type (" + serverHostKeyAlgorithm + ")");
+ }
+ }
+
+ /**
+ * Parses the given known_hosts data and adds entries to the database.
+ *
+ * @param knownHostsData
+ * @throws IOException
+ */
+ public void addHostkeys(char[] knownHostsData) throws IOException
+ {
+ initialize(knownHostsData);
+ }
+
+ /**
+ * Parses the given known_hosts file and adds entries to the database.
+ *
+ * @param knownHosts
+ * @throws IOException
+ */
+ public void addHostkeys(File knownHosts) throws IOException
+ {
+ initialize(knownHosts);
+ }
+
+ /**
+ * Generate the hashed representation of the given hostname. Useful for adding entries
+ * with hashed hostnames to a known_hosts file. (see -H option of OpenSSH key-gen).
+ *
+ * @param hostname
+ * @return the hashed representation, e.g., "|1|cDhrv7zwEUV3k71CEPHnhHZezhA=|Xo+2y6rUXo2OIWRAYhBOIijbJMA="
+ */
+ public static final String createHashedHostname(String hostname)
+ {
+ MessageDigest sha1;
+ try {
+ sha1 = MessageDigest.getInstance("SHA1");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("VM doesn't support SHA1", e);
+ }
+
+ byte[] salt = new byte[sha1.getDigestLength()];
+
+ new SecureRandom().nextBytes(salt);
+
+ byte[] hash = hmacSha1Hash(salt, hostname);
+
+ String base64_salt = new String(Base64.encode(salt));
+ String base64_hash = new String(Base64.encode(hash));
+
+ return new String("|1|" + base64_salt + "|" + base64_hash);
+ }
+
+ private static final byte[] hmacSha1Hash(byte[] salt, String hostname)
+ {
+ Mac hmac;
+ try {
+ hmac = Mac.getInstance("HmacSHA1");
+ if (salt.length != hmac.getMacLength())
+ throw new IllegalArgumentException("Salt has wrong length (" + salt.length + ")");
+ hmac.init(new SecretKeySpec(salt, "HmacSHA1"));
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Unable to HMAC-SHA1", e);
+ } catch (InvalidKeyException e) {
+ throw new RuntimeException("Unable to create SecretKey", e);
+ }
+
+ try {
+ hmac.update(hostname.getBytes("ISO-8859-1"));
+ } catch (UnsupportedEncodingException e) {
+ hmac.update(hostname.getBytes());
+ }
+
+ return hmac.doFinal();
+ }
+
+ private final boolean checkHashed(String entry, String hostname)
+ {
+ if (!entry.startsWith("|1|"))
+ return false;
+
+ int delim_idx = entry.indexOf('|', 3);
+
+ if (delim_idx == -1)
+ return false;
+
+ String salt_base64 = entry.substring(3, delim_idx);
+ String hash_base64 = entry.substring(delim_idx + 1);
+
+ byte[] salt = null;
+ byte[] hash = null;
+
+ try
+ {
+ salt = Base64.decode(salt_base64.toCharArray());
+ hash = Base64.decode(hash_base64.toCharArray());
+ }
+ catch (IOException e)
+ {
+ return false;
+ }
+
+ try {
+ MessageDigest sha1 = MessageDigest.getInstance("SHA1");
+ if (salt.length != sha1.getDigestLength())
+ return false;
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("VM does not support SHA1", e);
+ }
+
+ byte[] dig = hmacSha1Hash(salt, hostname);
+
+ for (int i = 0; i < dig.length; i++)
+ if (dig[i] != hash[i])
+ return false;
+
+ return true;
+ }
+
+ private int checkKey(String remoteHostname, PublicKey remoteKey)
+ {
+ int result = HOSTKEY_IS_NEW;
+
+ synchronized (publicKeys)
+ {
+ Iterator i = publicKeys.iterator();
+
+ while (i.hasNext())
+ {
+ KnownHostsEntry ke = i.next();
+
+ if (!hostnameMatches(ke.patterns, remoteHostname))
+ continue;
+
+ boolean res = matchKeys(ke.key, remoteKey);
+
+ if (res)
+ return HOSTKEY_IS_OK;
+
+ result = HOSTKEY_HAS_CHANGED;
+ }
+ }
+ return result;
+ }
+
+ private List getAllKeys(String hostname)
+ {
+ List keys = new ArrayList<>();
+
+ synchronized (publicKeys)
+ {
+ Iterator i = publicKeys.iterator();
+
+ while (i.hasNext())
+ {
+ KnownHostsEntry ke = i.next();
+
+ if (!hostnameMatches(ke.patterns, hostname))
+ continue;
+
+ keys.add(ke.key);
+ }
+ }
+
+ return keys;
+ }
+
+ /**
+ * Try to find the preferred order of hostkey algorithms for the given hostname.
+ * Based on the type of hostkey that is present in the internal database
+ * (i.e., either ssh-rsa or ssh-dss)
+ * an ordered list of hostkey algorithms is returned which can be passed
+ * to Connection.setServerHostKeyAlgorithms.
+ *
+ * @param hostname
+ * @return null if no key for the given hostname is present or
+ * there are keys of multiple types present for the given hostname. Otherwise,
+ * an array with hostkey algorithms is returned (i.e., an array of length 2).
+ */
+ public String[] getPreferredServerHostkeyAlgorithmOrder(String hostname)
+ {
+ String[] algos = recommendHostkeyAlgorithms(hostname);
+
+ if (algos != null)
+ return algos;
+
+ InetAddress[] ipAddresses;
+
+ try
+ {
+ ipAddresses = InetAddress.getAllByName(hostname);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+
+ for (InetAddress ipAddress : ipAddresses) {
+ algos = recommendHostkeyAlgorithms(ipAddress.getHostAddress());
+
+ if (algos != null)
+ return algos;
+ }
+
+ return null;
+ }
+
+ private final boolean hostnameMatches(String[] hostpatterns, String hostname)
+ {
+ boolean isMatch = false;
+ boolean negate = false;
+
+ hostname = hostname.toLowerCase(Locale.US);
+
+ for (int k = 0; k < hostpatterns.length; k++)
+ {
+ if (hostpatterns[k] == null)
+ continue;
+
+ String pattern = null;
+
+ /* In contrast to OpenSSH we also allow negated hash entries (as well as hashed
+ * entries in lines with multiple entries).
+ */
+
+ if ((hostpatterns[k].length() > 0) && (hostpatterns[k].charAt(0) == '!'))
+ {
+ pattern = hostpatterns[k].substring(1);
+ negate = true;
+ }
+ else
+ {
+ pattern = hostpatterns[k];
+ negate = false;
+ }
+
+ /* Optimize, no need to check this entry */
+
+ if ((isMatch) && (!negate))
+ continue;
+
+ /* Now compare */
+
+ if (pattern.charAt(0) == '|')
+ {
+ if (checkHashed(pattern, hostname))
+ {
+ if (negate)
+ return false;
+ isMatch = true;
+ }
+ }
+ else
+ {
+ pattern = pattern.toLowerCase(Locale.US);
+
+ if ((pattern.indexOf('?') != -1) || (pattern.indexOf('*') != -1))
+ {
+ if (pseudoRegex(pattern.toCharArray(), 0, hostname.toCharArray(), 0))
+ {
+ if (negate)
+ return false;
+ isMatch = true;
+ }
+ }
+ else if (pattern.compareTo(hostname) == 0)
+ {
+ if (negate)
+ return false;
+ isMatch = true;
+ }
+ }
+ }
+
+ return isMatch;
+ }
+
+ private void initialize(char[] knownHostsData) throws IOException
+ {
+ BufferedReader br = new BufferedReader(new CharArrayReader(knownHostsData));
+
+ while (true)
+ {
+ String line = br.readLine();
+
+ if (line == null)
+ break;
+
+ line = line.trim();
+
+ if (line.startsWith("#"))
+ continue;
+
+ String[] arr = line.split(" ");
+
+ if (arr.length >= 3)
+ {
+ String[] hostnames = arr[0].split(",");
+
+ byte[] msg = Base64.decode(arr[2].toCharArray());
+
+ addHostkey(hostnames, arr[1], msg);
+ }
+ }
+ }
+
+ private void initialize(File knownHosts) throws IOException
+ {
+ char[] buff = new char[512];
+
+ CharArrayWriter cw = new CharArrayWriter();
+
+ knownHosts.createNewFile();
+
+ FileReader fr = new FileReader(knownHosts);
+
+ while (true)
+ {
+ int len = fr.read(buff);
+ if (len < 0)
+ break;
+ cw.write(buff, 0, len);
+ }
+
+ fr.close();
+
+ initialize(cw.toCharArray());
+ }
+
+ private final boolean matchKeys(PublicKey key1, PublicKey key2)
+ {
+ return key1.equals(key2);
+ }
+
+ private final boolean pseudoRegex(char[] pattern, int i, char[] match, int j)
+ {
+ /* This matching logic is equivalent to the one present in OpenSSH 4.1 */
+
+ while (true)
+ {
+ /* Are we at the end of the pattern? */
+
+ if (pattern.length == i)
+ return (match.length == j);
+
+ if (pattern[i] == '*')
+ {
+ i++;
+
+ if (pattern.length == i)
+ return true;
+
+ if ((pattern[i] != '*') && (pattern[i] != '?'))
+ {
+ while (true)
+ {
+ if ((pattern[i] == match[j]) && pseudoRegex(pattern, i + 1, match, j + 1))
+ return true;
+ j++;
+ if (match.length == j)
+ return false;
+ }
+ }
+
+ while (true)
+ {
+ if (pseudoRegex(pattern, i, match, j))
+ return true;
+ j++;
+ if (match.length == j)
+ return false;
+ }
+ }
+
+ if (match.length == j)
+ return false;
+
+ if ((pattern[i] != '?') && (pattern[i] != match[j]))
+ return false;
+
+ i++;
+ j++;
+ }
+ }
+
+ private final String[] ALGOS_FOR_RSA = new String[] {
+ RSASHA512Verify.ID_RSA_SHA_2_512,
+ RSASHA256Verify.ID_RSA_SHA_2_256,
+ RSASHA1Verify.ID_SSH_RSA,
+ };
+
+ private final String ALGO_FOR_DSS = DSASHA1Verify.ID_SSH_DSS;
+
+ private final String ALGO_FOR_EDDSA = Ed25519Verify.ED25519_ID;
+
+ private String[] recommendHostkeyAlgorithms(String hostname) {
+ List preferredAlgos = new ArrayList<>();
+
+ List keys = getAllKeys(hostname);
+
+ for (PublicKey key : keys) {
+ if (key instanceof RSAPublicKey) {
+ preferredAlgos.addAll(Arrays.asList(ALGOS_FOR_RSA));
+ } else if (key instanceof DSAPublicKey) {
+ preferredAlgos.add(ALGO_FOR_DSS);
+ } else if (key instanceof Ed25519PublicKey) {
+ preferredAlgos.add(ALGO_FOR_EDDSA);
+ } else if (key instanceof ECPublicKey) {
+ preferredAlgos.add(ECDSASHA2Verify.getSshKeyType((ECPublicKey) key));
+ }
+ }
+
+ /* If we did not find anything that we know of, return null */
+ if (preferredAlgos.isEmpty())
+ return null;
+
+ /* Now put the preferred algo to the start of the array.
+ * You may ask yourself why we do it that way - basically, we could just
+ * return only the preferred algorithm: since we have a saved key of that
+ * type (sent earlier from the remote host), then that should work out.
+ * However, imagine that the server is (for whatever reasons) not offering
+ * that type of hostkey anymore (e.g., "ssh-rsa" was disabled and
+ * now "ssh-dss" is being used). If we then do not let the server send us
+ * a fresh key of the new type, then we shoot ourself into the foot:
+ * the connection cannot be established and hence the user cannot decide
+ * if he/she wants to accept the new key.
+ */
+
+ List preferredAndOthers = new ArrayList<>();
+ List notPreferred = new ArrayList<>();
+ for (String algo : KexManager.getDefaultServerHostkeyAlgorithmList()) {
+ if (preferredAlgos.contains(algo)) {
+ preferredAndOthers.add(algo);
+ } else {
+ notPreferred.add(algo);
+ }
+ }
+ preferredAndOthers.addAll(notPreferred);
+ return preferredAndOthers.toArray(new String[0]);
+ }
+
+ /**
+ * Checks the internal hostkey database for the given hostkey.
+ * If no matching key can be found, then the hostname is resolved to an IP address
+ * and the search is repeated using that IP address.
+ *
+ * @param hostname the server's hostname, will be matched with all hostname patterns
+ * @param serverHostKeyAlgorithm type of hostkey, either ssh-rsa or ssh-dss
+ * @param serverHostKey the key blob
+ * @return
+ *
HOSTKEY_IS_OK: the given hostkey matches an entry for the given hostname
+ *
HOSTKEY_IS_NEW: no entries found for this hostname and this type of hostkey
+ *
HOSTKEY_HAS_CHANGED: hostname is known, but with another key of the same type
+ * (man-in-the-middle attack?)
+ *
+ * @throws IOException if the supplied key blob cannot be parsed or does not match the given hostkey type.
+ */
+ public int verifyHostkey(String hostname, String serverHostKeyAlgorithm, byte[] serverHostKey) throws IOException
+ {
+ PublicKey remoteKey = null;
+
+ if (RSASHA1Verify.ID_SSH_RSA.equals(serverHostKeyAlgorithm) ||
+ RSASHA256Verify.ID_RSA_SHA_2_256.equals(serverHostKeyAlgorithm) ||
+ RSASHA512Verify.ID_RSA_SHA_2_512.equals(serverHostKeyAlgorithm))
+ {
+ remoteKey = RSASHA1Verify.get().decodePublicKey(serverHostKey);
+ }
+ else if (DSASHA1Verify.ID_SSH_DSS.equals(serverHostKeyAlgorithm))
+ {
+ remoteKey = DSASHA1Verify.get().decodePublicKey(serverHostKey);
+ }
+ else if (ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().getKeyFormat().equals(serverHostKeyAlgorithm))
+ {
+ remoteKey = ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().decodePublicKey(serverHostKey);
+ }
+ else if (ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().getKeyFormat().equals(serverHostKeyAlgorithm))
+ {
+ remoteKey = ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().decodePublicKey(serverHostKey);
+ }
+ else if (ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().getKeyFormat().equals(serverHostKeyAlgorithm))
+ {
+ remoteKey = ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().decodePublicKey(serverHostKey);
+ }
+ else if (Ed25519Verify.ED25519_ID.equals(serverHostKeyAlgorithm))
+ {
+ remoteKey = Ed25519Verify.get().decodePublicKey(serverHostKey);
+ }
+ else
+ throw new IllegalArgumentException("Unknown hostkey type " + serverHostKeyAlgorithm);
+
+ int result = checkKey(hostname, remoteKey);
+
+ if (result == HOSTKEY_IS_OK)
+ return result;
+
+ InetAddress[] ipAddresses = null;
+
+ try
+ {
+ ipAddresses = InetAddress.getAllByName(hostname);
+ }
+ catch (UnknownHostException e)
+ {
+ return result;
+ }
+
+ for (InetAddress ipAddress : ipAddresses) {
+ int newresult = checkKey(ipAddress.getHostAddress(), remoteKey);
+
+ if (newresult == HOSTKEY_IS_OK)
+ return newresult;
+
+ if (newresult == HOSTKEY_HAS_CHANGED)
+ result = HOSTKEY_HAS_CHANGED;
+ }
+
+ return result;
+ }
+
+ /**
+ * Adds a single public key entry to the a known_hosts file.
+ * This method is designed to be used in a {@link ServerHostKeyVerifier}.
+ *
+ * @param knownHosts the file where the publickey entry will be appended.
+ * @param hostnames a list of hostname patterns - at least one most be specified. Check out the
+ * OpenSSH sshd man page for a description of the pattern matching algorithm.
+ * @param serverHostKeyAlgorithm as passed to the {@link ServerHostKeyVerifier}.
+ * @param serverHostKey as passed to the {@link ServerHostKeyVerifier}.
+ * @throws IOException
+ */
+ public final static void addHostkeyToFile(File knownHosts, String[] hostnames, String serverHostKeyAlgorithm,
+ byte[] serverHostKey) throws IOException
+ {
+ if ((hostnames == null) || (hostnames.length == 0))
+ throw new IllegalArgumentException("Need at least one hostname specification");
+
+ if ((serverHostKeyAlgorithm == null) || (serverHostKey == null))
+ throw new IllegalArgumentException();
+
+ CharArrayWriter writer = new CharArrayWriter();
+
+ for (int i = 0; i < hostnames.length; i++)
+ {
+ if (i != 0)
+ writer.write(',');
+ writer.write(hostnames[i]);
+ }
+
+ writer.write(' ');
+ writer.write(serverHostKeyAlgorithm);
+ writer.write(' ');
+ writer.write(Base64.encode(serverHostKey));
+ writer.write("\n");
+
+ char[] entry = writer.toCharArray();
+
+ RandomAccessFile raf = new RandomAccessFile(knownHosts, "rw");
+
+ long len = raf.length();
+
+ if (len > 0)
+ {
+ raf.seek(len - 1);
+ int last = raf.read();
+ if (last != '\n')
+ raf.write('\n');
+ }
+
+ try {
+ raf.write(new String(entry).getBytes("ISO-8859-1"));
+ } catch (UnsupportedEncodingException e) {
+ raf.write(new String(entry).getBytes());
+ }
+ raf.close();
+ }
+
+ /**
+ * Generates a "raw" fingerprint of a hostkey.
+ *
+ * @param type either "md5" or "sha1"
+ * @param keyType either "ssh-rsa" or "ssh-dss"
+ * @param hostkey the hostkey
+ * @return the raw fingerprint
+ */
+ private static byte[] rawFingerPrint(String type, String keyType, byte[] hostkey)
+ {
+ MessageDigest dig = null;
+
+ try {
+ if ("md5".equals(type))
+ {
+ dig = MessageDigest.getInstance("MD5");
+ }
+ else if ("sha1".equals(type))
+ {
+ dig = MessageDigest.getInstance("SHA1");
+ }
+ else
+ {
+ throw new IllegalArgumentException("Unknown hash type " + type);
+ }
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Unknown hash type " + type);
+ }
+
+ if (Ed25519Verify.ED25519_ID.equals(keyType))
+ {
+ }
+ else if (keyType.startsWith(ECDSASHA2Verify.ECDSA_SHA2_PREFIX))
+ {
+ }
+ else if (RSASHA1Verify.ID_SSH_RSA.equals(keyType))
+ {
+ }
+ else if (DSASHA1Verify.ID_SSH_DSS.equals(keyType))
+ {
+ }
+ else if (RSASHA256Verify.ID_RSA_SHA_2_256.equals(keyType))
+ {
+ }
+ else if (RSASHA512Verify.ID_RSA_SHA_2_512.equals(keyType))
+ {
+ }
+ else
+ throw new IllegalArgumentException("Unknown key type " + keyType);
+
+ if (hostkey == null)
+ throw new IllegalArgumentException("hostkey is null");
+
+ dig.update(hostkey);
+ return dig.digest();
+ }
+
+ /**
+ * Convert a raw fingerprint to hex representation (XX:YY:ZZ...).
+ * @param fingerprint raw fingerprint
+ * @return the hex representation
+ */
+ private static String rawToHexFingerprint(byte[] fingerprint)
+ {
+ final char[] alpha = "0123456789abcdef".toCharArray();
+
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < fingerprint.length; i++)
+ {
+ if (i != 0)
+ sb.append(':');
+ int b = fingerprint[i] & 0xff;
+ sb.append(alpha[b >> 4]);
+ sb.append(alpha[b & 15]);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Convert a raw fingerprint to bubblebabble representation.
+ * @param raw raw fingerprint
+ * @return the bubblebabble representation
+ */
+ static final private String rawToBubblebabbleFingerprint(byte[] raw)
+ {
+ final char[] v = "aeiouy".toCharArray();
+ final char[] c = "bcdfghklmnprstvzx".toCharArray();
+
+ StringBuilder sb = new StringBuilder();
+
+ int seed = 1;
+
+ int rounds = (raw.length / 2) + 1;
+
+ sb.append('x');
+
+ for (int i = 0; i < rounds; i++)
+ {
+ if (((i + 1) < rounds) || ((raw.length) % 2 != 0))
+ {
+ sb.append(v[(((raw[2 * i] >> 6) & 3) + seed) % 6]);
+ sb.append(c[(raw[2 * i] >> 2) & 15]);
+ sb.append(v[((raw[2 * i] & 3) + (seed / 6)) % 6]);
+
+ if ((i + 1) < rounds)
+ {
+ sb.append(c[(((raw[(2 * i) + 1])) >> 4) & 15]);
+ sb.append('-');
+ sb.append(c[(((raw[(2 * i) + 1]))) & 15]);
+ // As long as seed >= 0, seed will be >= 0 afterwards
+ seed = ((seed * 5) + (((raw[2 * i] & 0xff) * 7) + (raw[(2 * i) + 1] & 0xff))) % 36;
+ }
+ }
+ else
+ {
+ sb.append(v[seed % 6]); // seed >= 0, therefore index positive
+ sb.append('x');
+ sb.append(v[seed / 6]);
+ }
+ }
+
+ sb.append('x');
+
+ return sb.toString();
+ }
+
+ /**
+ * Convert a ssh2 key-blob into a human readable hex fingerprint.
+ * Generated fingerprints are identical to those generated by OpenSSH.
+ *
+ * Example fingerprint: d0:cb:76:19:99:5a:03:fc:73:10:70:93:f2:44:63:47.
+
+ * @param keytype either "ssh-rsa" or "ssh-dss"
+ * @param publickey key blob
+ * @return Hex fingerprint
+ */
+ public final static String createHexFingerprint(String keytype, byte[] publickey)
+ {
+ byte[] raw = rawFingerPrint("md5", keytype, publickey);
+ return rawToHexFingerprint(raw);
+ }
+
+ /**
+ * Convert a ssh2 key-blob into a human readable bubblebabble fingerprint.
+ * The used bubblebabble algorithm (taken from OpenSSH) generates fingerprints
+ * that are easier to remember for humans.
+ *
+ * Example fingerprint: xofoc-bubuz-cazin-zufyl-pivuk-biduk-tacib-pybur-gonar-hotat-lyxux.
+ *
+ * @param keytype either "ssh-rsa" or "ssh-dss"
+ * @param publickey key data
+ * @return Bubblebabble fingerprint
+ */
+ public final static String createBubblebabbleFingerprint(String keytype, byte[] publickey)
+ {
+ byte[] raw = rawFingerPrint("sha1", keytype, publickey);
+ return rawToBubblebabbleFingerprint(raw);
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/LocalPortForwarder.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/LocalPortForwarder.java
new file mode 100644
index 0000000000..6f42d5ad27
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/LocalPortForwarder.java
@@ -0,0 +1,61 @@
+
+package com.trilead.ssh2;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import com.trilead.ssh2.channel.ChannelManager;
+import com.trilead.ssh2.channel.LocalAcceptThread;
+
+
+/**
+ * A LocalPortForwarder forwards TCP/IP connections to a local
+ * port via the secure tunnel to another host (which may or may not be identical
+ * to the remote SSH-2 server). Checkout {@link Connection#createLocalPortForwarder(int, String, int)}
+ * on how to create one.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: LocalPortForwarder.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class LocalPortForwarder
+{
+ ChannelManager cm;
+
+ String host_to_connect;
+
+ int port_to_connect;
+
+ LocalAcceptThread lat;
+
+ LocalPortForwarder(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;
+
+ lat = new LocalAcceptThread(cm, local_port, host_to_connect, port_to_connect);
+ lat.setDaemon(true);
+ lat.start();
+ }
+
+ LocalPortForwarder(ChannelManager cm, InetSocketAddress addr, 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;
+
+ lat = new LocalAcceptThread(cm, addr, host_to_connect, port_to_connect);
+ lat.setDaemon(true);
+ lat.start();
+ }
+
+ /**
+ * Stop TCP/IP forwarding of newly arriving connections.
+ *
+ */
+ public void close() {
+ lat.stopWorking();
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/LocalStreamForwarder.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/LocalStreamForwarder.java
new file mode 100644
index 0000000000..ce5c501ecc
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/LocalStreamForwarder.java
@@ -0,0 +1,74 @@
+
+package com.trilead.ssh2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import com.trilead.ssh2.channel.Channel;
+import com.trilead.ssh2.channel.ChannelManager;
+import com.trilead.ssh2.channel.LocalAcceptThread;
+
+
+/**
+ * A LocalStreamForwarder forwards an Input- and Outputstream
+ * pair via the secure tunnel to another host (which may or may not be identical
+ * to the remote SSH-2 server).
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: LocalStreamForwarder.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class LocalStreamForwarder
+{
+ ChannelManager cm;
+
+ String host_to_connect;
+ int port_to_connect;
+ LocalAcceptThread lat;
+
+ Channel cn;
+
+ LocalStreamForwarder(ChannelManager cm, 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;
+
+ cn = cm.openDirectTCPIPChannel(host_to_connect, port_to_connect, "127.0.0.1", 0);
+ }
+
+ /**
+ * @return An InputStream object.
+ */
+ public InputStream getInputStream() {
+ return cn.getStdoutStream();
+ }
+
+ /**
+ * Get the OutputStream. Please be aware that the implementation MAY use an
+ * internal buffer. To make sure that the buffered data is sent over the
+ * tunnel, you have to call the flush method of the
+ * OutputStream. To signal EOF, please use the
+ * close method of the OutputStream.
+ *
+ * @return An OutputStream object.
+ */
+ public OutputStream getOutputStream() {
+ return cn.getStdinStream();
+ }
+
+ /**
+ * Close the underlying SSH forwarding channel and free up resources.
+ * You can also use this method to force the shutdown of the underlying
+ * forwarding channel. Pending output (OutputStream not flushed) will NOT
+ * be sent. Pending input (InputStream) can still be read. If the shutdown
+ * operation is already in progress (initiated from either side), then this
+ * call is a no-op.
+ *
+ * @throws IOException
+ */
+ public void close() throws IOException
+ {
+ cm.closeChannel(cn, "Closed due to user request.", true);
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ProxyData.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ProxyData.java
new file mode 100644
index 0000000000..9b240bca36
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ProxyData.java
@@ -0,0 +1,28 @@
+
+package com.trilead.ssh2;
+
+import java.io.IOException;
+import java.net.Socket;
+
+/**
+ * An abstract interface implemented by all proxy data implementations.
+ *
+ * @see HTTPProxyData
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: ProxyData.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public interface ProxyData
+{
+ /**
+ * Connects the socket to the given destination using the proxy method that this instance
+ * represents.
+ * @param hostname hostname of end host (not proxy)
+ * @param port port of end host (not proxy)
+ * @param connectTimeout number of seconds before giving up on connecting to end host
+ * @throws IOException if the connection could not be completed
+ * @return connected socket instance
+ */
+ Socket openConnection(String hostname, int port, int connectTimeout) throws IOException;
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SCPClient.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SCPClient.java
new file mode 100644
index 0000000000..71345da6ad
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SCPClient.java
@@ -0,0 +1,747 @@
+
+package com.trilead.ssh2;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * A very basic SCPClient that can be used to copy files from/to
+ * the SSH-2 server. On the server side, the "scp" program must be in the PATH.
+ *
+ * This scp client is thread safe - you can download (and upload) different sets
+ * of files concurrently without any troubles. The SCPClient is
+ * actually mapping every request to a distinct {@link Session}.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: SCPClient.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
+ */
+
+public class SCPClient
+{
+ Connection conn;
+
+ class LenNamePair
+ {
+ long length;
+ String filename;
+ }
+
+ public SCPClient(Connection conn)
+ {
+ if (conn == null)
+ throw new IllegalArgumentException("Cannot accept null argument!");
+ this.conn = conn;
+ }
+
+ private void readResponse(InputStream is) throws IOException
+ {
+ int c = is.read();
+
+ if (c == 0)
+ return;
+
+ if (c == -1)
+ throw new IOException("Remote scp terminated unexpectedly.");
+
+ if ((c != 1) && (c != 2))
+ throw new IOException("Remote scp sent illegal error code.");
+
+ if (c == 2)
+ throw new IOException("Remote scp terminated with error.");
+
+ String err = receiveLine(is);
+ throw new IOException("Remote scp terminated with error (" + err + ").");
+ }
+
+ private String receiveLine(InputStream is) throws IOException
+ {
+ StringBuffer sb = new StringBuffer(30);
+
+ while (true)
+ {
+ /*
+ * This is a random limit - if your path names are longer, then
+ * adjust it
+ */
+
+ if (sb.length() > 8192)
+ throw new IOException("Remote scp sent a too long line");
+
+ int c = is.read();
+
+ if (c < 0)
+ throw new IOException("Remote scp terminated unexpectedly.");
+
+ if (c == '\n')
+ break;
+
+ sb.append((char) c);
+
+ }
+ return sb.toString();
+ }
+
+ private LenNamePair parseCLine(String line) throws IOException
+ {
+ /* Minimum line: "xxxx y z" ---> 8 chars */
+
+ long len;
+
+ if (line.length() < 8)
+ throw new IOException("Malformed C line sent by remote SCP binary, line too short.");
+
+ if ((line.charAt(4) != ' ') || (line.charAt(5) == ' '))
+ throw new IOException("Malformed C line sent by remote SCP binary.");
+
+ int length_name_sep = line.indexOf(' ', 5);
+
+ if (length_name_sep == -1)
+ throw new IOException("Malformed C line sent by remote SCP binary.");
+
+ String length_substring = line.substring(5, length_name_sep);
+ String name_substring = line.substring(length_name_sep + 1);
+
+ if ((length_substring.length() <= 0) || (name_substring.length() <= 0))
+ throw new IOException("Malformed C line sent by remote SCP binary.");
+
+ if ((6 + length_substring.length() + name_substring.length()) != line.length())
+ throw new IOException("Malformed C line sent by remote SCP binary.");
+
+ try
+ {
+ len = Long.parseLong(length_substring);
+ }
+ catch (NumberFormatException e)
+ {
+ throw new IOException("Malformed C line sent by remote SCP binary, cannot parse file length.");
+ }
+
+ if (len < 0)
+ throw new IOException("Malformed C line sent by remote SCP binary, illegal file length.");
+
+ LenNamePair lnp = new LenNamePair();
+ lnp.length = len;
+ lnp.filename = name_substring;
+
+ return lnp;
+ }
+
+ private void sendBytes(Session sess, byte[] data, String fileName, String mode) throws IOException
+ {
+ OutputStream os = sess.getStdin();
+ InputStream is = new BufferedInputStream(sess.getStdout(), 512);
+
+ readResponse(is);
+
+ String cline = "C" + mode + " " + data.length + " " + fileName + "\n";
+
+ try {
+ os.write(cline.getBytes("ISO-8859-1"));
+ } catch (UnsupportedEncodingException e) {
+ os.write(cline.getBytes());
+ }
+
+ os.flush();
+
+ readResponse(is);
+
+ os.write(data, 0, data.length);
+ os.write(0);
+ os.flush();
+
+ readResponse(is);
+
+ try {
+ os.write("E\n".getBytes("ISO-8859-1"));
+ } catch (UnsupportedEncodingException e) {
+ os.write("E\n".getBytes());
+ }
+ os.flush();
+ }
+
+ private void sendFiles(Session sess, String[] files, String[] remoteFiles, String mode) throws IOException
+ {
+ byte[] buffer = new byte[8192];
+
+ OutputStream os = new BufferedOutputStream(sess.getStdin(), 40000);
+ InputStream is = new BufferedInputStream(sess.getStdout(), 512);
+
+ readResponse(is);
+
+ for (int i = 0; i < files.length; i++)
+ {
+ File f = new File(files[i]);
+ long remain = f.length();
+
+ String remoteName;
+
+ if ((remoteFiles != null) && (remoteFiles.length > i) && (remoteFiles[i] != null))
+ remoteName = remoteFiles[i];
+ else
+ remoteName = f.getName();
+
+ String cline = "C" + mode + " " + remain + " " + remoteName + "\n";
+
+ try {
+ os.write(cline.getBytes("ISO-8859-1"));
+ } catch (UnsupportedEncodingException e) {
+ os.write(cline.getBytes());
+ }
+ os.flush();
+
+ readResponse(is);
+
+ FileInputStream fis = null;
+
+ try
+ {
+ fis = new FileInputStream(f);
+
+ while (remain > 0)
+ {
+ int trans;
+ if (remain > buffer.length)
+ trans = buffer.length;
+ else
+ trans = (int) remain;
+
+ if (fis.read(buffer, 0, trans) != trans)
+ throw new IOException("Cannot read enough from local file " + files[i]);
+
+ os.write(buffer, 0, trans);
+
+ remain -= trans;
+ }
+ }
+ finally
+ {
+ if (fis != null)
+ fis.close();
+ }
+
+ os.write(0);
+ os.flush();
+
+ readResponse(is);
+ }
+
+ try {
+ os.write("E\n".getBytes("ISO-8859-1"));
+ } catch (UnsupportedEncodingException e) {
+ os.write("E\n".getBytes("ISO-8859-1"));
+ }
+ os.flush();
+ }
+
+ private void receiveFiles(Session sess, OutputStream[] targets) throws IOException
+ {
+ byte[] buffer = new byte[8192];
+
+ OutputStream os = new BufferedOutputStream(sess.getStdin(), 512);
+ InputStream is = new BufferedInputStream(sess.getStdout(), 40000);
+
+ os.write(0x0);
+ os.flush();
+
+ for (int i = 0; i < targets.length; i++)
+ {
+ LenNamePair lnp = null;
+
+ while (true)
+ {
+ int c = is.read();
+ if (c < 0)
+ throw new IOException("Remote scp terminated unexpectedly.");
+
+ String line = receiveLine(is);
+
+ if (c == 'T')
+ {
+ /* Ignore modification times */
+
+ continue;
+ }
+
+ if ((c == 1) || (c == 2))
+ throw new IOException("Remote SCP error: " + line);
+
+ if (c == 'C')
+ {
+ lnp = parseCLine(line);
+ break;
+
+ }
+ throw new IOException("Remote SCP error: " + ((char) c) + line);
+ }
+
+ os.write(0x0);
+ os.flush();
+
+ long remain = lnp.length;
+
+ while (remain > 0)
+ {
+ int trans;
+ if (remain > buffer.length)
+ trans = buffer.length;
+ else
+ trans = (int) remain;
+
+ int this_time_received = is.read(buffer, 0, trans);
+
+ if (this_time_received < 0)
+ {
+ throw new IOException("Remote scp terminated connection unexpectedly");
+ }
+
+ targets[i].write(buffer, 0, this_time_received);
+
+ remain -= this_time_received;
+ }
+
+ readResponse(is);
+
+ os.write(0x0);
+ os.flush();
+ }
+ }
+
+ private void receiveFiles(Session sess, String[] files, String target) throws IOException
+ {
+ byte[] buffer = new byte[8192];
+
+ OutputStream os = new BufferedOutputStream(sess.getStdin(), 512);
+ InputStream is = new BufferedInputStream(sess.getStdout(), 40000);
+
+ os.write(0x0);
+ os.flush();
+
+ for (int i = 0; i < files.length; i++)
+ {
+ LenNamePair lnp = null;
+
+ while (true)
+ {
+ int c = is.read();
+ if (c < 0)
+ throw new IOException("Remote scp terminated unexpectedly.");
+
+ String line = receiveLine(is);
+
+ if (c == 'T')
+ {
+ /* Ignore modification times */
+
+ continue;
+ }
+
+ if ((c == 1) || (c == 2))
+ throw new IOException("Remote SCP error: " + line);
+
+ if (c == 'C')
+ {
+ lnp = parseCLine(line);
+ break;
+
+ }
+ throw new IOException("Remote SCP error: " + ((char) c) + line);
+ }
+
+ os.write(0x0);
+ os.flush();
+
+ File f = new File(target + File.separatorChar + lnp.filename);
+ FileOutputStream fop = null;
+
+ try
+ {
+ fop = new FileOutputStream(f);
+
+ long remain = lnp.length;
+
+ while (remain > 0)
+ {
+ int trans;
+ if (remain > buffer.length)
+ trans = buffer.length;
+ else
+ trans = (int) remain;
+
+ int this_time_received = is.read(buffer, 0, trans);
+
+ if (this_time_received < 0)
+ {
+ throw new IOException("Remote scp terminated connection unexpectedly");
+ }
+
+ fop.write(buffer, 0, this_time_received);
+
+ remain -= this_time_received;
+ }
+ }
+ finally
+ {
+ if (fop != null)
+ fop.close();
+ }
+
+ readResponse(is);
+
+ os.write(0x0);
+ os.flush();
+ }
+ }
+
+ /**
+ * Copy a local file to a remote directory, uses mode 0600 when creating the
+ * file on the remote side.
+ *
+ * @param localFile
+ * Path and name of local file.
+ * @param remoteTargetDirectory
+ * Remote target directory. Use an empty string to specify the
+ * default directory.
+ *
+ * @throws IOException
+ */
+ public void put(String localFile, String remoteTargetDirectory) throws IOException
+ {
+ put(new String[] { localFile }, remoteTargetDirectory, "0600");
+ }
+
+ /**
+ * Copy a set of local files to a remote directory, uses mode 0600 when
+ * creating files on the remote side.
+ *
+ * @param localFiles
+ * Paths and names of local file names.
+ * @param remoteTargetDirectory
+ * Remote target directory. Use an empty string to specify the
+ * default directory.
+ *
+ * @throws IOException
+ */
+
+ public void put(String[] localFiles, String remoteTargetDirectory) throws IOException
+ {
+ put(localFiles, remoteTargetDirectory, "0600");
+ }
+
+ /**
+ * Copy a local file to a remote directory, uses the specified mode when
+ * creating the file on the remote side.
+ *
+ * @param localFile
+ * Path and name of local file.
+ * @param remoteTargetDirectory
+ * Remote target directory. Use an empty string to specify the
+ * default directory.
+ * @param mode
+ * a four digit string (e.g., 0644, see "man chmod", "man open")
+ * @throws IOException
+ */
+ public void put(String localFile, String remoteTargetDirectory, String mode) throws IOException
+ {
+ put(new String[] { localFile }, remoteTargetDirectory, mode);
+ }
+
+ /**
+ * Copy a local file to a remote directory, uses the specified mode and
+ * remote filename when creating the file on the remote side.
+ *
+ * @param localFile
+ * Path and name of local file.
+ * @param remoteFileName
+ * The name of the file which will be created in the remote
+ * target directory.
+ * @param remoteTargetDirectory
+ * Remote target directory. Use an empty string to specify the
+ * default directory.
+ * @param mode
+ * a four digit string (e.g., 0644, see "man chmod", "man open")
+ * @throws IOException
+ */
+ public void put(String localFile, String remoteFileName, String remoteTargetDirectory, String mode)
+ throws IOException
+ {
+ put(new String[] { localFile }, new String[] { remoteFileName }, remoteTargetDirectory, mode);
+ }
+
+ /**
+ * Create a remote file and copy the contents of the passed byte array into
+ * it. Uses mode 0600 for creating the remote file.
+ *
+ * @param data
+ * the data to be copied into the remote file.
+ * @param remoteFileName
+ * The name of the file which will be created in the remote
+ * target directory.
+ * @param remoteTargetDirectory
+ * Remote target directory. Use an empty string to specify the
+ * default directory.
+ * @throws IOException
+ */
+
+ public void put(byte[] data, String remoteFileName, String remoteTargetDirectory) throws IOException
+ {
+ put(data, remoteFileName, remoteTargetDirectory, "0600");
+ }
+
+ /**
+ * Create a remote file and copy the contents of the passed byte array into
+ * it. The method use the specified mode when creating the file on the
+ * remote side.
+ *
+ * @param data
+ * the data to be copied into the remote file.
+ * @param remoteFileName
+ * The name of the file which will be created in the remote
+ * target directory.
+ * @param remoteTargetDirectory
+ * Remote target directory. Use an empty string to specify the
+ * default directory.
+ * @param mode
+ * a four digit string (e.g., 0644, see "man chmod", "man open")
+ * @throws IOException
+ */
+ public void put(byte[] data, String remoteFileName, String remoteTargetDirectory, String mode) throws IOException
+ {
+ Session sess = null;
+
+ if ((remoteFileName == null) || (remoteTargetDirectory == null) || (mode == null))
+ throw new IllegalArgumentException("Null argument.");
+
+ if (mode.length() != 4)
+ throw new IllegalArgumentException("Invalid mode.");
+
+ for (int i = 0; i < mode.length(); i++)
+ if (!Character.isDigit(mode.charAt(i)))
+ throw new IllegalArgumentException("Invalid mode.");
+
+ remoteTargetDirectory = remoteTargetDirectory.trim();
+ remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : ".";
+
+ String cmd = "scp -t -d " + remoteTargetDirectory;
+
+ try
+ {
+ sess = conn.openSession();
+ sess.execCommand(cmd);
+ sendBytes(sess, data, remoteFileName, mode);
+ }
+ catch (IOException e)
+ {
+ throw new IOException("Error during SCP transfer.", e);
+ }
+ finally
+ {
+ if (sess != null)
+ sess.close();
+ }
+ }
+
+ /**
+ * Copy a set of local files to a remote directory, uses the specified mode
+ * when creating the files on the remote side.
+ *
+ * @param localFiles
+ * Paths and names of the local files.
+ * @param remoteTargetDirectory
+ * Remote target directory. Use an empty string to specify the
+ * default directory.
+ * @param mode
+ * a four digit string (e.g., 0644, see "man chmod", "man open")
+ * @throws IOException
+ */
+ public void put(String[] localFiles, String remoteTargetDirectory, String mode) throws IOException
+ {
+ put(localFiles, null, remoteTargetDirectory, mode);
+ }
+
+ public void put(String[] localFiles, String[] remoteFiles, String remoteTargetDirectory, String mode)
+ throws IOException
+ {
+ Session sess = null;
+
+ /*
+ * remoteFiles may be null, indicating that the local filenames shall be
+ * used
+ */
+
+ if ((localFiles == null) || (remoteTargetDirectory == null) || (mode == null))
+ throw new IllegalArgumentException("Null argument.");
+
+ if (mode.length() != 4)
+ throw new IllegalArgumentException("Invalid mode.");
+
+ for (int i = 0; i < mode.length(); i++)
+ if (!Character.isDigit(mode.charAt(i)))
+ throw new IllegalArgumentException("Invalid mode.");
+
+ if (localFiles.length == 0)
+ return;
+
+ remoteTargetDirectory = remoteTargetDirectory.trim();
+ remoteTargetDirectory = (remoteTargetDirectory.length() > 0) ? remoteTargetDirectory : ".";
+
+ String cmd = "scp -t -d " + remoteTargetDirectory;
+
+ for (int i = 0; i < localFiles.length; i++)
+ {
+ if (localFiles[i] == null)
+ throw new IllegalArgumentException("Cannot accept null filename.");
+ }
+
+ try
+ {
+ sess = conn.openSession();
+ sess.execCommand(cmd);
+ sendFiles(sess, localFiles, remoteFiles, mode);
+ }
+ catch (IOException e)
+ {
+ throw new IOException("Error during SCP transfer.", e);
+ }
+ finally
+ {
+ if (sess != null)
+ sess.close();
+ }
+ }
+
+ /**
+ * Download a file from the remote server to a local directory.
+ *
+ * @param remoteFile
+ * Path and name of the remote file.
+ * @param localTargetDirectory
+ * Local directory to put the downloaded file.
+ *
+ * @throws IOException
+ */
+ public void get(String remoteFile, String localTargetDirectory) throws IOException
+ {
+ get(new String[] { remoteFile }, localTargetDirectory);
+ }
+
+ /**
+ * Download a file from the remote server and pipe its contents into an
+ * OutputStream. Please note that, to enable flexible usage
+ * of this method, the OutputStream will not be closed nor
+ * flushed.
+ *
+ * @param remoteFile
+ * Path and name of the remote file.
+ * @param target
+ * OutputStream where the contents of the file will be sent to.
+ * @throws IOException
+ */
+ public void get(String remoteFile, OutputStream target) throws IOException
+ {
+ get(new String[] { remoteFile }, new OutputStream[] { target });
+ }
+
+ private void get(String remoteFiles[], OutputStream[] targets) throws IOException
+ {
+ Session sess = null;
+
+ if ((remoteFiles == null) || (targets == null))
+ throw new IllegalArgumentException("Null argument.");
+
+ if (remoteFiles.length != targets.length)
+ throw new IllegalArgumentException("Length of arguments does not match.");
+
+ if (remoteFiles.length == 0)
+ return;
+
+ String cmd = "scp -f";
+
+ for (int i = 0; i < remoteFiles.length; i++)
+ {
+ if (remoteFiles[i] == null)
+ throw new IllegalArgumentException("Cannot accept null filename.");
+
+ String tmp = remoteFiles[i].trim();
+
+ if (tmp.length() == 0)
+ throw new IllegalArgumentException("Cannot accept empty filename.");
+
+ cmd += (" " + tmp);
+ }
+
+ try
+ {
+ sess = conn.openSession();
+ sess.execCommand(cmd);
+ receiveFiles(sess, targets);
+ }
+ catch (IOException e)
+ {
+ throw new IOException("Error during SCP transfer.", e);
+ }
+ finally
+ {
+ if (sess != null)
+ sess.close();
+ }
+ }
+
+ /**
+ * Download a set of files from the remote server to a local directory.
+ *
+ * @param remoteFiles
+ * Paths and names of the remote files.
+ * @param localTargetDirectory
+ * Local directory to put the downloaded files.
+ *
+ * @throws IOException
+ */
+ public void get(String remoteFiles[], String localTargetDirectory) throws IOException
+ {
+ Session sess = null;
+
+ if ((remoteFiles == null) || (localTargetDirectory == null))
+ throw new IllegalArgumentException("Null argument.");
+
+ if (remoteFiles.length == 0)
+ return;
+
+ String cmd = "scp -f";
+
+ for (int i = 0; i < remoteFiles.length; i++)
+ {
+ if (remoteFiles[i] == null)
+ throw new IllegalArgumentException("Cannot accept null filename.");
+
+ String tmp = remoteFiles[i].trim();
+
+ if (tmp.length() == 0)
+ throw new IllegalArgumentException("Cannot accept empty filename.");
+
+ cmd += (" " + tmp);
+ }
+
+ try
+ {
+ sess = conn.openSession();
+ sess.execCommand(cmd);
+ receiveFiles(sess, remoteFiles, localTargetDirectory);
+ }
+ catch (IOException e)
+ {
+ throw new IOException("Error during SCP transfer.", e);
+ }
+ finally
+ {
+ if (sess != null)
+ sess.close();
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPException.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPException.java
new file mode 100644
index 0000000000..99a69d9943
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPException.java
@@ -0,0 +1,91 @@
+
+package com.trilead.ssh2;
+
+import java.io.IOException;
+
+import com.trilead.ssh2.sftp.ErrorCodes;
+
+
+/**
+ * Used in combination with the SFTPv3Client. This exception wraps
+ * error messages sent by the SFTP server.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: SFTPException.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public class SFTPException extends IOException
+{
+ private static final long serialVersionUID = 578654644222421811L;
+
+ private final String sftpErrorMessage;
+ private final int sftpErrorCode;
+
+ private static String constructMessage(String s, int errorCode)
+ {
+ String[] detail = ErrorCodes.getDescription(errorCode);
+
+ if (detail == null)
+ return s + " (UNKNOW SFTP ERROR CODE)";
+
+ return s + " (" + detail[0] + ": " + detail[1] + ")";
+ }
+
+ SFTPException(String msg, int errorCode)
+ {
+ super(constructMessage(msg, errorCode));
+ sftpErrorMessage = msg;
+ sftpErrorCode = errorCode;
+ }
+
+ /**
+ * Get the error message sent by the server. Often, this
+ * message does not help a lot (e.g., "failure").
+ *
+ * @return the plain string as sent by the server.
+ */
+ public String getServerErrorMessage()
+ {
+ return sftpErrorMessage;
+ }
+
+ /**
+ * Get the error code sent by the server.
+ *
+ * @return an error code as defined in the SFTP specs.
+ */
+ public int getServerErrorCode()
+ {
+ return sftpErrorCode;
+ }
+
+ /**
+ * Get the symbolic name of the error code as given in the SFTP specs.
+ *
+ * @return e.g., "SSH_FX_INVALID_FILENAME".
+ */
+ public String getServerErrorCodeSymbol()
+ {
+ String[] detail = ErrorCodes.getDescription(sftpErrorCode);
+
+ if (detail == null)
+ return "UNKNOW SFTP ERROR CODE " + sftpErrorCode;
+
+ return detail[0];
+ }
+
+ /**
+ * Get the description of the error code as given in the SFTP specs.
+ *
+ * @return e.g., "The filename is not valid."
+ */
+ public String getServerErrorCodeVerbose()
+ {
+ String[] detail = ErrorCodes.getDescription(sftpErrorCode);
+
+ if (detail == null)
+ return "The error code " + sftpErrorCode + " is unknown.";
+
+ return detail[1];
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3Client.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3Client.java
new file mode 100644
index 0000000000..498300f6e1
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3Client.java
@@ -0,0 +1,1389 @@
+
+package com.trilead.ssh2;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Vector;
+
+import com.trilead.ssh2.packets.TypesReader;
+import com.trilead.ssh2.packets.TypesWriter;
+import com.trilead.ssh2.sftp.AttribFlags;
+import com.trilead.ssh2.sftp.ErrorCodes;
+import com.trilead.ssh2.sftp.Packet;
+
+
+/**
+ * A SFTPv3Client represents a SFTP (protocol version 3)
+ * client connection tunnelled over a SSH-2 connection. This is a very simple
+ * (synchronous) implementation.
+ *
+ * Basically, most methods in this class map directly to one of
+ * the packet types described in draft-ietf-secsh-filexfer-02.txt.
+ *
+ * Note: this is experimental code.
+ *
+ * Error handling: the methods of this class throw IOExceptions. However, unless
+ * there is catastrophic failure, exceptions of the type {@link SFTPv3Client} will
+ * be thrown (a subclass of IOException). Therefore, you can implement more verbose
+ * behavior by checking if a thrown exception if of this type. If yes, then you
+ * can cast the exception and access detailed information about the failure.
+ *
+ * Notes about file names, directory names and paths, copy-pasted
+ * from the specs:
+ *
+ *
SFTP v3 represents file names as strings. File names are
+ * assumed to use the slash ('/') character as a directory separator.
+ *
File names starting with a slash are "absolute", and are relative to
+ * the root of the file system. Names starting with any other character
+ * are relative to the user's default directory (home directory).
+ *
Servers SHOULD interpret a path name component ".." as referring to
+ * the parent directory, and "." as referring to the current directory.
+ * If the server implementation limits access to certain parts of the
+ * file system, it must be extra careful in parsing file names when
+ * enforcing such restrictions. There have been numerous reported
+ * security bugs where a ".." in a path name has allowed access outside
+ * the intended area.
+ *
An empty path name is valid, and it refers to the user's default
+ * directory (usually the user's home directory).
+ *
+ *
+ * If you are still not tired then please go on and read the comment for
+ * {@link #setCharset(String)}.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: SFTPv3Client.java,v 1.3 2008/04/01 12:38:09 cplattne Exp $
+ */
+public class SFTPv3Client
+{
+ final Connection conn;
+ final Session sess;
+ final PrintStream debug;
+
+ boolean flag_closed = false;
+
+ InputStream is;
+ OutputStream os;
+
+ int protocol_version = 0;
+ HashMap server_extensions = new HashMap();
+
+ int next_request_id = 1000;
+
+ String charsetName = null;
+
+ /**
+ * Create a SFTP v3 client.
+ *
+ * @param conn The underlying SSH-2 connection to be used.
+ * @param debug
+ * @throws IOException
+ *
+ * @deprecated this constructor (debug version) will disappear in the future,
+ * use {@link #SFTPv3Client(Connection)} instead.
+ */
+ @Deprecated
+ public SFTPv3Client(Connection conn, PrintStream debug) throws IOException
+ {
+ if (conn == null)
+ throw new IllegalArgumentException("Cannot accept null argument!");
+
+ this.conn = conn;
+ this.debug = debug;
+
+ if (debug != null)
+ debug.println("Opening session and starting SFTP subsystem.");
+
+ sess = conn.openSession();
+ sess.startSubSystem("sftp");
+
+ is = sess.getStdout();
+ os = new BufferedOutputStream(sess.getStdin(), 2048);
+
+ if ((is == null) || (os == null))
+ throw new IOException("There is a problem with the streams of the underlying channel.");
+
+ init();
+ }
+
+ /**
+ * Create a SFTP v3 client.
+ *
+ * @param conn The underlying SSH-2 connection to be used.
+ * @throws IOException
+ */
+ public SFTPv3Client(Connection conn) throws IOException
+ {
+ this(conn, null);
+ }
+
+ /**
+ * Set the charset used to convert between Java Unicode Strings and byte encodings
+ * used by the server for paths and file names. Unfortunately, the SFTP v3 draft
+ * says NOTHING about such conversions (well, with the exception of error messages
+ * which have to be in UTF-8). Newer drafts specify to use UTF-8 for file names
+ * (if I remember correctly). However, a quick test using OpenSSH serving a EXT-3
+ * filesystem has shown that UTF-8 seems to be a bad choice for SFTP v3 (tested with
+ * filenames containing german umlauts). "windows-1252" seems to work better for Europe.
+ * Luckily, "windows-1252" is the platform default in my case =).
+ *
+ * If you don't set anything, then the platform default will be used (this is the default
+ * behavior).
+ *
+ * @see #getCharset()
+ *
+ * @param charset the name of the charset to be used or null to use the platform's
+ * default encoding.
+ * @throws IOException
+ */
+ public void setCharset(String charset) throws IOException
+ {
+ if (charset == null)
+ {
+ charsetName = charset;
+ return;
+ }
+
+ try
+ {
+ Charset.forName(charset);
+ }
+ catch (Exception e)
+ {
+ throw new IOException("This charset is not supported", e);
+ }
+ charsetName = charset;
+ }
+
+ /**
+ * The currently used charset for filename encoding/decoding.
+ *
+ * @see #setCharset(String)
+ *
+ * @return The name of the charset (null if the platform's default charset is being used)
+ */
+ public String getCharset()
+ {
+ return charsetName;
+ }
+
+ private final void checkHandleValidAndOpen(SFTPv3FileHandle handle) throws IOException
+ {
+ if (handle.client != this)
+ throw new IOException("The file handle was created with another SFTPv3FileHandle instance.");
+
+ if (handle.isClosed)
+ throw new IOException("The file handle is closed.");
+ }
+
+ private final void sendMessage(int type, int requestId, byte[] msg, int off, int len) throws IOException
+ {
+ int msglen = len + 1;
+
+ if (type != Packet.SSH_FXP_INIT)
+ msglen += 4;
+
+ os.write(msglen >> 24);
+ os.write(msglen >> 16);
+ os.write(msglen >> 8);
+ os.write(msglen);
+ os.write(type);
+
+ if (type != Packet.SSH_FXP_INIT)
+ {
+ os.write(requestId >> 24);
+ os.write(requestId >> 16);
+ os.write(requestId >> 8);
+ os.write(requestId);
+ }
+
+ os.write(msg, off, len);
+ os.flush();
+ }
+
+ private final void sendMessage(int type, int requestId, byte[] msg) throws IOException
+ {
+ sendMessage(type, requestId, msg, 0, msg.length);
+ }
+
+ private final void readBytes(byte[] buff, int pos, int len) throws IOException
+ {
+ while (len > 0)
+ {
+ int count = is.read(buff, pos, len);
+ if (count < 0)
+ throw new IOException("Unexpected end of sftp stream.");
+ if ((count == 0) || (count > len))
+ throw new IOException("Underlying stream implementation is bogus!");
+ len -= count;
+ pos += count;
+ }
+ }
+
+ /**
+ * Read a message and guarantee that the contents is not larger than
+ * maxlen bytes.
+ *
+ * Note: receiveMessage(34000) actually means that the message may be up to 34004
+ * bytes (the length attribute preceeding the contents is 4 bytes).
+ *
+ * @param maxlen
+ * @return the message contents
+ * @throws IOException
+ */
+ private final byte[] receiveMessage(int maxlen) throws IOException
+ {
+ byte[] msglen = new byte[4];
+
+ readBytes(msglen, 0, 4);
+
+ int len = (((msglen[0] & 0xff) << 24) | ((msglen[1] & 0xff) << 16) | ((msglen[2] & 0xff) << 8) | (msglen[3] & 0xff));
+
+ if ((len > maxlen) || (len <= 0))
+ throw new IOException("Illegal sftp packet len: " + len);
+
+ byte[] msg = new byte[len];
+
+ readBytes(msg, 0, len);
+
+ return msg;
+ }
+
+ private final int generateNextRequestID()
+ {
+ synchronized (this)
+ {
+ return next_request_id++;
+ }
+ }
+
+ private final void closeHandle(byte[] handle) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(handle, 0, handle.length);
+
+ sendMessage(Packet.SSH_FXP_CLOSE, req_id, tw.getBytes());
+
+ expectStatusOKMessage(req_id);
+ }
+
+ private SFTPv3FileAttributes readAttrs(TypesReader tr) throws IOException
+ {
+ /*
+ * uint32 flags
+ * uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
+ * uint32 uid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
+ * uint32 gid present only if flag SSH_FILEXFER_ATTR_V3_UIDGID
+ * uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
+ * uint32 atime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
+ * uint32 mtime present only if flag SSH_FILEXFER_ATTR_V3_ACMODTIME
+ * uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
+ * string extended_type
+ * string extended_data
+ * ... more extended data (extended_type - extended_data pairs),
+ * so that number of pairs equals extended_count
+ */
+
+ SFTPv3FileAttributes fa = new SFTPv3FileAttributes();
+
+ int flags = tr.readUINT32();
+
+ if ((flags & AttribFlags.SSH_FILEXFER_ATTR_SIZE) != 0)
+ {
+ if (debug != null)
+ debug.println("SSH_FILEXFER_ATTR_SIZE");
+ fa.size = new Long(tr.readUINT64());
+ }
+
+ if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID) != 0)
+ {
+ if (debug != null)
+ debug.println("SSH_FILEXFER_ATTR_V3_UIDGID");
+ fa.uid = new Integer(tr.readUINT32());
+ fa.gid = new Integer(tr.readUINT32());
+ }
+
+ if ((flags & AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS) != 0)
+ {
+ if (debug != null)
+ debug.println("SSH_FILEXFER_ATTR_PERMISSIONS");
+ fa.permissions = new Integer(tr.readUINT32());
+ }
+
+ if ((flags & AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME) != 0)
+ {
+ if (debug != null)
+ debug.println("SSH_FILEXFER_ATTR_V3_ACMODTIME");
+ fa.atime = new Long(((long) tr.readUINT32()) & 0xffffffffl);
+ fa.mtime = new Long(((long) tr.readUINT32()) & 0xffffffffl);
+
+ }
+
+ if ((flags & AttribFlags.SSH_FILEXFER_ATTR_EXTENDED) != 0)
+ {
+ int count = tr.readUINT32();
+
+ if (debug != null)
+ debug.println("SSH_FILEXFER_ATTR_EXTENDED (" + count + ")");
+
+ /* Read it anyway to detect corrupt packets */
+
+ while (count > 0)
+ {
+ tr.readByteString();
+ tr.readByteString();
+ count--;
+ }
+ }
+
+ return fa;
+ }
+
+ /**
+ * Retrieve the file attributes of an open file.
+ *
+ * @param handle a SFTPv3FileHandle handle.
+ * @return a SFTPv3FileAttributes object.
+ * @throws IOException
+ */
+ public SFTPv3FileAttributes fstat(SFTPv3FileHandle handle) throws IOException
+ {
+ checkHandleValidAndOpen(handle);
+
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_FSTAT...");
+ debug.flush();
+ }
+
+ sendMessage(Packet.SSH_FXP_FSTAT, req_id, tw.getBytes());
+
+ byte[] resp = receiveMessage(34000);
+
+ if (debug != null)
+ {
+ debug.println("Got REPLY.");
+ debug.flush();
+ }
+
+ TypesReader tr = new TypesReader(resp);
+
+ int t = tr.readByte();
+
+ int rep_id = tr.readUINT32();
+ if (rep_id != req_id)
+ throw new IOException("The server sent an invalid id field.");
+
+ if (t == Packet.SSH_FXP_ATTRS)
+ {
+ return readAttrs(tr);
+ }
+
+ if (t != Packet.SSH_FXP_STATUS)
+ throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
+
+ int errorCode = tr.readUINT32();
+
+ throw new SFTPException(tr.readString(), errorCode);
+ }
+
+ private SFTPv3FileAttributes statBoth(String path, int statMethod) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(path, charsetName);
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_STAT/SSH_FXP_LSTAT...");
+ debug.flush();
+ }
+
+ sendMessage(statMethod, req_id, tw.getBytes());
+
+ byte[] resp = receiveMessage(34000);
+
+ if (debug != null)
+ {
+ debug.println("Got REPLY.");
+ debug.flush();
+ }
+
+ TypesReader tr = new TypesReader(resp);
+
+ int t = tr.readByte();
+
+ int rep_id = tr.readUINT32();
+ if (rep_id != req_id)
+ throw new IOException("The server sent an invalid id field.");
+
+ if (t == Packet.SSH_FXP_ATTRS)
+ {
+ return readAttrs(tr);
+ }
+
+ if (t != Packet.SSH_FXP_STATUS)
+ throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
+
+ int errorCode = tr.readUINT32();
+
+ throw new SFTPException(tr.readString(), errorCode);
+ }
+
+ /**
+ * Retrieve the file attributes of a file. This method
+ * follows symbolic links on the server.
+ *
+ * @see #lstat(String)
+ *
+ * @param path See the {@link SFTPv3Client comment} for the class for more details.
+ * @return a SFTPv3FileAttributes object.
+ * @throws IOException
+ */
+ public SFTPv3FileAttributes stat(String path) throws IOException
+ {
+ return statBoth(path, Packet.SSH_FXP_STAT);
+ }
+
+ /**
+ * Retrieve the file attributes of a file. This method
+ * does NOT follow symbolic links on the server.
+ *
+ * @see #stat(String)
+ *
+ * @param path See the {@link SFTPv3Client comment} for the class for more details.
+ * @return a SFTPv3FileAttributes object.
+ * @throws IOException
+ */
+ public SFTPv3FileAttributes lstat(String path) throws IOException
+ {
+ return statBoth(path, Packet.SSH_FXP_LSTAT);
+ }
+
+ /**
+ * Read the target of a symbolic link.
+ *
+ * @param path See the {@link SFTPv3Client comment} for the class for more details.
+ * @return The target of the link.
+ * @throws IOException
+ */
+ public String readLink(String path) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(path, charsetName);
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_READLINK...");
+ debug.flush();
+ }
+
+ sendMessage(Packet.SSH_FXP_READLINK, req_id, tw.getBytes());
+
+ byte[] resp = receiveMessage(34000);
+
+ if (debug != null)
+ {
+ debug.println("Got REPLY.");
+ debug.flush();
+ }
+
+ TypesReader tr = new TypesReader(resp);
+
+ int t = tr.readByte();
+
+ int rep_id = tr.readUINT32();
+ if (rep_id != req_id)
+ throw new IOException("The server sent an invalid id field.");
+
+ if (t == Packet.SSH_FXP_NAME)
+ {
+ int count = tr.readUINT32();
+
+ if (count != 1)
+ throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
+
+ return tr.readString(charsetName);
+ }
+
+ if (t != Packet.SSH_FXP_STATUS)
+ throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
+
+ int errorCode = tr.readUINT32();
+
+ throw new SFTPException(tr.readString(), errorCode);
+ }
+
+ private void expectStatusOKMessage(int id) throws IOException
+ {
+ byte[] resp = receiveMessage(34000);
+
+ if (debug != null)
+ {
+ debug.println("Got REPLY.");
+ debug.flush();
+ }
+
+ TypesReader tr = new TypesReader(resp);
+
+ int t = tr.readByte();
+
+ int rep_id = tr.readUINT32();
+ if (rep_id != id)
+ throw new IOException("The server sent an invalid id field.");
+
+ if (t != Packet.SSH_FXP_STATUS)
+ throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
+
+ int errorCode = tr.readUINT32();
+
+ if (errorCode == ErrorCodes.SSH_FX_OK)
+ return;
+
+ throw new SFTPException(tr.readString(), errorCode);
+ }
+
+ /**
+ * Modify the attributes of a file. Used for operations such as changing
+ * the ownership, permissions or access times, as well as for truncating a file.
+ *
+ * @param path See the {@link SFTPv3Client comment} for the class for more details.
+ * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
+ * made to the attributes of the file. Empty fields will be ignored.
+ * @throws IOException
+ */
+ public void setstat(String path, SFTPv3FileAttributes attr) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(path, charsetName);
+ tw.writeBytes(createAttrs(attr));
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_SETSTAT...");
+ debug.flush();
+ }
+
+ sendMessage(Packet.SSH_FXP_SETSTAT, req_id, tw.getBytes());
+
+ expectStatusOKMessage(req_id);
+ }
+
+ /**
+ * Modify the attributes of a file. Used for operations such as changing
+ * the ownership, permissions or access times, as well as for truncating a file.
+ *
+ * @param handle a SFTPv3FileHandle handle
+ * @param attr A SFTPv3FileAttributes object. Specifies the modifications to be
+ * made to the attributes of the file. Empty fields will be ignored.
+ * @throws IOException
+ */
+ public void fsetstat(SFTPv3FileHandle handle, SFTPv3FileAttributes attr) throws IOException
+ {
+ checkHandleValidAndOpen(handle);
+
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
+ tw.writeBytes(createAttrs(attr));
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_FSETSTAT...");
+ debug.flush();
+ }
+
+ sendMessage(Packet.SSH_FXP_FSETSTAT, req_id, tw.getBytes());
+
+ expectStatusOKMessage(req_id);
+ }
+
+ /**
+ * Create a symbolic link on the server. Creates a link "src" that points
+ * to "target".
+ *
+ * @param src See the {@link SFTPv3Client comment} for the class for more details.
+ * @param target See the {@link SFTPv3Client comment} for the class for more details.
+ * @throws IOException
+ */
+ public void createSymlink(String src, String target) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ /* Either I am too stupid to understand the SFTP draft
+ * or the OpenSSH guys changed the semantics of src and target.
+ */
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(target, charsetName);
+ tw.writeString(src, charsetName);
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_SYMLINK...");
+ debug.flush();
+ }
+
+ sendMessage(Packet.SSH_FXP_SYMLINK, req_id, tw.getBytes());
+
+ expectStatusOKMessage(req_id);
+ }
+
+ /**
+ * Have the server canonicalize any given path name to an absolute path.
+ * This is useful for converting path names containing ".." components or
+ * relative pathnames without a leading slash into absolute paths.
+ *
+ * @param path See the {@link SFTPv3Client comment} for the class for more details.
+ * @return An absolute path.
+ * @throws IOException
+ */
+ public String canonicalPath(String path) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(path, charsetName);
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_REALPATH...");
+ debug.flush();
+ }
+
+ sendMessage(Packet.SSH_FXP_REALPATH, req_id, tw.getBytes());
+
+ byte[] resp = receiveMessage(34000);
+
+ if (debug != null)
+ {
+ debug.println("Got REPLY.");
+ debug.flush();
+ }
+
+ TypesReader tr = new TypesReader(resp);
+
+ int t = tr.readByte();
+
+ int rep_id = tr.readUINT32();
+ if (rep_id != req_id)
+ throw new IOException("The server sent an invalid id field.");
+
+ if (t == Packet.SSH_FXP_NAME)
+ {
+ int count = tr.readUINT32();
+
+ if (count != 1)
+ throw new IOException("The server sent an invalid SSH_FXP_NAME packet.");
+
+ return tr.readString(charsetName);
+ }
+
+ if (t != Packet.SSH_FXP_STATUS)
+ throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
+
+ int errorCode = tr.readUINT32();
+
+ throw new SFTPException(tr.readString(), errorCode);
+ }
+
+ private final Vector scanDirectory(byte[] handle) throws IOException
+ {
+ Vector files = new Vector();
+
+ while (true)
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(handle, 0, handle.length);
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_READDIR...");
+ debug.flush();
+ }
+
+ sendMessage(Packet.SSH_FXP_READDIR, req_id, tw.getBytes());
+
+ /* Some servers send here a packet with size > 34000 */
+ /* To whom it may concern: please learn to read the specs. */
+
+ byte[] resp = receiveMessage(65536);
+
+ if (debug != null)
+ {
+ debug.println("Got REPLY.");
+ debug.flush();
+ }
+
+ TypesReader tr = new TypesReader(resp);
+
+ int t = tr.readByte();
+
+ int rep_id = tr.readUINT32();
+ if (rep_id != req_id)
+ throw new IOException("The server sent an invalid id field.");
+
+ if (t == Packet.SSH_FXP_NAME)
+ {
+ int count = tr.readUINT32();
+
+ if (debug != null)
+ debug.println("Parsing " + count + " name entries...");
+
+ while (count > 0)
+ {
+ SFTPv3DirectoryEntry dirEnt = new SFTPv3DirectoryEntry();
+
+ dirEnt.filename = tr.readString(charsetName);
+ dirEnt.longEntry = tr.readString(charsetName);
+
+ dirEnt.attributes = readAttrs(tr);
+ files.addElement(dirEnt);
+
+ if (debug != null)
+ debug.println("File: '" + dirEnt.filename + "'");
+ count--;
+ }
+ continue;
+ }
+
+ if (t != Packet.SSH_FXP_STATUS)
+ throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
+
+ int errorCode = tr.readUINT32();
+
+ if (errorCode == ErrorCodes.SSH_FX_EOF)
+ return files;
+
+ throw new SFTPException(tr.readString(), errorCode);
+ }
+ }
+
+ private final byte[] openDirectory(String path) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(path, charsetName);
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_OPENDIR...");
+ debug.flush();
+ }
+
+ sendMessage(Packet.SSH_FXP_OPENDIR, req_id, tw.getBytes());
+
+ byte[] resp = receiveMessage(34000);
+
+ TypesReader tr = new TypesReader(resp);
+
+ int t = tr.readByte();
+
+ int rep_id = tr.readUINT32();
+ if (rep_id != req_id)
+ throw new IOException("The server sent an invalid id field.");
+
+ if (t == Packet.SSH_FXP_HANDLE)
+ {
+ if (debug != null)
+ {
+ debug.println("Got SSH_FXP_HANDLE.");
+ debug.flush();
+ }
+
+ byte[] handle = tr.readByteString();
+ return handle;
+ }
+
+ if (t != Packet.SSH_FXP_STATUS)
+ throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
+
+ int errorCode = tr.readUINT32();
+ String errorMessage = tr.readString();
+
+ throw new SFTPException(errorMessage, errorCode);
+ }
+
+ private final String expandString(byte[] b, int off, int len)
+ {
+ StringBuffer sb = new StringBuffer();
+
+ for (int i = 0; i < len; i++)
+ {
+ int c = b[off + i] & 0xff;
+
+ if ((c >= 32) && (c <= 126))
+ {
+ sb.append((char) c);
+ }
+ else
+ {
+ sb.append("{0x" + Integer.toHexString(c) + "}");
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private void init() throws IOException
+ {
+ /* Send SSH_FXP_INIT (version 3) */
+
+ final int client_version = 3;
+
+ if (debug != null)
+ debug.println("Sending SSH_FXP_INIT (" + client_version + ")...");
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeUINT32(client_version);
+ sendMessage(Packet.SSH_FXP_INIT, 0, tw.getBytes());
+
+ /* Receive SSH_FXP_VERSION */
+
+ if (debug != null)
+ debug.println("Waiting for SSH_FXP_VERSION...");
+
+ TypesReader tr = new TypesReader(receiveMessage(34000)); /* Should be enough for any reasonable server */
+
+ int type = tr.readByte();
+
+ if (type != Packet.SSH_FXP_VERSION)
+ {
+ throw new IOException("The server did not send a SSH_FXP_VERSION packet (got " + type + ")");
+ }
+
+ protocol_version = tr.readUINT32();
+
+ if (debug != null)
+ debug.println("SSH_FXP_VERSION: protocol_version = " + protocol_version);
+
+ if (protocol_version != 3)
+ throw new IOException("Server version " + protocol_version + " is currently not supported");
+
+ /* Read and save extensions (if any) for later use */
+
+ while (tr.remain() != 0)
+ {
+ String name = tr.readString();
+ byte[] value = tr.readByteString();
+ server_extensions.put(name, value);
+
+ if (debug != null)
+ debug.println("SSH_FXP_VERSION: extension: " + name + " = '" + expandString(value, 0, value.length)
+ + "'");
+ }
+ }
+
+ /**
+ * Returns the negotiated SFTP protocol version between the client and the server.
+ *
+ * @return SFTP protocol version, i.e., "3".
+ *
+ */
+ public int getProtocolVersion()
+ {
+ return protocol_version;
+ }
+
+ /**
+ * Close this SFTP session. NEVER forget to call this method to free up
+ * resources - even if you got an exception from one of the other methods.
+ * Sometimes these other methods may throw an exception, saying that the
+ * underlying channel is closed (this can happen, e.g., if the other server
+ * sent a close message.) However, as long as you have not called the
+ * close() method, you are likely wasting resources.
+ *
+ */
+ public void close()
+ {
+ sess.close();
+ }
+
+ /**
+ * List the contents of a directory.
+ *
+ * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
+ * @return A Vector containing {@link SFTPv3DirectoryEntry} objects.
+ * @throws IOException
+ */
+ public Vector ls(String dirName) throws IOException
+ {
+ byte[] handle = openDirectory(dirName);
+ Vector result = scanDirectory(handle);
+ closeHandle(handle);
+ return result;
+ }
+
+ /**
+ * Create a new directory.
+ *
+ * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
+ * @param posixPermissions the permissions for this directory, e.g., "0700" (remember that
+ * this is octal noation). The server will likely apply a umask.
+ *
+ * @throws IOException
+ */
+ public void mkdir(String dirName, int posixPermissions) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(dirName, charsetName);
+ tw.writeUINT32(AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS);
+ tw.writeUINT32(posixPermissions);
+
+ sendMessage(Packet.SSH_FXP_MKDIR, req_id, tw.getBytes());
+
+ expectStatusOKMessage(req_id);
+ }
+
+ /**
+ * Remove a file.
+ *
+ * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
+ * @throws IOException
+ */
+ public void rm(String fileName) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(fileName, charsetName);
+
+ sendMessage(Packet.SSH_FXP_REMOVE, req_id, tw.getBytes());
+
+ expectStatusOKMessage(req_id);
+ }
+
+ /**
+ * Remove an empty directory.
+ *
+ * @param dirName See the {@link SFTPv3Client comment} for the class for more details.
+ * @throws IOException
+ */
+ public void rmdir(String dirName) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(dirName, charsetName);
+
+ sendMessage(Packet.SSH_FXP_RMDIR, req_id, tw.getBytes());
+
+ expectStatusOKMessage(req_id);
+ }
+
+ /**
+ * Move a file or directory.
+ *
+ * @param oldPath See the {@link SFTPv3Client comment} for the class for more details.
+ * @param newPath See the {@link SFTPv3Client comment} for the class for more details.
+ * @throws IOException
+ */
+ public void mv(String oldPath, String newPath) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(oldPath, charsetName);
+ tw.writeString(newPath, charsetName);
+
+ sendMessage(Packet.SSH_FXP_RENAME, req_id, tw.getBytes());
+
+ expectStatusOKMessage(req_id);
+ }
+
+ /**
+ * Open a file for reading.
+ *
+ * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
+ * @return a SFTPv3FileHandle handle
+ * @throws IOException
+ */
+ public SFTPv3FileHandle openFileRO(String fileName) throws IOException
+ {
+ return openFile(fileName, 0x00000001, null); // SSH_FXF_READ
+ }
+
+ /**
+ * Open a file for reading and writing.
+ *
+ * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
+ * @return a SFTPv3FileHandle handle
+ * @throws IOException
+ */
+ public SFTPv3FileHandle openFileRW(String fileName) throws IOException
+ {
+ return openFile(fileName, 0x00000003, null); // SSH_FXF_READ | SSH_FXF_WRITE
+ }
+
+ // Append is broken (already in the specification, because there is no way to
+ // send a write operation (what offset to use??))
+ // public SFTPv3FileHandle openFileRWAppend(String fileName) throws IOException
+ // {
+ // return openFile(fileName, 0x00000007, null); // SSH_FXF_READ | SSH_FXF_WRITE | SSH_FXF_APPEND
+ // }
+
+ /**
+ * Create a file and open it for reading and writing.
+ * Same as {@link #createFile(String, SFTPv3FileAttributes) createFile(fileName, null)}.
+ *
+ * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
+ * @return a SFTPv3FileHandle handle
+ * @throws IOException
+ */
+ public SFTPv3FileHandle createFile(String fileName) throws IOException
+ {
+ return createFile(fileName, null);
+ }
+
+ /**
+ * Create a file and open it for reading and writing.
+ * You can specify the default attributes of the file (the server may or may
+ * not respect your wishes).
+ *
+ * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
+ * @param attr may be null to use server defaults. Probably only
+ * the uid, gid and permissions
+ * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
+ * structure make sense. You need only to set those fields where you want
+ * to override the server's defaults.
+ * @return a SFTPv3FileHandle handle
+ * @throws IOException
+ */
+ public SFTPv3FileHandle createFile(String fileName, SFTPv3FileAttributes attr) throws IOException
+ {
+ return openFile(fileName, 0x00000008 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_READ | SSH_FXF_WRITE
+ }
+
+ /**
+ * Create a file (truncate it if it already exists) and open it for reading and writing.
+ * Same as {@link #createFileTruncate(String, SFTPv3FileAttributes) createFileTruncate(fileName, null)}.
+ *
+ * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
+ * @return a SFTPv3FileHandle handle
+ * @throws IOException
+ */
+ public SFTPv3FileHandle createFileTruncate(String fileName) throws IOException
+ {
+ return createFileTruncate(fileName, null);
+ }
+
+ /**
+ * reate a file (truncate it if it already exists) and open it for reading and writing.
+ * You can specify the default attributes of the file (the server may or may
+ * not respect your wishes).
+ *
+ * @param fileName See the {@link SFTPv3Client comment} for the class for more details.
+ * @param attr may be null to use server defaults. Probably only
+ * the uid, gid and permissions
+ * (remember the server may apply a umask) entries of the {@link SFTPv3FileHandle}
+ * structure make sense. You need only to set those fields where you want
+ * to override the server's defaults.
+ * @return a SFTPv3FileHandle handle
+ * @throws IOException
+ */
+ public SFTPv3FileHandle createFileTruncate(String fileName, SFTPv3FileAttributes attr) throws IOException
+ {
+ return openFile(fileName, 0x00000018 | 0x00000003, attr); // SSH_FXF_CREAT | SSH_FXF_TRUNC | SSH_FXF_READ | SSH_FXF_WRITE
+ }
+
+ private byte[] createAttrs(SFTPv3FileAttributes attr)
+ {
+ TypesWriter tw = new TypesWriter();
+
+ int attrFlags = 0;
+
+ if (attr == null)
+ {
+ tw.writeUINT32(0);
+ }
+ else
+ {
+ if (attr.size != null)
+ attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_SIZE;
+
+ if ((attr.uid != null) && (attr.gid != null))
+ attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_UIDGID;
+
+ if (attr.permissions != null)
+ attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_PERMISSIONS;
+
+ if ((attr.atime != null) && (attr.mtime != null))
+ attrFlags = attrFlags | AttribFlags.SSH_FILEXFER_ATTR_V3_ACMODTIME;
+
+ tw.writeUINT32(attrFlags);
+
+ if (attr.size != null)
+ tw.writeUINT64(attr.size.longValue());
+
+ if ((attr.uid != null) && (attr.gid != null))
+ {
+ tw.writeUINT32(attr.uid.intValue());
+ tw.writeUINT32(attr.gid.intValue());
+ }
+
+ if (attr.permissions != null)
+ tw.writeUINT32(attr.permissions.intValue());
+
+ if ((attr.atime != null) && (attr.mtime != null))
+ {
+ tw.writeUINT32(attr.atime.intValue());
+ tw.writeUINT32(attr.mtime.intValue());
+ }
+ }
+
+ return tw.getBytes();
+ }
+
+ private SFTPv3FileHandle openFile(String fileName, int flags, SFTPv3FileAttributes attr) throws IOException
+ {
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(fileName, charsetName);
+ tw.writeUINT32(flags);
+ tw.writeBytes(createAttrs(attr));
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_OPEN...");
+ debug.flush();
+ }
+
+ sendMessage(Packet.SSH_FXP_OPEN, req_id, tw.getBytes());
+
+ byte[] resp = receiveMessage(34000);
+
+ TypesReader tr = new TypesReader(resp);
+
+ int t = tr.readByte();
+
+ int rep_id = tr.readUINT32();
+ if (rep_id != req_id)
+ throw new IOException("The server sent an invalid id field.");
+
+ if (t == Packet.SSH_FXP_HANDLE)
+ {
+ if (debug != null)
+ {
+ debug.println("Got SSH_FXP_HANDLE.");
+ debug.flush();
+ }
+
+ return new SFTPv3FileHandle(this, tr.readByteString());
+ }
+
+ if (t != Packet.SSH_FXP_STATUS)
+ throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
+
+ int errorCode = tr.readUINT32();
+ String errorMessage = tr.readString();
+
+ throw new SFTPException(errorMessage, errorCode);
+ }
+
+ /**
+ * Read bytes from a file. No more than 32768 bytes may be read at once.
+ * Be aware that the semantics of read() are different than for Java streams.
+ *
+ *
+ *
The server will read as many bytes as it can from the file (up to len),
+ * and return them.
+ *
If EOF is encountered before reading any data, -1 is returned.
+ *
If an error occurs, an exception is thrown.
+ *
For normal disk files, it is guaranteed that the server will return the specified
+ * number of bytes, or up to end of file. For, e.g., device files this may return
+ * fewer bytes than requested.
+ *
+ *
+ * @param handle a SFTPv3FileHandle handle
+ * @param fileOffset offset (in bytes) in the file
+ * @param dst the destination byte array
+ * @param dstoff offset in the destination byte array
+ * @param len how many bytes to read, 0 < len <= 32768 bytes
+ * @return the number of bytes that could be read, may be less than requested if
+ * the end of the file is reached, -1 is returned in case of EOF
+ * @throws IOException
+ */
+ public int read(SFTPv3FileHandle handle, long fileOffset, byte[] dst, int dstoff, int len) throws IOException
+ {
+ checkHandleValidAndOpen(handle);
+
+ if ((len > 32768) || (len <= 0))
+ throw new IllegalArgumentException("invalid len argument");
+
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
+ tw.writeUINT64(fileOffset);
+ tw.writeUINT32(len);
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_READ...");
+ debug.flush();
+ }
+
+ sendMessage(Packet.SSH_FXP_READ, req_id, tw.getBytes());
+
+ byte[] resp = receiveMessage(34000);
+
+ TypesReader tr = new TypesReader(resp);
+
+ int t = tr.readByte();
+
+ int rep_id = tr.readUINT32();
+ if (rep_id != req_id)
+ throw new IOException("The server sent an invalid id field.");
+
+ if (t == Packet.SSH_FXP_DATA)
+ {
+ if (debug != null)
+ {
+ debug.println("Got SSH_FXP_DATA...");
+ debug.flush();
+ }
+
+ int readLen = tr.readUINT32();
+
+ if ((readLen < 0) || (readLen > len))
+ throw new IOException("The server sent an invalid length field.");
+
+ tr.readBytes(dst, dstoff, readLen);
+
+ return readLen;
+ }
+
+ if (t != Packet.SSH_FXP_STATUS)
+ throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
+
+ int errorCode = tr.readUINT32();
+
+ if (errorCode == ErrorCodes.SSH_FX_EOF)
+ {
+ if (debug != null)
+ {
+ debug.println("Got SSH_FX_EOF.");
+ debug.flush();
+ }
+
+ return -1;
+ }
+
+ String errorMessage = tr.readString();
+
+ throw new SFTPException(errorMessage, errorCode);
+ }
+
+ /**
+ * Write bytes to a file. If len > 32768, then the write operation will
+ * be split into multiple writes.
+ *
+ * @param handle a SFTPv3FileHandle handle.
+ * @param fileOffset offset (in bytes) in the file.
+ * @param src the source byte array.
+ * @param srcoff offset in the source byte array.
+ * @param len how many bytes to write.
+ * @throws IOException
+ */
+ public void write(SFTPv3FileHandle handle, long fileOffset, byte[] src, int srcoff, int len) throws IOException
+ {
+ checkHandleValidAndOpen(handle);
+
+ while (len > 0)
+ {
+ int writeRequestLen = len;
+
+ if (writeRequestLen > 32768)
+ writeRequestLen = 32768;
+
+ int req_id = generateNextRequestID();
+
+ TypesWriter tw = new TypesWriter();
+ tw.writeString(handle.fileHandle, 0, handle.fileHandle.length);
+ tw.writeUINT64(fileOffset);
+ tw.writeString(src, srcoff, writeRequestLen);
+
+ if (debug != null)
+ {
+ debug.println("Sending SSH_FXP_WRITE...");
+ debug.flush();
+ }
+
+ sendMessage(Packet.SSH_FXP_WRITE, req_id, tw.getBytes());
+
+ fileOffset += writeRequestLen;
+
+ srcoff += writeRequestLen;
+ len -= writeRequestLen;
+
+ byte[] resp = receiveMessage(34000);
+
+ TypesReader tr = new TypesReader(resp);
+
+ int t = tr.readByte();
+
+ int rep_id = tr.readUINT32();
+ if (rep_id != req_id)
+ throw new IOException("The server sent an invalid id field.");
+
+ if (t != Packet.SSH_FXP_STATUS)
+ throw new IOException("The SFTP server sent an unexpected packet type (" + t + ")");
+
+ int errorCode = tr.readUINT32();
+
+ if (errorCode == ErrorCodes.SSH_FX_OK)
+ continue;
+
+ String errorMessage = tr.readString();
+
+ throw new SFTPException(errorMessage, errorCode);
+ }
+ }
+
+ /**
+ * Close a file.
+ *
+ * @param handle a SFTPv3FileHandle handle
+ * @throws IOException
+ */
+ public void closeFile(SFTPv3FileHandle handle) throws IOException
+ {
+ if (handle == null)
+ throw new IllegalArgumentException("the handle argument may not be null");
+
+ try
+ {
+ if (!handle.isClosed)
+ {
+ closeHandle(handle.fileHandle);
+ }
+ }
+ finally
+ {
+ handle.isClosed = true;
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3DirectoryEntry.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3DirectoryEntry.java
new file mode 100644
index 0000000000..547c7d2e0d
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3DirectoryEntry.java
@@ -0,0 +1,38 @@
+
+package com.trilead.ssh2;
+
+/**
+ * A SFTPv3DirectoryEntry as returned by {@link SFTPv3Client#ls(String)}.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: SFTPv3DirectoryEntry.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public class SFTPv3DirectoryEntry
+{
+ /**
+ * A relative name within the directory, without any path components.
+ */
+ public String filename;
+
+ /**
+ * An expanded format for the file name, similar to what is returned by
+ * "ls -l" on Un*x systems.
+ *
+ * The format of this field is unspecified by the SFTP v3 protocol.
+ * It MUST be suitable for use in the output of a directory listing
+ * command (in fact, the recommended operation for a directory listing
+ * command is to simply display this data). However, clients SHOULD NOT
+ * attempt to parse the longname field for file attributes; they SHOULD
+ * use the attrs field instead.
+ *
+ * The recommended format for the longname field is as follows:
+ * -rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer
+ */
+ public String longEntry;
+
+ /**
+ * The attributes of this entry.
+ */
+ public SFTPv3FileAttributes attributes;
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3FileAttributes.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3FileAttributes.java
new file mode 100644
index 0000000000..8fc1cc5bf7
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3FileAttributes.java
@@ -0,0 +1,145 @@
+
+package com.trilead.ssh2;
+
+/**
+ * A SFTPv3FileAttributes object represents detail information
+ * about a file on the server. Not all fields may/must be present.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: SFTPv3FileAttributes.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
+ */
+
+public class SFTPv3FileAttributes
+{
+ /**
+ * The SIZE attribute. NULL if not present.
+ */
+ public Long size = null;
+
+ /**
+ * The UID attribute. NULL if not present.
+ */
+ public Integer uid = null;
+
+ /**
+ * The GID attribute. NULL if not present.
+ */
+ public Integer gid = null;
+
+ /**
+ * The POSIX permissions. NULL if not present.
+ *
+ * Here is a list:
+ *
+ *
Note: these numbers are all OCTAL.
+ *
+ * S_IFMT 0170000 bitmask for the file type bitfields
+ * S_IFSOCK 0140000 socket
+ * S_IFLNK 0120000 symbolic link
+ * S_IFREG 0100000 regular file
+ * S_IFBLK 0060000 block device
+ * S_IFDIR 0040000 directory
+ * S_IFCHR 0020000 character device
+ * S_IFIFO 0010000 fifo
+ * S_ISUID 0004000 set UID bit
+ * S_ISGID 0002000 set GID bit
+ * S_ISVTX 0001000 sticky bit
+ *
+ * S_IRWXU 00700 mask for file owner permissions
+ * S_IRUSR 00400 owner has read permission
+ * S_IWUSR 00200 owner has write permission
+ * S_IXUSR 00100 owner has execute permission
+ * S_IRWXG 00070 mask for group permissions
+ * S_IRGRP 00040 group has read permission
+ * S_IWGRP 00020 group has write permission
+ * S_IXGRP 00010 group has execute permission
+ * S_IRWXO 00007 mask for permissions for others (not in group)
+ * S_IROTH 00004 others have read permission
+ * S_IWOTH 00002 others have write permisson
+ * S_IXOTH 00001 others have execute permission
+ *
+ */
+ public Integer permissions = null;
+
+ /**
+ * The ATIME attribute. Represented as seconds from Jan 1, 1970 in UTC.
+ * NULL if not present.
+ */
+ public Long atime = null;
+
+ /**
+ * The MTIME attribute. Represented as seconds from Jan 1, 1970 in UTC.
+ * NULL if not present.
+ */
+ public Long mtime = null;
+
+ /**
+ * Checks if this entry is a directory.
+ *
+ * @return Returns true if permissions are available and they indicate
+ * that this entry represents a directory.
+ */
+ public boolean isDirectory()
+ {
+ if (permissions == null)
+ return false;
+
+ return ((permissions.intValue() & 0040000) != 0);
+ }
+
+ /**
+ * Checks if this entry is a regular file.
+ *
+ * @return Returns true if permissions are available and they indicate
+ * that this entry represents a regular file.
+ */
+ public boolean isRegularFile()
+ {
+ if (permissions == null)
+ return false;
+
+ return ((permissions.intValue() & 0100000) != 0);
+ }
+
+ /**
+ * Checks if this entry is a a symlink.
+ *
+ * @return Returns true if permissions are available and they indicate
+ * that this entry represents a symlink.
+ */
+ public boolean isSymlink()
+ {
+ if (permissions == null)
+ return false;
+
+ return ((permissions.intValue() & 0120000) != 0);
+ }
+
+ /**
+ * Turn the POSIX permissions into a 7 digit octal representation.
+ * Note: the returned value is first masked with 0177777.
+ *
+ * @return NULL if permissions are not available.
+ */
+ public String getOctalPermissions()
+ {
+ if (permissions == null)
+ return null;
+
+ String res = Integer.toString(permissions.intValue() & 0177777, 8);
+
+ StringBuffer sb = new StringBuffer();
+
+ int leadingZeros = 7 - res.length();
+
+ while (leadingZeros > 0)
+ {
+ sb.append('0');
+ leadingZeros--;
+ }
+
+ sb.append(res);
+
+ return sb.toString();
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3FileHandle.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3FileHandle.java
new file mode 100644
index 0000000000..817b9cf534
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/SFTPv3FileHandle.java
@@ -0,0 +1,45 @@
+
+package com.trilead.ssh2;
+
+/**
+ * A SFTPv3FileHandle.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: SFTPv3FileHandle.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public class SFTPv3FileHandle
+{
+ final SFTPv3Client client;
+ final byte[] fileHandle;
+ boolean isClosed = false;
+
+ /* The constructor is NOT public */
+
+ SFTPv3FileHandle(SFTPv3Client client, byte[] h)
+ {
+ this.client = client;
+ this.fileHandle = h;
+ }
+
+ /**
+ * Get the SFTPv3Client instance which created this handle.
+ *
+ * @return A SFTPv3Client instance.
+ */
+ public SFTPv3Client getClient()
+ {
+ return client;
+ }
+
+ /**
+ * Check if this handle was closed with the {@link SFTPv3Client#closeFile(SFTPv3FileHandle)} method
+ * of the SFTPv3Client instance which created the handle.
+ *
+ * @return if the handle is closed.
+ */
+ public boolean isClosed()
+ {
+ return isClosed;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ServerHostKeyVerifier.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ServerHostKeyVerifier.java
new file mode 100644
index 0000000000..0cdfb8401d
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/ServerHostKeyVerifier.java
@@ -0,0 +1,31 @@
+
+package com.trilead.ssh2;
+
+/**
+ * A callback interface used to implement a client specific method of checking
+ * server host keys.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: ServerHostKeyVerifier.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public interface ServerHostKeyVerifier
+{
+ /**
+ * The actual verifier method, it will be called by the key exchange code
+ * on EVERY key exchange - this can happen several times during the lifetime
+ * of a connection.
+ *
+ * Note: SSH-2 servers are allowed to change their hostkey at ANY time.
+ *
+ * @param hostname the hostname used to create the {@link Connection} object
+ * @param port the remote TCP port
+ * @param serverHostKeyAlgorithm the public key algorithm (ssh-rsa or ssh-dss)
+ * @param serverHostKey the server's public key blob
+ * @return if the client wants to accept the server's host key - if not, the
+ * connection will be closed.
+ * @throws Exception Will be wrapped with an IOException, extended version of returning false =)
+ */
+ boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey)
+ throws Exception;
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/Session.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/Session.java
new file mode 100644
index 0000000000..29c96e422f
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/Session.java
@@ -0,0 +1,529 @@
+
+package com.trilead.ssh2;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+
+import com.trilead.ssh2.channel.Channel;
+import com.trilead.ssh2.channel.ChannelManager;
+import com.trilead.ssh2.channel.X11ServerData;
+
+
+/**
+ * A Session is a remote execution of a program. "Program" means
+ * in this context either a shell, an application or a system command. The
+ * program may or may not have a tty. Only one single program can be started on
+ * a session. However, multiple sessions can be active simultaneously.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: Session.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $
+ */
+public class Session implements AutoCloseable
+{
+ ChannelManager cm;
+ Channel cn;
+
+ boolean flag_pty_requested = false;
+ boolean flag_x11_requested = false;
+ boolean flag_execution_started = false;
+ boolean flag_closed = false;
+
+ String x11FakeCookie = null;
+
+ final SecureRandom rnd;
+
+ Session(ChannelManager cm, SecureRandom rnd) throws IOException
+ {
+ this.cm = cm;
+ this.cn = cm.openSessionChannel();
+ this.rnd = rnd;
+ }
+
+ /**
+ * Basically just a wrapper for lazy people - identical to calling
+ * requestPTY("dumb", 0, 0, 0, 0, null).
+ *
+ * @throws IOException
+ */
+ public void requestDumbPTY() throws IOException
+ {
+ requestPTY("dumb", 0, 0, 0, 0, null);
+ }
+
+ /**
+ * Basically just another wrapper for lazy people - identical to calling
+ * requestPTY(term, 0, 0, 0, 0, null).
+ *
+ * @throws IOException
+ */
+ public void requestPTY(String term) throws IOException
+ {
+ requestPTY(term, 0, 0, 0, 0, null);
+ }
+
+ /**
+ * Allocate a pseudo-terminal for this session.
+ *
+ * This method may only be called before a program or shell is started in
+ * this session.
+ *
+ * Different aspects can be specified:
+ *
+ *
+ *
The TERM environment variable value (e.g., vt100)
+ *
The terminal's dimensions.
+ *
The encoded terminal modes.
+ *
+ * Zero dimension parameters are ignored. The character/row dimensions
+ * override the pixel dimensions (when nonzero). Pixel dimensions refer to
+ * the drawable area of the window. The dimension parameters are only
+ * informational. The encoding of terminal modes (parameter
+ * terminal_modes) is described in RFC4254.
+ *
+ * @param term
+ * The TERM environment variable value (e.g., vt100)
+ * @param term_width_characters
+ * terminal width, characters (e.g., 80)
+ * @param term_height_characters
+ * terminal height, rows (e.g., 24)
+ * @param term_width_pixels
+ * terminal width, pixels (e.g., 640)
+ * @param term_height_pixels
+ * terminal height, pixels (e.g., 480)
+ * @param terminal_modes
+ * encoded terminal modes (may be null)
+ * @throws IOException
+ */
+ public void requestPTY(String term, int term_width_characters, int term_height_characters, int term_width_pixels,
+ int term_height_pixels, byte[] terminal_modes) throws IOException
+ {
+ if (term == null)
+ throw new IllegalArgumentException("TERM cannot be null.");
+
+ if ((terminal_modes != null) && (terminal_modes.length > 0))
+ {
+ if (terminal_modes[terminal_modes.length - 1] != 0)
+ throw new IOException("Illegal terminal modes description, does not end in zero byte");
+ }
+ else
+ terminal_modes = new byte[] { 0 };
+
+ synchronized (this)
+ {
+ /* The following is just a nicer error, we would catch it anyway later in the channel code */
+ if (flag_closed)
+ throw new IOException("This session is closed.");
+
+ if (flag_pty_requested)
+ throw new IOException("A PTY was already requested.");
+
+ if (flag_execution_started)
+ throw new IOException(
+ "Cannot request PTY at this stage anymore, a remote execution has already started.");
+
+ flag_pty_requested = true;
+ }
+
+ cm.requestPTY(cn, term, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels,
+ terminal_modes);
+ }
+
+ /**
+ * Inform other side of connection that our PTY has resized.
+ *
+ * Zero dimension parameters are ignored. The character/row dimensions
+ * override the pixel dimensions (when nonzero). Pixel dimensions refer to
+ * the drawable area of the window. The dimension parameters are only
+ * informational.
+ *
+ * @param term_width_characters
+ * terminal width, characters (e.g., 80)
+ * @param term_height_characters
+ * terminal height, rows (e.g., 24)
+ * @param term_width_pixels
+ * terminal width, pixels (e.g., 640)
+ * @param term_height_pixels
+ * terminal height, pixels (e.g., 480)
+ * @throws IOException
+ */
+ public void resizePTY(int term_width_characters, int term_height_characters, int term_width_pixels,
+ int term_height_pixels) throws IOException {
+ synchronized (this)
+ {
+ /* The following is just a nicer error, we would catch it anyway later in the channel code */
+ if (flag_closed)
+ throw new IOException("This session is closed.");
+ }
+
+ cm.resizePTY(cn, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels);
+ }
+
+ /**
+ * Request X11 forwarding for the current session.
+ *
+ * You have to supply the name and port of your X-server.
+ *
+ * This method may only be called before a program or shell is started in
+ * this session.
+ *
+ * @param hostname the hostname of the real (target) X11 server (e.g., 127.0.0.1)
+ * @param port the port of the real (target) X11 server (e.g., 6010)
+ * @param cookie if non-null, then present this cookie to the real X11 server
+ * @param singleConnection if true, then the server is instructed to only forward one single
+ * connection, no more connections shall be forwarded after first, or after the session
+ * channel has been closed
+ * @throws IOException
+ */
+ public void requestX11Forwarding(String hostname, int port, byte[] cookie, boolean singleConnection)
+ throws IOException
+ {
+ if (hostname == null)
+ throw new IllegalArgumentException("hostname argument may not be null");
+
+ synchronized (this)
+ {
+ /* The following is just a nicer error, we would catch it anyway later in the channel code */
+ if (flag_closed)
+ throw new IOException("This session is closed.");
+
+ if (flag_x11_requested)
+ throw new IOException("X11 forwarding was already requested.");
+
+ if (flag_execution_started)
+ throw new IOException(
+ "Cannot request X11 forwarding at this stage anymore, a remote execution has already started.");
+
+ flag_x11_requested = true;
+ }
+
+ /* X11ServerData - used to store data about the target X11 server */
+
+ X11ServerData x11data = new X11ServerData();
+
+ x11data.hostname = hostname;
+ x11data.port = port;
+ x11data.x11_magic_cookie = cookie; /* if non-null, then present this cookie to the real X11 server */
+
+ /* Generate fake cookie - this one is used between remote clients and our proxy */
+
+ byte[] fakeCookie = new byte[16];
+ String hexEncodedFakeCookie;
+
+ /* Make sure that this fake cookie is unique for this connection */
+
+ while (true)
+ {
+ rnd.nextBytes(fakeCookie);
+
+ /* Generate also hex representation of fake cookie */
+
+ StringBuffer tmp = new StringBuffer(32);
+ for (int i = 0; i < fakeCookie.length; i++)
+ {
+ String digit2 = Integer.toHexString(fakeCookie[i] & 0xff);
+ tmp.append((digit2.length() == 2) ? digit2 : "0" + digit2);
+ }
+ hexEncodedFakeCookie = tmp.toString();
+
+ /* Well, yes, chances are low, but we want to be on the safe side */
+
+ if (cm.checkX11Cookie(hexEncodedFakeCookie) == null)
+ break;
+ }
+
+ /* Ask for X11 forwarding */
+
+ cm.requestX11(cn, singleConnection, "MIT-MAGIC-COOKIE-1", hexEncodedFakeCookie, 0);
+
+ /* OK, that went fine, get ready to accept X11 connections... */
+ /* ... but only if the user has not called close() in the meantime =) */
+
+ synchronized (this)
+ {
+ if (!flag_closed)
+ {
+ this.x11FakeCookie = hexEncodedFakeCookie;
+ cm.registerX11Cookie(hexEncodedFakeCookie, x11data);
+ }
+ }
+
+ /* Now it is safe to start remote X11 programs */
+ }
+
+ /**
+ * Execute a command on the remote machine.
+ *
+ * @param cmd
+ * The command to execute on the remote host.
+ * @throws IOException
+ */
+ public void execCommand(String cmd) throws IOException
+ {
+ if (cmd == null)
+ throw new IllegalArgumentException("cmd argument may not be null");
+
+ synchronized (this)
+ {
+ /* The following is just a nicer error, we would catch it anyway later in the channel code */
+ if (flag_closed)
+ throw new IOException("This session is closed.");
+
+ if (flag_execution_started)
+ throw new IOException("A remote execution has already started.");
+
+ flag_execution_started = true;
+ }
+
+ cm.requestExecCommand(cn, cmd);
+ }
+
+ /**
+ * Start a shell on the remote machine.
+ *
+ * @throws IOException
+ */
+ public void startShell() throws IOException
+ {
+ synchronized (this)
+ {
+ /* The following is just a nicer error, we would catch it anyway later in the channel code */
+ if (flag_closed)
+ throw new IOException("This session is closed.");
+
+ if (flag_execution_started)
+ throw new IOException("A remote execution has already started.");
+
+ flag_execution_started = true;
+ }
+
+ cm.requestShell(cn);
+ }
+
+ /**
+ * Start a subsystem on the remote machine.
+ * Unless you know what you are doing, you will never need this.
+ *
+ * @param name the name of the subsystem.
+ * @throws IOException
+ */
+ public void startSubSystem(String name) throws IOException
+ {
+ if (name == null)
+ throw new IllegalArgumentException("name argument may not be null");
+
+ synchronized (this)
+ {
+ /* The following is just a nicer error, we would catch it anyway later in the channel code */
+ if (flag_closed)
+ throw new IOException("This session is closed.");
+
+ if (flag_execution_started)
+ throw new IOException("A remote execution has already started.");
+
+ flag_execution_started = true;
+ }
+
+ cm.requestSubSystem(cn, name);
+ }
+
+ /**
+ * This method can be used to perform end-to-end session (i.e., SSH channel)
+ * testing. It sends a 'ping' message to the server and waits for the 'pong'
+ * from the server.
+ *
+ * Implementation details: this method sends a SSH_MSG_CHANNEL_REQUEST request
+ * ('trilead-ping') to the server and waits for the SSH_MSG_CHANNEL_FAILURE reply
+ * packet.
+ *
+ * @throws IOException in case of any problem or when the session is closed
+ */
+ public void ping() throws IOException
+ {
+ synchronized (this)
+ {
+ /*
+ * The following is just a nicer error, we would catch it anyway
+ * later in the channel code
+ */
+ if (flag_closed)
+ throw new IOException("This session is closed.");
+ }
+
+ cm.requestChannelTrileadPing(cn);
+ }
+
+ /**
+ * Request authentication agent forwarding.
+ * @param agent object that implements the callbacks
+ *
+ * @throws IOException in case of any problem or when the session is closed
+ */
+ public synchronized boolean requestAuthAgentForwarding(AuthAgentCallback agent) throws IOException
+ {
+ synchronized (this)
+ {
+ /*
+ * The following is just a nicer error, we would catch it anyway
+ * later in the channel code
+ */
+ if (flag_closed)
+ throw new IOException("This session is closed.");
+ }
+
+ return cm.requestChannelAgentForwarding(cn, agent);
+ }
+
+ public InputStream getStdout()
+ {
+ return cn.getStdoutStream();
+ }
+
+ public InputStream getStderr()
+ {
+ return cn.getStderrStream();
+ }
+
+ public OutputStream getStdin()
+ {
+ return cn.getStdinStream();
+ }
+
+ /**
+ * This method blocks until there is more data available on either the
+ * stdout or stderr InputStream of this Session. Very useful
+ * if you do not want to use two parallel threads for reading from the two
+ * InputStreams. One can also specify a timeout. NOTE: do NOT call this
+ * method if you use concurrent threads that operate on either of the two
+ * InputStreams of this Session (otherwise this method may
+ * block, even though more data is available).
+ *
+ * @param timeout
+ * The (non-negative) timeout in ms. 0 means no
+ * timeout, the call may block forever.
+ * @return
+ *
+ *
0 if no more data will arrive.
+ *
1 if more data is available.
+ *
-1 if a timeout occurred.
+ *
+ *
+ * @deprecated This method has been replaced with a much more powerful wait-for-condition
+ * interface and therefore acts only as a wrapper.
+ *
+ */
+ @Deprecated
+ public int waitUntilDataAvailable(long timeout) {
+ if (timeout < 0)
+ throw new IllegalArgumentException("timeout must not be negative!");
+
+ int conditions = cm.waitForCondition(cn, timeout, ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA
+ | ChannelCondition.EOF);
+
+ if ((conditions & ChannelCondition.TIMEOUT) != 0)
+ return -1;
+
+ if ((conditions & (ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA)) != 0)
+ return 1;
+
+ /* Here we do not need to check separately for CLOSED, since CLOSED implies EOF */
+
+ if ((conditions & ChannelCondition.EOF) != 0)
+ return 0;
+
+ throw new IllegalStateException("Unexpected condition result (" + conditions + ")");
+ }
+
+ /**
+ * This method blocks until certain conditions hold true on the underlying SSH-2 channel.
+ *
+ * This method returns as soon as one of the following happens:
+ *
+ *
at least of the specified conditions (see {@link ChannelCondition}) holds true
+ *
timeout > 0 and a timeout occured (TIMEOUT will be set in result conditions)
+ *
the underlying channel was closed (CLOSED will be set in result conditions)
+ *
+ *
+ * In any case, the result value contains ALL current conditions, which may be more
+ * than the specified condition set (i.e., never use the "==" operator to test for conditions
+ * in the bitmask, see also comments in {@link ChannelCondition}).
+ *
+ * Note: do NOT call this method if you want to wait for STDOUT_DATA or STDERR_DATA and
+ * there are concurrent threads (e.g., StreamGobblers) that operate on either of the two
+ * InputStreams of this Session (otherwise this method may
+ * block, even though more data is available in the StreamGobblers).
+ *
+ * @param condition_set a bitmask based on {@link ChannelCondition} values
+ * @param timeout non-negative timeout in ms, 0 means no timeout
+ * @return all bitmask specifying all current conditions that are true
+ */
+
+ public int waitForCondition(int condition_set, long timeout)
+ {
+ if (timeout < 0)
+ throw new IllegalArgumentException("timeout must be non-negative!");
+
+ return cm.waitForCondition(cn, timeout, condition_set);
+ }
+
+ /**
+ * Get the exit code/status from the remote command - if available. Be
+ * careful - not all server implementations return this value. It is
+ * generally a good idea to call this method only when all data from the
+ * remote side has been consumed (see also the WaitForCondition method).
+ *
+ * @return An Integer holding the exit code, or
+ * null if no exit code is (yet) available.
+ */
+ public Integer getExitStatus()
+ {
+ return cn.getExitStatus();
+ }
+
+ /**
+ * Get the name of the signal by which the process on the remote side was
+ * stopped - if available and applicable. Be careful - not all server
+ * implementations return this value.
+ *
+ * @return An String holding the name of the signal, or
+ * null if the process exited normally or is still
+ * running (or if the server forgot to send this information).
+ */
+ public String getExitSignal()
+ {
+ return cn.getExitSignal();
+ }
+
+ /**
+ * Close this session. NEVER forget to call this method to free up resources -
+ * even if you got an exception from one of the other methods (or when
+ * getting an Exception on the Input- or OutputStreams). Sometimes these other
+ * methods may throw an exception, saying that the underlying channel is
+ * closed (this can happen, e.g., if the other server sent a close message.)
+ * However, as long as you have not called the close()
+ * method, you may be wasting (local) resources.
+ *
+ */
+ public void close()
+ {
+ synchronized (this)
+ {
+ if (flag_closed)
+ return;
+
+ flag_closed = true;
+
+ if (x11FakeCookie != null)
+ cm.unRegisterX11Cookie(x11FakeCookie, true);
+
+ try
+ {
+ cm.closeChannel(cn, "Closed due to user request", true);
+ }
+ catch (IOException ignored)
+ {
+ }
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/StreamGobbler.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/StreamGobbler.java
new file mode 100644
index 0000000000..3c9572768b
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/StreamGobbler.java
@@ -0,0 +1,229 @@
+
+package com.trilead.ssh2;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A StreamGobbler is an InputStream that uses an internal worker
+ * thread to constantly consume input from another InputStream. It uses a buffer
+ * to store the consumed data. The buffer size is automatically adjusted, if needed.
+ *
+ * This class is sometimes very convenient - if you wrap a session's STDOUT and STDERR
+ * InputStreams with instances of this class, then you don't have to bother about
+ * the shared window of STDOUT and STDERR in the low level SSH-2 protocol,
+ * since all arriving data will be immediatelly consumed by the worker threads.
+ * Also, as a side effect, the streams will be buffered (e.g., single byte
+ * read() operations are faster).
+ *
+ * Other SSH for Java libraries include this functionality by default in
+ * their STDOUT and STDERR InputStream implementations, however, please be aware
+ * that this approach has also a downside:
+ *
+ * If you do not call the StreamGobbler's read() method often enough
+ * and the peer is constantly sending huge amounts of data, then you will sooner or later
+ * encounter a low memory situation due to the aggregated data (well, it also depends on the Java heap size).
+ * Joe Average will like this class anyway - a paranoid programmer would never use such an approach.
+ *
+ * The term "StreamGobbler" was taken from an article called "When Runtime.exec() won't",
+ * see http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: StreamGobbler.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public class StreamGobbler extends InputStream
+{
+ class GobblerThread extends Thread
+ {
+ public void run()
+ {
+ byte[] buff = new byte[8192];
+
+ while (true)
+ {
+ try
+ {
+ int avail = is.read(buff);
+
+ synchronized (synchronizer)
+ {
+ if (avail <= 0)
+ {
+ isEOF = true;
+ synchronizer.notifyAll();
+ break;
+ }
+
+ int space_available = buffer.length - write_pos;
+
+ if (space_available < avail)
+ {
+ /* compact/resize buffer */
+
+ int unread_size = write_pos - read_pos;
+ int need_space = unread_size + avail;
+
+ byte[] new_buffer = buffer;
+
+ if (need_space > buffer.length)
+ {
+ int inc = need_space / 3;
+ inc = (inc < 256) ? 256 : inc;
+ inc = (inc > 8192) ? 8192 : inc;
+ new_buffer = new byte[need_space + inc];
+ }
+
+ if (unread_size > 0)
+ System.arraycopy(buffer, read_pos, new_buffer, 0, unread_size);
+
+ buffer = new_buffer;
+
+ read_pos = 0;
+ write_pos = unread_size;
+ }
+
+ System.arraycopy(buff, 0, buffer, write_pos, avail);
+ write_pos += avail;
+
+ synchronizer.notifyAll();
+ }
+ }
+ catch (IOException e)
+ {
+ synchronized (synchronizer)
+ {
+ exception = e;
+ synchronizer.notifyAll();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private InputStream is;
+ private GobblerThread t;
+
+ private Object synchronizer = new Object();
+
+ private boolean isEOF = false;
+ private boolean isClosed = false;
+ private IOException exception = null;
+
+ private byte[] buffer = new byte[2048];
+ private int read_pos = 0;
+ private int write_pos = 0;
+
+ public StreamGobbler(InputStream is)
+ {
+ this.is = is;
+ t = new GobblerThread();
+ t.setDaemon(true);
+ t.start();
+ }
+
+ public int read() throws IOException
+ {
+ synchronized (synchronizer)
+ {
+ if (isClosed)
+ throw new IOException("This StreamGobbler is closed.");
+
+ while (read_pos == write_pos)
+ {
+ if (exception != null)
+ throw exception;
+
+ if (isEOF)
+ return -1;
+
+ try
+ {
+ synchronizer.wait();
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+
+ int b = buffer[read_pos++] & 0xff;
+
+ return b;
+ }
+ }
+
+ public int available() throws IOException
+ {
+ synchronized (synchronizer)
+ {
+ if (isClosed)
+ throw new IOException("This StreamGobbler is closed.");
+
+ return write_pos - read_pos;
+ }
+ }
+
+ public int read(byte[] b) throws IOException
+ {
+ return read(b, 0, b.length);
+ }
+
+ public void close() throws IOException
+ {
+ synchronized (synchronizer)
+ {
+ if (isClosed)
+ return;
+ isClosed = true;
+ isEOF = true;
+ synchronizer.notifyAll();
+ is.close();
+ }
+ }
+
+ 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;
+
+ synchronized (synchronizer)
+ {
+ if (isClosed)
+ throw new IOException("This StreamGobbler is closed.");
+
+ while (read_pos == write_pos)
+ {
+ if (exception != null)
+ throw exception;
+
+ if (isEOF)
+ return -1;
+
+ try
+ {
+ synchronizer.wait();
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+
+ int avail = write_pos - read_pos;
+
+ avail = (avail > len) ? len : avail;
+
+ System.arraycopy(buffer, read_pos, b, off, avail);
+
+ read_pos += avail;
+
+ return avail;
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java
new file mode 100644
index 0000000000..47460ca307
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/auth/AuthenticationManager.java
@@ -0,0 +1,518 @@
+
+package com.trilead.ssh2.auth;
+
+import com.trilead.ssh2.crypto.keys.Ed25519PrivateKey;
+import com.trilead.ssh2.crypto.keys.Ed25519PublicKey;
+import com.trilead.ssh2.signature.RSASHA256Verify;
+import com.trilead.ssh2.signature.RSASHA512Verify;
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Set;
+import java.util.Vector;
+
+import com.trilead.ssh2.InteractiveCallback;
+import com.trilead.ssh2.crypto.PEMDecoder;
+import com.trilead.ssh2.packets.PacketServiceAccept;
+import com.trilead.ssh2.packets.PacketServiceRequest;
+import com.trilead.ssh2.packets.PacketUserauthBanner;
+import com.trilead.ssh2.packets.PacketUserauthFailure;
+import com.trilead.ssh2.packets.PacketUserauthInfoRequest;
+import com.trilead.ssh2.packets.PacketUserauthInfoResponse;
+import com.trilead.ssh2.packets.PacketUserauthRequestInteractive;
+import com.trilead.ssh2.packets.PacketUserauthRequestNone;
+import com.trilead.ssh2.packets.PacketUserauthRequestPassword;
+import com.trilead.ssh2.packets.PacketUserauthRequestPublicKey;
+import com.trilead.ssh2.packets.Packets;
+import com.trilead.ssh2.packets.TypesWriter;
+import com.trilead.ssh2.signature.DSASHA1Verify;
+import com.trilead.ssh2.signature.ECDSASHA2Verify;
+import com.trilead.ssh2.signature.Ed25519Verify;
+import com.trilead.ssh2.signature.RSASHA1Verify;
+import com.trilead.ssh2.signature.SSHSignature;
+import com.trilead.ssh2.transport.MessageHandler;
+import com.trilead.ssh2.transport.TransportManager;
+
+
+/**
+ * AuthenticationManager.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: AuthenticationManager.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $
+ */
+public class AuthenticationManager implements MessageHandler
+{
+ TransportManager tm;
+
+ Vector packets = new Vector();
+ boolean connectionClosed = false;
+
+ String banner;
+
+ String[] remainingMethods = new String[0];
+ boolean isPartialSuccess = false;
+
+ boolean authenticated = false;
+ boolean initDone = false;
+
+ public AuthenticationManager(TransportManager tm)
+ {
+ this.tm = tm;
+ }
+
+ boolean methodPossible(String methName)
+ {
+ if (remainingMethods == null)
+ return false;
+
+ for (int i = 0; i < remainingMethods.length; i++)
+ {
+ if (remainingMethods[i].compareTo(methName) == 0)
+ return true;
+ }
+ return false;
+ }
+
+ byte[] deQueue() throws IOException
+ {
+ synchronized (packets)
+ {
+ while (packets.size() == 0)
+ {
+ if (connectionClosed)
+ throw new IOException("The connection is closed.", tm.getReasonClosedCause());
+
+ try
+ {
+ packets.wait();
+ }
+ catch (InterruptedException ign)
+ {
+ }
+ }
+ /* This sequence works with J2ME */
+ byte[] res = (byte[]) packets.firstElement();
+ packets.removeElementAt(0);
+ return res;
+ }
+ }
+
+ byte[] getNextMessage() throws IOException
+ {
+ while (true)
+ {
+ byte[] msg = deQueue();
+
+ if (msg[0] != Packets.SSH_MSG_USERAUTH_BANNER)
+ return msg;
+
+ PacketUserauthBanner sb = new PacketUserauthBanner(msg, 0, msg.length);
+
+ banner = sb.getBanner();
+ }
+ }
+
+ public String[] getRemainingMethods(String user) throws IOException
+ {
+ initialize(user);
+ return remainingMethods;
+ }
+
+ public boolean getPartialSuccess()
+ {
+ return isPartialSuccess;
+ }
+
+ private boolean initialize(String user) throws IOException
+ {
+ if (!initDone)
+ {
+ tm.registerMessageHandler(this, 0, 255);
+
+ PacketServiceRequest sr = new PacketServiceRequest("ssh-userauth");
+ tm.sendMessage(sr.getPayload());
+
+ PacketUserauthRequestNone urn = new PacketUserauthRequestNone("ssh-connection", user);
+ tm.sendMessage(urn.getPayload());
+
+ byte[] msg = getNextMessage();
+ new PacketServiceAccept(msg, 0, msg.length);
+ msg = getNextMessage();
+
+ initDone = true;
+
+ if (msg[0] == Packets.SSH_MSG_USERAUTH_SUCCESS)
+ {
+ authenticated = true;
+ tm.removeMessageHandler(this, 0, 255);
+ return true;
+ }
+
+ if (msg[0] == Packets.SSH_MSG_USERAUTH_FAILURE)
+ {
+ PacketUserauthFailure puf = new PacketUserauthFailure(msg, 0, msg.length);
+
+ remainingMethods = puf.getAuthThatCanContinue();
+ isPartialSuccess = puf.isPartialSuccess();
+ return false;
+ }
+
+ throw new IOException("Unexpected SSH message (type " + msg[0] + ")");
+ }
+ return authenticated;
+ }
+
+ public boolean authenticatePublicKey(String user, char[] PEMPrivateKey, String password, SecureRandom rnd)
+ throws IOException
+ {
+ KeyPair pair = PEMDecoder.decode(PEMPrivateKey, password);
+
+ return authenticatePublicKey(user, pair, rnd);
+ }
+
+ public boolean authenticatePublicKey(String user, KeyPair pair, SecureRandom rnd)
+ throws IOException
+ {
+ return authenticatePublicKey(user, pair, rnd, null);
+ }
+
+ public boolean authenticatePublicKey(String user, SignatureProxy signatureProxy)
+ throws IOException
+ {
+ return authenticatePublicKey(user, null, null, signatureProxy);
+ }
+
+ public boolean authenticatePublicKey(String user, KeyPair pair, SecureRandom rnd, SignatureProxy signatureProxy)
+ throws IOException
+ {
+ PrivateKey privateKey = null;
+ PublicKey publicKey = null;
+ if (pair != null)
+ {
+ privateKey = pair.getPrivate();
+ publicKey = pair.getPublic();
+ }
+ if (signatureProxy != null)
+ {
+ publicKey = signatureProxy.getPublicKey();
+ }
+
+ try
+ {
+ initialize(user);
+
+ if (!methodPossible("publickey"))
+ throw new IOException("Authentication method publickey not supported by the server at this stage.");
+
+ if (publicKey instanceof DSAPublicKey)
+ {
+ SSHSignature s = DSASHA1Verify.get();
+ byte[] pk_enc = s.encodePublicKey(publicKey);
+
+ byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, DSASHA1Verify.ID_SSH_DSS, pk_enc);
+
+ byte[] ds_enc;
+ if (signatureProxy != null)
+ {
+ ds_enc = signatureProxy.sign(msg, SignatureProxy.SHA1);
+ }
+ else
+ {
+ ds_enc = s.generateSignature(msg, privateKey, rnd);
+ }
+
+ PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
+ DSASHA1Verify.ID_SSH_DSS, pk_enc, ds_enc);
+ tm.sendMessage(ua.getPayload());
+ }
+ else if (publicKey instanceof RSAPublicKey)
+ {
+ byte[] pk_enc = RSASHA1Verify.get().encodePublicKey(publicKey);
+ String pk_algorithm;
+
+
+ // Servers support different hash algorithms for RSA keys
+ // https://tools.ietf.org/html/draft-ietf-curdle-rsa-sha2-12
+ Set algsAccepted = tm.getExtensionInfo().getSignatureAlgorithmsAccepted();
+ final byte[] rsa_sig_enc;
+
+ if (algsAccepted.contains(RSASHA512Verify.get().getKeyFormat()))
+ {
+ SSHSignature s = RSASHA512Verify.get();
+ pk_algorithm = s.getKeyFormat();
+ byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, pk_algorithm, pk_enc);
+ if (signatureProxy != null)
+ {
+ rsa_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA512);
+ }
+ else
+ {
+ rsa_sig_enc = s.generateSignature(msg, privateKey, rnd);
+ }
+ }
+ else if (algsAccepted.contains(RSASHA256Verify.ID_RSA_SHA_2_256))
+ {
+ pk_algorithm = RSASHA256Verify.ID_RSA_SHA_2_256;
+ byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, pk_algorithm, pk_enc);
+
+ if (signatureProxy != null)
+ {
+ rsa_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA256);
+ }
+ else
+ {
+ rsa_sig_enc = RSASHA256Verify.get().generateSignature(msg, privateKey, rnd);
+ }
+ }
+ else
+ {
+ pk_algorithm = "ssh-rsa";
+ byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, pk_algorithm, pk_enc);
+ if (signatureProxy != null)
+ {
+ rsa_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA1);
+ }
+ else
+ {
+ // Server always accepts RSA with SHA1
+ rsa_sig_enc = RSASHA1Verify.get().generateSignature(msg, privateKey, rnd);
+ }
+ }
+
+ PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
+ pk_algorithm, pk_enc, rsa_sig_enc);
+
+ tm.sendMessage(ua.getPayload());
+ }
+ else if (publicKey instanceof ECPublicKey)
+ {
+ ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
+
+ ECDSASHA2Verify verifier = ECDSASHA2Verify.getVerifierForKey(ecPublicKey);
+
+ final String algo = verifier.getKeyFormat();
+
+ byte[] pk_enc = verifier.encodePublicKey(ecPublicKey);
+
+ byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, algo, pk_enc);
+
+ byte[] ec_sig_enc;
+ if (signatureProxy != null)
+ {
+ ec_sig_enc = signatureProxy.sign(msg, ECDSASHA2Verify.getDigestAlgorithmForParams(ecPublicKey));
+ }
+ else
+ {
+ ec_sig_enc = verifier.generateSignature(msg, privateKey, rnd);
+ }
+
+ PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
+ algo, pk_enc, ec_sig_enc);
+
+ tm.sendMessage(ua.getPayload());
+ }
+ else if (publicKey instanceof Ed25519PublicKey)
+ {
+ final String algo = Ed25519Verify.ED25519_ID;
+
+ byte[] pk_enc = Ed25519Verify.get().encodePublicKey(publicKey);
+
+ byte[] msg = this.generatePublicKeyUserAuthenticationRequest(user, algo, pk_enc);
+
+ byte[] ed_sig_enc;
+ if (signatureProxy != null)
+ {
+ ed_sig_enc = signatureProxy.sign(msg, SignatureProxy.SHA512);
+ }
+ else
+ {
+ Ed25519PrivateKey pk = (Ed25519PrivateKey) privateKey;
+ ed_sig_enc = Ed25519Verify.get().generateSignature(msg, pk, rnd);
+ }
+
+ PacketUserauthRequestPublicKey ua = new PacketUserauthRequestPublicKey("ssh-connection", user,
+ algo, pk_enc, ed_sig_enc);
+
+ tm.sendMessage(ua.getPayload());
+ }
+ else
+ {
+ throw new IOException("Unknown public key type.");
+ }
+
+ byte[] ar = getNextMessage();
+
+ return isAuthenticationSuccessful(ar);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ tm.close(e, false);
+ throw new IOException("Publickey authentication failed.", e);
+ }
+ }
+
+ public boolean authenticateNone(String user) throws IOException
+ {
+ try
+ {
+ initialize(user);
+ return authenticated;
+ }
+ catch (IOException e)
+ {
+ tm.close(e, false);
+ throw new IOException("None authentication failed.", e);
+ }
+ }
+
+ public boolean authenticatePassword(String user, String pass) throws IOException
+ {
+ try
+ {
+ initialize(user);
+
+ if (!methodPossible("password"))
+ throw new IOException("Authentication method password not supported by the server at this stage.");
+
+ PacketUserauthRequestPassword ua = new PacketUserauthRequestPassword("ssh-connection", user, pass);
+ tm.sendMessage(ua.getPayload());
+
+ byte[] ar = getNextMessage();
+
+ return isAuthenticationSuccessful(ar);
+ }
+ catch (IOException e)
+ {
+ tm.close(e, false);
+ throw new IOException("Password authentication failed.", e);
+ }
+ }
+
+ public boolean authenticateInteractive(String user, String[] submethods, InteractiveCallback cb) throws IOException
+ {
+ try
+ {
+ initialize(user);
+
+ if (!methodPossible("keyboard-interactive"))
+ throw new IOException(
+ "Authentication method keyboard-interactive not supported by the server at this stage.");
+
+ if (submethods == null)
+ submethods = new String[0];
+
+ PacketUserauthRequestInteractive ua = new PacketUserauthRequestInteractive("ssh-connection", user,
+ submethods);
+
+ tm.sendMessage(ua.getPayload());
+
+ while (true)
+ {
+ byte[] ar = getNextMessage();
+
+ if (ar[0] == Packets.SSH_MSG_USERAUTH_INFO_REQUEST)
+ {
+ PacketUserauthInfoRequest pui = new PacketUserauthInfoRequest(ar, 0, ar.length);
+
+ String[] responses;
+
+ try
+ {
+ responses = cb.replyToChallenge(pui.getName(), pui.getInstruction(), pui.getNumPrompts(), pui
+ .getPrompt(), pui.getEcho());
+ }
+ catch (Exception e)
+ {
+ throw new IOException("Exception in callback.", e);
+ }
+
+ if (responses == null)
+ throw new IOException("Your callback may not return NULL!");
+
+ PacketUserauthInfoResponse puir = new PacketUserauthInfoResponse(responses);
+ tm.sendMessage(puir.getPayload());
+
+ continue;
+ }
+
+ return isAuthenticationSuccessful(ar);
+ }
+ }
+ catch (IOException e)
+ {
+ tm.close(e, false);
+ throw new IOException("Keyboard-interactive authentication failed.", e);
+ }
+ }
+
+ public void handleMessage(byte[] msg, int msglen) throws IOException
+ {
+ synchronized (packets)
+ {
+ if (msg == null)
+ {
+ connectionClosed = true;
+ }
+ else
+ {
+ byte[] tmp = new byte[msglen];
+ System.arraycopy(msg, 0, tmp, 0, msglen);
+ packets.addElement(tmp);
+ }
+
+ packets.notifyAll();
+
+ if (packets.size() > 5)
+ {
+ connectionClosed = true;
+ throw new IOException("Error, peer is flooding us with authentication packets.");
+ }
+ }
+ }
+
+ private boolean isAuthenticationSuccessful(byte[] ar) throws IOException
+ {
+ if (ar[0] == Packets.SSH_MSG_USERAUTH_SUCCESS)
+ {
+ authenticated = true;
+ tm.removeMessageHandler(this, 0, 255);
+ return true;
+ }
+
+ if (ar[0] == Packets.SSH_MSG_USERAUTH_FAILURE)
+ {
+ PacketUserauthFailure puf = new PacketUserauthFailure(ar, 0, ar.length);
+
+ remainingMethods = puf.getAuthThatCanContinue();
+ isPartialSuccess = puf.isPartialSuccess();
+
+ return false;
+ }
+
+ throw new IOException("Unexpected SSH message (type " + ar[0] + ")");
+ }
+
+ private byte[] generatePublicKeyUserAuthenticationRequest(String user, String algorithm, byte[] publicKeyEncoded) {
+ TypesWriter tw = new TypesWriter();
+ {
+ byte[] H = tm.getSessionIdentifier();
+
+ tw.writeString(H, 0, H.length);
+ tw.writeByte(Packets.SSH_MSG_USERAUTH_REQUEST);
+ tw.writeString(user);
+ tw.writeString("ssh-connection");
+ tw.writeString("publickey");
+ tw.writeBoolean(true);
+ tw.writeString(algorithm);
+ tw.writeString(publicKeyEncoded, 0, publicKeyEncoded.length);
+ }
+
+ return tw.getBytes();
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/auth/SignatureProxy.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/auth/SignatureProxy.java
new file mode 100644
index 0000000000..bdf00bbcfd
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/auth/SignatureProxy.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 Jonas Dippel, Michael Perk, Marc Totzke
+ */
+
+package com.trilead.ssh2.auth;
+
+import java.io.IOException;
+import java.security.PublicKey;
+
+public abstract class SignatureProxy
+{
+ public static final String SHA1 = "SHA-1";
+ public static final String SHA256 = "SHA-256";
+ public static final String SHA384 = "SHA-384";
+ public static final String SHA512 = "SHA-512";
+
+ /**
+ * Holds the public key which belongs to the private key which is used in the signing process.
+ */
+ private PublicKey mPublicKey;
+
+ /**
+ * Instantiates a new SignatureProxy which needs a public key for the
+ * later authentication process.
+ *
+ * @param publicKey The public key.
+ * @throws IllegalArgumentException Might be thrown id the public key is invalid.
+ */
+ public SignatureProxy(PublicKey publicKey)
+ {
+ if (publicKey == null)
+ {
+ throw new IllegalArgumentException("Public key must not be null");
+ }
+ mPublicKey = publicKey;
+ }
+
+ /**
+ * This method should sign a given byte array message using the private key.
+ *
+ * @param message The message which should be signed.
+ * @param hashAlgorithm The hashing algorithm which should be used.
+ * @return The signed message.
+ * @throws IOException This exception might be thrown during the signing process.
+ */
+ public abstract byte[] sign(byte[] message, String hashAlgorithm) throws IOException;
+
+ public PublicKey getPublicKey()
+ {
+ return mPublicKey;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/AuthAgentForwardThread.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/AuthAgentForwardThread.java
new file mode 100644
index 0000000000..b7c753a6ed
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/AuthAgentForwardThread.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * a.) Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * b.) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * c.) Neither the name of Trilead nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.trilead.ssh2.channel;
+
+import com.trilead.ssh2.signature.RSASHA256Verify;
+import com.trilead.ssh2.signature.RSASHA512Verify;
+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.crypto.keys.Ed25519PrivateKey;
+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.Ed25519Verify;
+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;
+ // https://tools.ietf.org/html/draft-miller-ssh-agent-02#section-7.3
+ private static final int SSH_AGENT_RSA_SHA2_256 = 0x02;
+ private static final int SSH_AGENT_RSA_SHA2_512 = 0x04;
+
+ 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 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 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(DSASHA1Verify.ID_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(ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().getKeyFormat())) {
+ ECDSASHA2Verify verifier = ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get();
+ 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 params = verifier.getParameterSpec();
+ ECPoint group = verifier.decodeECPoint(groupBytes);
+ if (group == null) {
+ // TODO log error
+ os.write(SSH_AGENT_FAILURE);
+ return;
+ }
+
+ pubSpec = new ECPublicKeySpec(group, params);
+ privSpec = new ECPrivateKeySpec(exponent, params);
+ } 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 & ~SSH_AGENT_RSA_SHA2_512 & ~SSH_AGENT_RSA_SHA2_256) != 0) {
+ // We don't understand these flags; abort!
+ log.log(2, "Unrecognized ssh-agent flags: " + flags);
+ 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) {
+ RSAPrivateKey rsaPrivKey = (RSAPrivateKey) privKey;
+ if ((flags & SSH_AGENT_RSA_SHA2_512) != 0) {
+ response = RSASHA512Verify.get().generateSignature(challenge, rsaPrivKey, new SecureRandom());
+ } else if ((flags & SSH_AGENT_RSA_SHA2_256) != 0) {
+ response = RSASHA256Verify.get().generateSignature(challenge, rsaPrivKey, new SecureRandom());
+ } else {
+ response = RSASHA1Verify.get().generateSignature(challenge, rsaPrivKey, new SecureRandom());
+ }
+ } else if (privKey instanceof DSAPrivateKey) {
+ response = DSASHA1Verify.get().generateSignature(challenge, privKey, new SecureRandom());
+ } else if (privKey instanceof Ed25519PrivateKey) {
+ response = Ed25519Verify.get().generateSignature(challenge, privKey, new SecureRandom());
+ } 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 message
+ * @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/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/Channel.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/Channel.java
new file mode 100644
index 0000000000..c6d4354cf3
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/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/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java
new file mode 100644
index 0000000000..e1f6a9c1e2
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/ChannelInputStream.java
@@ -0,0 +1,85 @@
+
+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() {
+ 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/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/ChannelManager.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/ChannelManager.java
new file mode 100644
index 0000000000..7858a57af6
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/ChannelManager.java
@@ -0,0 +1,1749 @@
+
+package com.trilead.ssh2.channel;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import com.trilead.ssh2.AuthAgentCallback;
+import com.trilead.ssh2.ChannelCondition;
+import com.trilead.ssh2.log.Logger;
+import com.trilead.ssh2.packets.PacketChannelAuthAgentReq;
+import com.trilead.ssh2.packets.PacketChannelOpenConfirmation;
+import com.trilead.ssh2.packets.PacketChannelOpenFailure;
+import com.trilead.ssh2.packets.PacketChannelTrileadPing;
+import com.trilead.ssh2.packets.PacketGlobalCancelForwardRequest;
+import com.trilead.ssh2.packets.PacketGlobalForwardRequest;
+import com.trilead.ssh2.packets.PacketGlobalTrileadPing;
+import com.trilead.ssh2.packets.PacketOpenDirectTCPIPChannel;
+import com.trilead.ssh2.packets.PacketOpenSessionChannel;
+import com.trilead.ssh2.packets.PacketSessionExecCommand;
+import com.trilead.ssh2.packets.PacketSessionPtyRequest;
+import com.trilead.ssh2.packets.PacketSessionPtyResize;
+import com.trilead.ssh2.packets.PacketSessionStartShell;
+import com.trilead.ssh2.packets.PacketSessionSubsystemRequest;
+import com.trilead.ssh2.packets.PacketSessionX11Request;
+import com.trilead.ssh2.packets.Packets;
+import com.trilead.ssh2.packets.TypesReader;
+import com.trilead.ssh2.transport.MessageHandler;
+import com.trilead.ssh2.transport.TransportManager;
+
+/**
+ * ChannelManager. Please read the comments in Channel.java.
+ *
+ * Besides the crypto part, this is the core of the library.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: ChannelManager.java,v 1.2 2008/03/03 07:01:36 cplattne Exp $
+ */
+public class ChannelManager implements MessageHandler
+{
+ private static final Logger log = Logger.getLogger(ChannelManager.class);
+
+ private final HashMap x11_magic_cookies = new HashMap<>();
+
+ private TransportManager tm;
+
+ private final List channels = new ArrayList<>();
+ private int nextLocalChannel = 100;
+ private boolean shutdown = false;
+ private int globalSuccessCounter = 0;
+ private int globalFailedCounter = 0;
+
+ private final HashMap remoteForwardings = new HashMap<>();
+
+ private AuthAgentCallback authAgent;
+
+ private final List listenerThreads = new ArrayList<>();
+
+ private boolean listenerThreadsAllowed = true;
+
+ public ChannelManager(TransportManager tm)
+ {
+ this.tm = tm;
+ tm.registerMessageHandler(this, 80, 100);
+ }
+
+ private Channel getChannel(int id)
+ {
+ synchronized (channels)
+ {
+ for (Channel c : channels)
+ {
+ 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.get(i);
+ if (c.localID == id)
+ {
+ channels.remove(i);
+ break;
+ }
+ }
+ }
+ }
+
+ private int addChannel(Channel c)
+ {
+ synchronized (channels)
+ {
+ channels.add(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 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 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)
+ return;
+
+ if (log.isEnabled())
+ log.log(50, "Closing all X11 channels for the given fake cookie");
+
+ List channel_copy;
+
+ synchronized (channels)
+ {
+ channel_copy = new ArrayList<>(channels);
+ }
+
+ for (int i = 0; i < channel_copy.size(); i++)
+ {
+ Channel c = channel_copy.get(i);
+
+ synchronized (c)
+ {
+ if (!hexFakeCookie.equals(c.hexX11FakeCookie))
+ 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");
+
+ List channel_copy;
+
+ synchronized (channels)
+ {
+ channel_copy = new ArrayList<>(channels);
+ }
+
+ for (int i = 0; i < channel_copy.size(); i++)
+ {
+ Channel c = channel_copy.get(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)
+ 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)
+ 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)
+ 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)
+ 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)
+ {
+ if (remoteForwardings.get(bindPort) != null)
+ {
+ throw new IOException("There is already a forwarding for remote port " + bindPort);
+ }
+
+ remoteForwardings.put(bindPort, 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())
+ throw new IOException("The server denied the request (did you enable port forwarding?)");
+ }
+ catch (IOException e)
+ {
+ synchronized (remoteForwardings)
+ {
+ remoteForwardings.remove(rfd.bindPort);
+ }
+ throw e;
+ }
+
+ return bindPort;
+ }
+
+ public void requestCancelGlobalForward(int bindPort) throws IOException
+ {
+ RemoteForwardingData rfd = null;
+
+ synchronized (remoteForwardings)
+ {
+ rfd = remoteForwardings.get(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())
+ 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.bindPort);
+ }
+ }
+
+ }
+
+ /**
+ * @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)) {
+ authAgent = null;
+ return false;
+ }
+
+ return true;
+ }
+
+ public void registerThread(IChannelWorkerThread thr) throws IOException
+ {
+ synchronized (listenerThreads)
+ {
+ if (!listenerThreadsAllowed)
+ throw new IOException("Too late, this connection is closed.");
+ listenerThreads.add(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())
+ 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 new IOException("The ping request failed.", 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))
+ 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 new IOException("The ping request failed.", 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))
+ throw new IOException("The server denied the request.");
+ }
+ catch (IOException e)
+ {
+ throw new IOException("PTY request failed", 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))
+ throw new IOException("The server denied the request.");
+ }
+ catch (IOException e)
+ {
+ throw new IOException("The X11 request failed.", 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))
+ throw new IOException("The server denied the request.");
+ }
+ catch (IOException e)
+ {
+ throw new IOException("The subsystem request failed.", 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))
+ throw new IOException("The server denied the request.");
+ }
+ catch (IOException e)
+ {
+ throw new IOException("The execute request failed.", 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))
+ throw new IOException("The server denied the request.");
+ }
+ catch (IOException e)
+ {
+ throw new IOException("The shell request failed.", 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) {
+ 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)
+ 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)
+ 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)
+ 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 + ")";
+ }
+
+ StringBuilder descriptionBuffer = new StringBuilder();
+ 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() {
+ synchronized (channels)
+ {
+ globalSuccessCounter++;
+ channels.notifyAll();
+ }
+
+ if (log.isEnabled())
+ log.log(80, "Got SSH_MSG_REQUEST_SUCCESS");
+ }
+
+ public void msgGlobalFailure() {
+ 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 (IChannelWorkerThread lat : listenerThreads)
+ {
+ lat.stopWorking();
+ }
+ listenerThreadsAllowed = false;
+ }
+
+ synchronized (channels)
+ {
+ shutdown = true;
+
+ for (Channel c : channels)
+ {
+ 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.clear();
+ 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/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/ChannelOutputStream.java
new file mode 100644
index 0000000000..ef7acd9eca
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/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)
+ {
+ 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/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java
new file mode 100644
index 0000000000..629a70108c
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/DynamicAcceptThread.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * a.) Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * b.) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * c.) Neither the name of Trilead nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.trilead.ssh2.channel;
+
+import org.connectbot.simplesocks.Socks5Server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * DynamicAcceptThread.
+ *
+ * @author Kenny Root
+ * @version $Id$
+ */
+public class DynamicAcceptThread extends Thread implements IChannelWorkerThread {
+ private ChannelManager cm;
+ private ServerSocket ss;
+
+ 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) {
+ final Socket sock;
+ try {
+ sock = ss.accept();
+ } catch (IOException e) {
+ stopWorking();
+ return;
+ }
+
+ DynamicAcceptRunnable dar = new DynamicAcceptRunnable(sock);
+ Thread t = new Thread(dar);
+ t.setDaemon(true);
+ t.start();
+ }
+ }
+
+ @Override
+ public void stopWorking() {
+ try {
+ /* This will lead to an IOException in the ss.accept() call */
+ ss.close();
+ } catch (IOException ignore) {
+ }
+ }
+
+ class DynamicAcceptRunnable implements Runnable {
+ private static final int idleTimeout = 180000; //3 minutes
+
+ private Socket sock;
+ private InputStream in;
+ private OutputStream out;
+
+ public DynamicAcceptRunnable(Socket sock) {
+ this.sock = sock;
+
+ setName("DynamicAcceptRunnable");
+ }
+
+ public void run() {
+ try {
+ startSession();
+ } catch (IOException ioe) {
+ try {
+ sock.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ private void startSession() throws IOException {
+ sock.setSoTimeout(idleTimeout);
+
+ in = sock.getInputStream();
+ out = sock.getOutputStream();
+ Socks5Server server = new Socks5Server(in, out);
+ try {
+ if (!server.acceptAuthentication() || !server.readRequest()) {
+ System.out.println("Could not start SOCKS session");
+ return;
+ }
+ } catch (IOException ioe) {
+ server.sendReply(Socks5Server.ResponseCode.GENERAL_FAILURE);
+ return;
+ }
+
+ if (server.getCommand() == Socks5Server.Command.CONNECT) {
+ onConnect(server);
+ } else {
+ server.sendReply(Socks5Server.ResponseCode.COMMAND_NOT_SUPPORTED);
+ }
+ }
+
+ private void onConnect(Socks5Server server) throws IOException {
+ final Channel cn;
+
+ String destHost = server.getHostName();
+ if (destHost == null) {
+ destHost = server.getAddress().getHostAddress();
+ }
+
+ try {
+ /*
+ * This may fail, e.g., if the remote port is closed (in
+ * optimistic terms: not open yet)
+ */
+
+ cn = cm.openDirectTCPIPChannel(destHost, server.getPort(),
+ "127.0.0.1", 0);
+
+ } catch (IOException e) {
+ /*
+ * Try to send a notification back to the client and then close the socket.
+ */
+ try {
+ server.sendReply(Socks5Server.ResponseCode.GENERAL_FAILURE);
+ } catch (IOException ignore) {
+ }
+
+ try {
+ sock.close();
+ } catch (IOException ignore) {
+ }
+
+ return;
+ }
+
+ server.sendReply(Socks5Server.ResponseCode.SUCCESS);
+
+ final StreamForwarder r2l = new StreamForwarder(cn, null, sock, cn.stdoutStream, out, "RemoteToLocal");
+ final StreamForwarder l2r = new StreamForwarder(cn, r2l, sock, in, cn.stdinStream, "LocalToRemote");
+
+ r2l.setDaemon(true);
+ l2r.setDaemon(true);
+ r2l.start();
+ l2r.start();
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/IChannelWorkerThread.java
new file mode 100644
index 0000000000..7610b9d2e9
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/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
+{
+ void stopWorking();
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/LocalAcceptThread.java
new file mode 100644
index 0000000000..e0b92e0b73
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/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/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/RemoteAcceptThread.java
new file mode 100644
index 0000000000..3ad9045231
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/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/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/RemoteForwardingData.java
new file mode 100644
index 0000000000..fbf4b65413
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/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/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java
new file mode 100644
index 0000000000..95667a2d57
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/RemoteX11AcceptThread.java
@@ -0,0 +1,247 @@
+
+package com.trilead.ssh2.channel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+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)");
+
+ String authProtocolNameStr;
+ try {
+ authProtocolNameStr = new String(authProtocolName, "ISO-8859-1");
+ } catch (UnsupportedEncodingException e) {
+ authProtocolNameStr = new String(authProtocolName);
+ }
+ if (!"MIT-MAGIC-COOKIE-1".equals(authProtocolNameStr))
+ 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/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java
new file mode 100644
index 0000000000..496b5736a0
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/StreamForwarder.java
@@ -0,0 +1,111 @@
+
+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) {
+ 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/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/X11ServerData.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/channel/X11ServerData.java
new file mode 100644
index 0000000000..2c85b98ba8
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/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 */
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/CompressionFactory.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/CompressionFactory.java
new file mode 100644
index 0000000000..153a74f56d
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/CompressionFactory.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * a.) Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * b.) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * c.) Neither the name of Trilead nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.trilead.ssh2.compression;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Kenny Root
+ *
+ */
+public class CompressionFactory {
+ private CompressionFactory() { }
+
+ private static class CompressorEntry
+ {
+ String type;
+ String compressorClass;
+
+ private CompressorEntry(String type, String compressorClass)
+ {
+ this.type = type;
+ this.compressorClass = compressorClass;
+ }
+ }
+
+ private static List compressors = new ArrayList<>();
+
+ static
+ {
+ /* Higher Priority First */
+ compressors.add(new CompressorEntry("zlib", "com.trilead.ssh2.compression.Zlib"));
+ compressors.add(new CompressorEntry("zlib@openssh.com", "com.trilead.ssh2.compression.ZlibOpenSSH"));
+ compressors.add(new CompressorEntry("none", ""));
+ }
+
+ static void addCompressor(String protocolName, String className) {
+ compressors.add(new CompressorEntry(protocolName, className));
+ }
+
+ public static String[] getDefaultCompressorList()
+ {
+ String[] list = new String[compressors.size()];
+ for (int i = 0; i < compressors.size(); i++)
+ {
+ CompressorEntry ce = compressors.get(i);
+ list[i] = ce.type;
+ }
+ return list;
+ }
+
+ public static void checkCompressorList(String[] compressorCandidates)
+ {
+ for (String compressorCandidate : compressorCandidates) {
+ getEntry(compressorCandidate);
+ }
+ }
+
+ public static ICompressor createCompressor(String type)
+ {
+ try
+ {
+ CompressorEntry ce = getEntry(type);
+ if ("".equals(ce.compressorClass))
+ return null;
+
+ Class> cc = Class.forName(ce.compressorClass);
+ Constructor> constructor = cc.getConstructor();
+ return (ICompressor) constructor.newInstance();
+ }
+ catch (Exception e)
+ {
+ throw new IllegalArgumentException("Cannot instantiate " + type);
+ }
+ }
+
+ private static CompressorEntry getEntry(String type)
+ {
+ for (CompressorEntry ce : compressors) {
+ if (ce.type.equals(type))
+ return ce;
+ }
+ throw new IllegalArgumentException("Unknown algorithm " + type);
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/ICompressor.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/ICompressor.java
new file mode 100644
index 0000000000..5b733ec94c
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/ICompressor.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * a.) Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * b.) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * c.) Neither the name of Trilead nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.trilead.ssh2.compression;
+
+/**
+ * @author Kenny Root
+ *
+ */
+public interface ICompressor {
+ int getBufferSize();
+
+ int compress(byte[] buf, int start, int len, byte[] output);
+
+ byte[] uncompress(byte[] buf, int start, int[] len);
+
+ boolean canCompressPreauth();
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/Zlib.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/Zlib.java
new file mode 100644
index 0000000000..c1acab3f6c
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/Zlib.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * a.) Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * b.) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * c.) Neither the name of Trilead nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.trilead.ssh2.compression;
+
+import com.jcraft.jzlib.JZlib;
+import com.jcraft.jzlib.ZStream;
+
+/**
+ * @author Kenny Root
+ *
+ */
+public class Zlib implements ICompressor {
+ static private final int DEFAULT_BUF_SIZE = 4096;
+ static private final int LEVEL = 5;
+
+ private ZStream deflate;
+ private byte[] deflate_tmpbuf;
+
+ private ZStream inflate;
+ private byte[] inflate_tmpbuf;
+ private byte[] inflated_buf;
+
+ public Zlib() {
+ deflate = new ZStream();
+ inflate = new ZStream();
+
+ deflate.deflateInit(LEVEL);
+ inflate.inflateInit();
+
+ deflate_tmpbuf = new byte[DEFAULT_BUF_SIZE];
+ inflate_tmpbuf = new byte[DEFAULT_BUF_SIZE];
+ inflated_buf = new byte[DEFAULT_BUF_SIZE];
+ }
+
+ public boolean canCompressPreauth() {
+ return true;
+ }
+
+ public int getBufferSize() {
+ return DEFAULT_BUF_SIZE;
+ }
+
+ public int compress(byte[] buf, int start, int len, byte[] output) {
+ deflate.next_in = buf;
+ deflate.next_in_index = start;
+ deflate.avail_in = len - start;
+
+ if ((buf.length + 1024) > deflate_tmpbuf.length) {
+ deflate_tmpbuf = new byte[buf.length + 1024];
+ }
+
+ deflate.next_out = deflate_tmpbuf;
+ deflate.next_out_index = 0;
+ deflate.avail_out = output.length;
+
+ if (deflate.deflate(JZlib.Z_PARTIAL_FLUSH) != JZlib.Z_OK) {
+ System.err.println("compress: compression failure");
+ }
+
+ if (deflate.avail_in > 0) {
+ System.err.println("compress: deflated data too large");
+ }
+
+ int outputlen = output.length - deflate.avail_out;
+
+ System.arraycopy(deflate_tmpbuf, 0, output, 0, outputlen);
+
+ return outputlen;
+ }
+
+ public byte[] uncompress(byte[] buffer, int start, int[] length) {
+ int inflated_end = 0;
+
+ inflate.next_in = buffer;
+ inflate.next_in_index = start;
+ inflate.avail_in = length[0];
+
+ while (true) {
+ inflate.next_out = inflate_tmpbuf;
+ inflate.next_out_index = 0;
+ inflate.avail_out = DEFAULT_BUF_SIZE;
+ int status = inflate.inflate(JZlib.Z_PARTIAL_FLUSH);
+ switch (status) {
+ case JZlib.Z_OK:
+ if (inflated_buf.length < inflated_end + DEFAULT_BUF_SIZE
+ - inflate.avail_out) {
+ byte[] foo = new byte[inflated_end + DEFAULT_BUF_SIZE
+ - inflate.avail_out];
+ System.arraycopy(inflated_buf, 0, foo, 0, inflated_end);
+ inflated_buf = foo;
+ }
+ System.arraycopy(inflate_tmpbuf, 0, inflated_buf, inflated_end,
+ DEFAULT_BUF_SIZE - inflate.avail_out);
+ inflated_end += (DEFAULT_BUF_SIZE - inflate.avail_out);
+ length[0] = inflated_end;
+ break;
+ case JZlib.Z_BUF_ERROR:
+ if (inflated_end > buffer.length - start) {
+ byte[] foo = new byte[inflated_end + start];
+ System.arraycopy(buffer, 0, foo, 0, start);
+ System.arraycopy(inflated_buf, 0, foo, start, inflated_end);
+ buffer = foo;
+ } else {
+ System.arraycopy(inflated_buf, 0, buffer, start,
+ inflated_end);
+ }
+ length[0] = inflated_end;
+ return buffer;
+ default:
+ System.err.println("uncompress: inflate returnd " + status);
+ return null;
+ }
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/ZlibOpenSSH.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/ZlibOpenSSH.java
new file mode 100644
index 0000000000..039766a849
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/compression/ZlibOpenSSH.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2007 Kenny Root, Jeffrey Sharkey
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * a.) Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * b.) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * c.) Neither the name of Trilead nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.trilead.ssh2.compression;
+
+/**
+ * Defines how zlib@openssh.org compression works.
+ * See
+ * http://www.openssh.org/txt/draft-miller-secsh-compression-delayed-00.txt
+ * compression is disabled until userauth has occurred.
+ *
+ * @author Matt Johnston
+ *
+ */
+public class ZlibOpenSSH extends Zlib {
+
+ public boolean canCompressPreauth() {
+ return false;
+ }
+
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/Base64.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/Base64.java
new file mode 100644
index 0000000000..096f6734af
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/Base64.java
@@ -0,0 +1,148 @@
+
+package com.trilead.ssh2.crypto;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+
+/**
+ * Basic Base64 Support.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: Base64.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class Base64
+{
+ static final char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
+
+ public static char[] encode(byte[] content)
+ {
+ CharArrayWriter cw = new CharArrayWriter((4 * content.length) / 3);
+
+ int idx = 0;
+
+ int x = 0;
+
+ for (int i = 0; i < content.length; i++)
+ {
+ if (idx == 0)
+ x = (content[i] & 0xff) << 16;
+ else if (idx == 1)
+ x = x | ((content[i] & 0xff) << 8);
+ else
+ x = x | (content[i] & 0xff);
+
+ idx++;
+
+ if (idx == 3)
+ {
+ cw.write(alphabet[x >> 18]);
+ cw.write(alphabet[(x >> 12) & 0x3f]);
+ cw.write(alphabet[(x >> 6) & 0x3f]);
+ cw.write(alphabet[x & 0x3f]);
+
+ idx = 0;
+ }
+ }
+
+ if (idx == 1)
+ {
+ cw.write(alphabet[x >> 18]);
+ cw.write(alphabet[(x >> 12) & 0x3f]);
+ cw.write('=');
+ cw.write('=');
+ }
+
+ if (idx == 2)
+ {
+ cw.write(alphabet[x >> 18]);
+ cw.write(alphabet[(x >> 12) & 0x3f]);
+ cw.write(alphabet[(x >> 6) & 0x3f]);
+ cw.write('=');
+ }
+
+ return cw.toCharArray();
+ }
+
+ public static byte[] decode(char[] message) throws IOException
+ {
+ byte buff[] = new byte[4];
+ byte dest[] = new byte[message.length];
+
+ int bpos = 0;
+ int destpos = 0;
+
+ for (int i = 0; i < message.length; i++)
+ {
+ int c = message[i];
+
+ if ((c == '\n') || (c == '\r') || (c == ' ') || (c == '\t'))
+ continue;
+
+ if ((c >= 'A') && (c <= 'Z'))
+ {
+ buff[bpos++] = (byte) (c - 'A');
+ }
+ else if ((c >= 'a') && (c <= 'z'))
+ {
+ buff[bpos++] = (byte) ((c - 'a') + 26);
+ }
+ else if ((c >= '0') && (c <= '9'))
+ {
+ buff[bpos++] = (byte) ((c - '0') + 52);
+ }
+ else if (c == '+')
+ {
+ buff[bpos++] = 62;
+ }
+ else if (c == '/')
+ {
+ buff[bpos++] = 63;
+ }
+ else if (c == '=')
+ {
+ buff[bpos++] = 64;
+ }
+ else
+ {
+ throw new IOException("Illegal char in base64 code.");
+ }
+
+ if (bpos == 4)
+ {
+ bpos = 0;
+
+ if (buff[0] == 64)
+ break;
+
+ if (buff[1] == 64)
+ throw new IOException("Unexpected '=' in base64 code.");
+
+ if (buff[2] == 64)
+ {
+ int v = (((buff[0] & 0x3f) << 6) | ((buff[1] & 0x3f)));
+ dest[destpos++] = (byte) (v >> 4);
+ break;
+ }
+ else if (buff[3] == 64)
+ {
+ int v = (((buff[0] & 0x3f) << 12) | ((buff[1] & 0x3f) << 6) | ((buff[2] & 0x3f)));
+ dest[destpos++] = (byte) (v >> 10);
+ dest[destpos++] = (byte) (v >> 2);
+ break;
+ }
+ else
+ {
+ int v = (((buff[0] & 0x3f) << 18) | ((buff[1] & 0x3f) << 12) | ((buff[2] & 0x3f) << 6) | ((buff[3] & 0x3f)));
+ dest[destpos++] = (byte) (v >> 16);
+ dest[destpos++] = (byte) (v >> 8);
+ dest[destpos++] = (byte) (v);
+ }
+ }
+ }
+
+ byte[] res = new byte[destpos];
+ System.arraycopy(dest, 0, res, 0, destpos);
+
+ return res;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/CryptoWishList.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/CryptoWishList.java
new file mode 100644
index 0000000000..f080c8d0db
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/CryptoWishList.java
@@ -0,0 +1,26 @@
+
+package com.trilead.ssh2.crypto;
+
+import com.trilead.ssh2.compression.CompressionFactory;
+import com.trilead.ssh2.crypto.cipher.BlockCipherFactory;
+import com.trilead.ssh2.crypto.digest.MACs;
+import com.trilead.ssh2.transport.KexManager;
+
+
+/**
+ * CryptoWishList.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: CryptoWishList.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class CryptoWishList
+{
+ public String[] kexAlgorithms = KexManager.getDefaultKexAlgorithmList();
+ public String[] serverHostKeyAlgorithms = KexManager.getDefaultServerHostkeyAlgorithmList();
+ public String[] c2s_enc_algos = BlockCipherFactory.getDefaultCipherList();
+ public String[] s2c_enc_algos = BlockCipherFactory.getDefaultCipherList();
+ public String[] c2s_mac_algos = MACs.getMacList();
+ public String[] s2c_mac_algos = MACs.getMacList();
+ public String[] c2s_comp_algos = CompressionFactory.getDefaultCompressorList();
+ public String[] s2c_comp_algos = CompressionFactory.getDefaultCompressorList();
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/KeyMaterial.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/KeyMaterial.java
new file mode 100644
index 0000000000..e711f5b7ca
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/KeyMaterial.java
@@ -0,0 +1,91 @@
+
+package com.trilead.ssh2.crypto;
+
+
+import java.math.BigInteger;
+
+import com.trilead.ssh2.crypto.digest.HashForSSH2Types;
+
+/**
+ * Establishes key material for iv/key/mac (both directions).
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: KeyMaterial.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class KeyMaterial
+{
+ public byte[] initial_iv_client_to_server;
+ public byte[] initial_iv_server_to_client;
+ public byte[] enc_key_client_to_server;
+ public byte[] enc_key_server_to_client;
+ public byte[] integrity_key_client_to_server;
+ public byte[] integrity_key_server_to_client;
+
+ private static byte[] calculateKey(HashForSSH2Types sh, BigInteger K, byte[] H, byte type, byte[] SessionID,
+ int keyLength)
+ {
+ byte[] res = new byte[keyLength];
+
+ int dglen = sh.getDigestLength();
+ int numRounds = (keyLength + dglen - 1) / dglen;
+
+ byte[][] tmp = new byte[numRounds][];
+
+ sh.reset();
+ sh.updateBigInt(K);
+ sh.updateBytes(H);
+ sh.updateByte(type);
+ sh.updateBytes(SessionID);
+
+ tmp[0] = sh.getDigest();
+
+ int off = 0;
+ int produced = Math.min(dglen, keyLength);
+
+ System.arraycopy(tmp[0], 0, res, off, produced);
+
+ keyLength -= produced;
+ off += produced;
+
+ for (int i = 1; i < numRounds; i++)
+ {
+ sh.updateBigInt(K);
+ sh.updateBytes(H);
+
+ for (int j = 0; j < i; j++)
+ sh.updateBytes(tmp[j]);
+
+ tmp[i] = sh.getDigest();
+
+ produced = Math.min(dglen, keyLength);
+ System.arraycopy(tmp[i], 0, res, off, produced);
+ keyLength -= produced;
+ off += produced;
+ }
+
+ return res;
+ }
+
+ public static KeyMaterial create(String hashAlgo, byte[] H, BigInteger K, byte[] SessionID, int keyLengthCS,
+ int blockSizeCS, int macLengthCS, int keyLengthSC, int blockSizeSC, int macLengthSC)
+ throws IllegalArgumentException
+ {
+ KeyMaterial km = new KeyMaterial();
+
+ HashForSSH2Types sh = new HashForSSH2Types(hashAlgo);
+
+ km.initial_iv_client_to_server = calculateKey(sh, K, H, (byte) 'A', SessionID, blockSizeCS);
+
+ km.initial_iv_server_to_client = calculateKey(sh, K, H, (byte) 'B', SessionID, blockSizeSC);
+
+ km.enc_key_client_to_server = calculateKey(sh, K, H, (byte) 'C', SessionID, keyLengthCS);
+
+ km.enc_key_server_to_client = calculateKey(sh, K, H, (byte) 'D', SessionID, keyLengthSC);
+
+ km.integrity_key_client_to_server = calculateKey(sh, K, H, (byte) 'E', SessionID, macLengthCS);
+
+ km.integrity_key_server_to_client = calculateKey(sh, K, H, (byte) 'F', SessionID, macLengthSC);
+
+ return km;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java
new file mode 100644
index 0000000000..cc0c2f4f54
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/PEMDecoder.java
@@ -0,0 +1,696 @@
+
+package com.trilead.ssh2.crypto;
+
+import java.io.BufferedReader;
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.DigestException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+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.RSAPrivateKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Arrays;
+import java.util.Locale;
+
+import com.trilead.ssh2.crypto.cipher.AES;
+import com.trilead.ssh2.crypto.cipher.BlockCipher;
+import com.trilead.ssh2.crypto.cipher.DES;
+import com.trilead.ssh2.crypto.cipher.DESede;
+import com.trilead.ssh2.crypto.keys.Ed25519PrivateKey;
+import com.trilead.ssh2.crypto.keys.Ed25519PublicKey;
+import com.trilead.ssh2.packets.TypesReader;
+import com.trilead.ssh2.signature.DSASHA1Verify;
+import com.trilead.ssh2.signature.ECDSASHA2Verify;
+import com.trilead.ssh2.signature.Ed25519Verify;
+import com.trilead.ssh2.signature.RSASHA1Verify;
+import org.mindrot.jbcrypt.BCrypt;
+
+/**
+ * PEM Support.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: PEMDecoder.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
+ */
+public class PEMDecoder
+{
+ public static final int PEM_RSA_PRIVATE_KEY = 1;
+ public static final int PEM_DSA_PRIVATE_KEY = 2;
+ public static final int PEM_EC_PRIVATE_KEY = 3;
+ public static final int PEM_OPENSSH_PRIVATE_KEY = 4;
+
+ private static final byte[] OPENSSH_V1_MAGIC = new byte[] {
+ 'o', 'p', 'e', 'n', 's', 's', 'h', '-', 'k', 'e', 'y', '-', 'v', '1', '\0',
+ };
+
+ private static int hexToInt(char c)
+ {
+ if ((c >= 'a') && (c <= 'f'))
+ {
+ return (c - 'a') + 10;
+ }
+
+ if ((c >= 'A') && (c <= 'F'))
+ {
+ return (c - 'A') + 10;
+ }
+
+ if ((c >= '0') && (c <= '9'))
+ {
+ return (c - '0');
+ }
+
+ throw new IllegalArgumentException("Need hex char");
+ }
+
+ private static byte[] hexToByteArray(String hex)
+ {
+ if (hex == null)
+ throw new IllegalArgumentException("null argument");
+
+ if ((hex.length() % 2) != 0)
+ throw new IllegalArgumentException("Uneven string length in hex encoding.");
+
+ byte decoded[] = new byte[hex.length() / 2];
+
+ for (int i = 0; i < decoded.length; i++)
+ {
+ int hi = hexToInt(hex.charAt(i * 2));
+ int lo = hexToInt(hex.charAt((i * 2) + 1));
+
+ decoded[i] = (byte) (hi * 16 + lo);
+ }
+
+ return decoded;
+ }
+
+ private static byte[] generateKeyFromPasswordSaltWithMD5(byte[] password, byte[] salt, int keyLen)
+ throws IOException
+ {
+ if (salt.length < 8)
+ throw new IllegalArgumentException("Salt needs to be at least 8 bytes for key generation.");
+
+ MessageDigest md5;
+ try {
+ md5 = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("VM does not support MD5", e);
+ }
+
+ byte[] key = new byte[keyLen];
+ byte[] tmp = new byte[md5.getDigestLength()];
+
+ while (true)
+ {
+ md5.update(password, 0, password.length);
+ md5.update(salt, 0, 8); // ARGH we only use the first 8 bytes of the
+ // salt in this step.
+ // This took me two hours until I got AES-xxx running.
+
+ int copy = (keyLen < tmp.length) ? keyLen : tmp.length;
+
+ try {
+ md5.digest(tmp, 0, tmp.length);
+ } catch (DigestException e) {
+ throw new IOException("could not digest password", e);
+ }
+
+ System.arraycopy(tmp, 0, key, key.length - keyLen, copy);
+
+ keyLen -= copy;
+
+ if (keyLen == 0)
+ return key;
+
+ md5.update(tmp, 0, tmp.length);
+ }
+ }
+
+ private static byte[] removePadding(byte[] buff, int blockSize) throws IOException
+ {
+ /* Removes RFC 1423/PKCS #7 padding */
+
+ int rfc_1423_padding = buff[buff.length - 1] & 0xff;
+
+ if ((rfc_1423_padding < 1) || (rfc_1423_padding > blockSize))
+ throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?");
+
+ for (int i = 2; i <= rfc_1423_padding; i++)
+ {
+ if (buff[buff.length - i] != rfc_1423_padding)
+ throw new IOException("Decrypted PEM has wrong padding, did you specify the correct password?");
+ }
+
+ byte[] tmp = new byte[buff.length - rfc_1423_padding];
+ System.arraycopy(buff, 0, tmp, 0, buff.length - rfc_1423_padding);
+ return tmp;
+ }
+
+ public static final PEMStructure parsePEM(char[] pem) throws IOException
+ {
+ PEMStructure ps = new PEMStructure();
+
+ String line = null;
+
+ BufferedReader br = new BufferedReader(new CharArrayReader(pem));
+
+ String endLine = null;
+
+ while (true)
+ {
+ line = br.readLine();
+
+ if (line == null)
+ throw new IOException("Invalid PEM structure, '-----BEGIN...' missing");
+
+ line = line.trim();
+
+ if (line.startsWith("-----BEGIN DSA PRIVATE KEY-----"))
+ {
+ endLine = "-----END DSA PRIVATE KEY-----";
+ ps.pemType = PEM_DSA_PRIVATE_KEY;
+ break;
+ }
+
+ if (line.startsWith("-----BEGIN RSA PRIVATE KEY-----"))
+ {
+ endLine = "-----END RSA PRIVATE KEY-----";
+ ps.pemType = PEM_RSA_PRIVATE_KEY;
+ break;
+ }
+
+ if (line.startsWith("-----BEGIN EC PRIVATE KEY-----")) {
+ endLine = "-----END EC PRIVATE KEY-----";
+ ps.pemType = PEM_EC_PRIVATE_KEY;
+ break;
+ }
+
+ if (line.startsWith("-----BEGIN OPENSSH PRIVATE KEY-----")) {
+ endLine = "-----END OPENSSH PRIVATE KEY-----";
+ ps.pemType = PEM_OPENSSH_PRIVATE_KEY;
+ break;
+ }
+ }
+
+ while (true)
+ {
+ line = br.readLine();
+
+ if (line == null)
+ throw new IOException("Invalid PEM structure, " + endLine + " missing");
+
+ line = line.trim();
+
+ int sem_idx = line.indexOf(':');
+
+ if (sem_idx == -1)
+ break;
+
+ String name = line.substring(0, sem_idx + 1);
+ String value = line.substring(sem_idx + 1);
+
+ String values[] = value.split(",");
+
+ for (int i = 0; i < values.length; i++)
+ values[i] = values[i].trim();
+
+ // Proc-Type: 4,ENCRYPTED
+ // DEK-Info: DES-EDE3-CBC,579B6BE3E5C60483
+
+ if ("Proc-Type:".equals(name))
+ {
+ ps.procType = values;
+ continue;
+ }
+
+ if ("DEK-Info:".equals(name))
+ {
+ ps.dekInfo = values;
+ continue;
+ }
+ /* Ignore line */
+ }
+
+ StringBuffer keyData = new StringBuffer();
+
+ while (true)
+ {
+ if (line == null)
+ throw new IOException("Invalid PEM structure, " + endLine + " missing");
+
+ line = line.trim();
+
+ if (line.startsWith(endLine))
+ break;
+
+ keyData.append(line);
+
+ line = br.readLine();
+ }
+
+ char[] pem_chars = new char[keyData.length()];
+ keyData.getChars(0, pem_chars.length, pem_chars, 0);
+
+ ps.data = Base64.decode(pem_chars);
+
+ if (ps.data.length == 0)
+ throw new IOException("Invalid PEM structure, no data available");
+
+ return ps;
+ }
+
+ private static byte[] decryptData(byte[] data, byte[] pw, byte[] salt, int rounds, String algo) throws IOException
+ {
+ BlockCipher bc;
+ int keySize;
+
+ String algoLower = algo.toLowerCase(Locale.US);
+ if (algoLower.equals("des-ede3-cbc"))
+ {
+ bc = new DESede.CBC();
+ keySize = 24;
+ }
+ else if (algoLower.equals("des-cbc"))
+ {
+ bc = new DES.CBC();
+ keySize = 8;
+ }
+ else if (algoLower.equals("aes-128-cbc") || algoLower.equals("aes128-cbc"))
+ {
+ bc = new AES.CBC();
+ keySize = 16;
+ }
+ else if (algoLower.equals("aes-192-cbc") || algoLower.equals("aes192-cbc"))
+ {
+ bc = new AES.CBC();
+ keySize = 24;
+ }
+ else if (algoLower.equals("aes-256-cbc") || algoLower.equals("aes256-cbc"))
+ {
+ bc = new AES.CBC();
+ keySize = 32;
+ }
+ else if (algoLower.equals("aes-128-ctr") || algoLower.equals("aes128-ctr"))
+ {
+ bc = new AES.CTR();
+ keySize = 16;
+ }
+ else if (algoLower.equals("aes-192-ctr") || algoLower.equals("aes192-ctr"))
+ {
+ bc = new AES.CTR();
+ keySize = 24;
+ }
+ else if (algoLower.equals("aes-256-ctr") || algoLower.equals("aes256-ctr"))
+ {
+ bc = new AES.CTR();
+ keySize = 32;
+ }
+ else
+ {
+ throw new IOException("Cannot decrypt PEM structure, unknown cipher " + algo);
+ }
+
+ if (rounds == -1)
+ {
+ bc.init(false, generateKeyFromPasswordSaltWithMD5(pw, salt, keySize), salt);
+ }
+ else
+ {
+ byte[] key = new byte[keySize];
+ byte[] iv = new byte[bc.getBlockSize()];
+
+ byte[] keyAndIV = new byte[key.length + iv.length];
+
+ new BCrypt().pbkdf(pw, salt, rounds, keyAndIV);
+
+ System.arraycopy(keyAndIV, 0, key, 0, key.length);
+ System.arraycopy(keyAndIV, key.length, iv, 0, iv.length);
+
+ bc.init(false, key, iv);
+ }
+
+
+ if ((data.length % bc.getBlockSize()) != 0)
+ throw new IOException("Invalid PEM structure, size of encrypted block is not a multiple of "
+ + bc.getBlockSize());
+
+ /* Now decrypt the content */
+ byte[] dz = new byte[data.length];
+
+ for (int i = 0; i < data.length / bc.getBlockSize(); i++)
+ {
+ bc.transformBlock(data, i * bc.getBlockSize(), dz, i * bc.getBlockSize());
+ }
+
+ if (rounds == -1) {
+ /* Now check and remove RFC 1423/PKCS #7 padding */
+ return removePadding(dz, bc.getBlockSize());
+ } else {
+ /* New style is to check the padding after reading the comment. */
+ return dz;
+ }
+ }
+
+ private static void decryptPEM(PEMStructure ps, byte[] pw) throws IOException
+ {
+ if (ps.dekInfo == null)
+ throw new IOException("Broken PEM, no mode and salt given, but encryption enabled");
+
+ if (ps.dekInfo.length != 2)
+ throw new IOException("Broken PEM, DEK-Info is incomplete!");
+
+ String algo = ps.dekInfo[0];
+ byte[] salt = hexToByteArray(ps.dekInfo[1]);
+
+ byte[] dz = decryptData(ps.data, pw, salt, -1, algo);
+
+ ps.data = dz;
+ ps.dekInfo = null;
+ ps.procType = null;
+ }
+
+ public static final boolean isPEMEncrypted(PEMStructure ps) throws IOException
+ {
+ if (ps.pemType == PEM_OPENSSH_PRIVATE_KEY) {
+ TypesReader tr = new TypesReader(ps.data);
+ byte[] magic = tr.readBytes(OPENSSH_V1_MAGIC.length);
+ if (!Arrays.equals(OPENSSH_V1_MAGIC, magic)) {
+ throw new IOException("Could not find OPENSSH key magic: " + new String(magic));
+ }
+
+ tr.readString();
+ String kdfname = tr.readString();
+ return !"none".equals(kdfname);
+ }
+
+ if (ps.procType == null)
+ return false;
+
+ if (ps.procType.length != 2)
+ throw new IOException("Unknown Proc-Type field.");
+
+ if (!"4".equals(ps.procType[0]))
+ throw new IOException("Unknown Proc-Type field (" + ps.procType[0] + ")");
+
+ return "ENCRYPTED".equals(ps.procType[1]);
+
+ }
+
+ public static KeyPair decode(char[] pem, String password) throws IOException
+ {
+ PEMStructure ps = parsePEM(pem);
+ return decode(ps, password);
+ }
+
+ public static KeyPair decode(PEMStructure ps, String password) throws IOException
+ {
+ if (isPEMEncrypted(ps) && ps.pemType != PEM_OPENSSH_PRIVATE_KEY)
+ {
+ if (password == null)
+ throw new IOException("PEM is encrypted, but no password was specified");
+
+ try {
+ decryptPEM(ps, password.getBytes("ISO-8859-1"));
+ } catch (UnsupportedEncodingException e) {
+ decryptPEM(ps, password.getBytes("ISO-8859-1"));
+ }
+ }
+
+ if (ps.pemType == PEM_DSA_PRIVATE_KEY)
+ {
+ SimpleDERReader dr = new SimpleDERReader(ps.data);
+
+ byte[] seq = dr.readSequenceAsByteArray();
+
+ if (dr.available() != 0)
+ throw new IOException("Padding in DSA PRIVATE KEY DER stream.");
+
+ dr.resetInput(seq);
+
+ BigInteger version = dr.readInt();
+
+ if (version.compareTo(BigInteger.ZERO) != 0)
+ throw new IOException("Wrong version (" + version + ") in DSA PRIVATE KEY DER stream.");
+
+ BigInteger p = dr.readInt();
+ BigInteger q = dr.readInt();
+ BigInteger g = dr.readInt();
+ BigInteger y = dr.readInt();
+ BigInteger x = dr.readInt();
+
+ if (dr.available() != 0)
+ throw new IOException("Padding in DSA PRIVATE KEY DER stream.");
+
+ DSAPrivateKeySpec privSpec = new DSAPrivateKeySpec(x, p, q, g);
+ DSAPublicKeySpec pubSpec = new DSAPublicKeySpec(y, p, q, g);
+
+ return generateKeyPair("DSA", privSpec, pubSpec);
+ }
+
+ if (ps.pemType == PEM_RSA_PRIVATE_KEY)
+ {
+ SimpleDERReader dr = new SimpleDERReader(ps.data);
+
+ byte[] seq = dr.readSequenceAsByteArray();
+
+ if (dr.available() != 0)
+ throw new IOException("Padding in RSA PRIVATE KEY DER stream.");
+
+ dr.resetInput(seq);
+
+ BigInteger version = dr.readInt();
+
+ if ((version.compareTo(BigInteger.ZERO) != 0) && (version.compareTo(BigInteger.ONE) != 0))
+ throw new IOException("Wrong version (" + version + ") in RSA PRIVATE KEY DER stream.");
+
+ BigInteger n = dr.readInt();
+ BigInteger e = dr.readInt();
+ BigInteger d = dr.readInt();
+ // TODO: is this right?
+ BigInteger primeP = dr.readInt();
+ BigInteger primeQ = dr.readInt();
+ BigInteger expP = dr.readInt();
+ BigInteger expQ = dr.readInt();
+ BigInteger coeff = dr.readInt();
+
+ RSAPrivateKeySpec privSpec = new RSAPrivateCrtKeySpec(n, e, d, primeP, primeQ, expP, expQ, coeff);
+ RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(n, e);
+
+ return generateKeyPair("RSA", privSpec, pubSpec);
+ }
+
+ if (ps.pemType == PEM_EC_PRIVATE_KEY) {
+ SimpleDERReader dr = new SimpleDERReader(ps.data);
+
+ byte[] seq = dr.readSequenceAsByteArray();
+
+ if (dr.available() != 0)
+ throw new IOException("Padding in EC PRIVATE KEY DER stream.");
+
+ dr.resetInput(seq);
+
+ BigInteger version = dr.readInt();
+
+ if ((version.compareTo(BigInteger.ONE) != 0))
+ throw new IOException("Wrong version (" + version + ") in EC PRIVATE KEY DER stream.");
+
+ byte[] privateBytes = dr.readOctetString();
+
+ String curveOid = null;
+ byte[] publicBytes = null;
+ while (dr.available() > 0) {
+ int type = dr.readConstructedType();
+ SimpleDERReader cr = dr.readConstructed();
+ switch (type) {
+ case 0:
+ curveOid = cr.readOid();
+ break;
+ case 1:
+ publicBytes = cr.readOctetString();
+ break;
+ }
+ }
+
+ ECDSASHA2Verify verifier = ECDSASHA2Verify.getVerifierForOID(curveOid);
+ if (verifier == null)
+ throw new IOException("invalid OID");
+
+ BigInteger s = new BigInteger(1, privateBytes);
+ byte[] publicBytesSlice = new byte[publicBytes.length - 1];
+ System.arraycopy(publicBytes, 1, publicBytesSlice, 0, publicBytesSlice.length);
+ ECParameterSpec params = verifier.getParameterSpec();
+ ECPoint w = verifier.decodeECPoint(publicBytesSlice);
+
+ ECPrivateKeySpec privSpec = new ECPrivateKeySpec(s, params);
+ ECPublicKeySpec pubSpec = new ECPublicKeySpec(w, params);
+
+ return generateKeyPair("EC", privSpec, pubSpec);
+ }
+
+ if (ps.pemType == PEM_OPENSSH_PRIVATE_KEY) {
+ TypesReader tr = new TypesReader(ps.data);
+ byte[] magic = tr.readBytes(OPENSSH_V1_MAGIC.length);
+ if (!Arrays.equals(OPENSSH_V1_MAGIC, magic)) {
+ throw new IOException("Could not find OPENSSH key magic: " + new String(magic));
+ }
+
+ String ciphername = tr.readString();
+ String kdfname = tr.readString();
+ byte[] kdfoptions = tr.readByteString();
+ int numberOfKeys = tr.readUINT32();
+
+ // TODO support multiple keys
+ if (numberOfKeys != 1) {
+ throw new IOException("Only one key supported, but encountered bundle of " + numberOfKeys);
+ }
+
+ // OpenSSH discards this, so we will as well.
+ tr.readByteString();
+
+ byte[] dataBytes = tr.readByteString();
+
+ if ("bcrypt".equals(kdfname)) {
+ if (password == null) {
+ throw new IOException("PEM is encrypted, but no password was specified");
+ }
+
+ TypesReader optionsReader = new TypesReader(kdfoptions);
+ byte[] salt = optionsReader.readByteString();
+ int rounds = optionsReader.readUINT32();
+ byte[] passwordBytes;
+ try {
+ passwordBytes = password.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ passwordBytes = password.getBytes();
+ }
+ dataBytes = decryptData(dataBytes, passwordBytes, salt, rounds, ciphername);
+ } else if (!"none".equals(ciphername) || !"none".equals(kdfname)) {
+ throw new IOException("encryption not supported");
+ }
+
+ TypesReader trEnc = new TypesReader(dataBytes);
+
+ int checkInt1 = trEnc.readUINT32();
+ int checkInt2 = trEnc.readUINT32();
+
+ if (checkInt1 != checkInt2) {
+ throw new IOException("Decryption failed when trying to read private keys");
+ }
+
+ String keyType = trEnc.readString();
+
+ KeyPair keyPair;
+ if (Ed25519Verify.ED25519_ID.equals(keyType)) {
+ byte[] publicBytes = trEnc.readByteString();
+ byte[] privateBytes = trEnc.readByteString();
+ PrivateKey privKey = new Ed25519PrivateKey(
+ Arrays.copyOfRange(privateBytes, 0, 32));
+ PublicKey pubKey = new Ed25519PublicKey(publicBytes);
+ keyPair = new KeyPair(pubKey, privKey);
+ } else if (keyType.startsWith("ecdsa-sha2-")) {
+ String curveName = trEnc.readString();
+
+ byte[] groupBytes = trEnc.readByteString();
+ BigInteger privateKey = trEnc.readMPINT();
+
+ final ECDSASHA2Verify verifier;
+ if (curveName.equals(ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().getCurveName())) {
+ verifier = ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get();
+ } else if (curveName.equals(ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().getCurveName())) {
+ verifier = ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get();
+ } else if (curveName.equals(ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().getCurveName())) {
+ verifier = ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get();
+ } else {
+ throw new IOException("Invalid ECDSA group");
+ }
+
+ ECParameterSpec spec = verifier.getParameterSpec();
+ ECPoint group = verifier.decodeECPoint(groupBytes);
+
+ ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(group, spec);
+ ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(privateKey, spec);
+ keyPair = generateKeyPair("EC", privateKeySpec, publicKeySpec);
+ } else if (RSASHA1Verify.get().getKeyFormat().equals(keyType)) {
+ BigInteger n = trEnc.readMPINT();
+ BigInteger e = trEnc.readMPINT();
+ BigInteger d = trEnc.readMPINT();
+
+ BigInteger crtCoefficient = trEnc.readMPINT();
+ BigInteger p = trEnc.readMPINT();
+
+ RSAPrivateKeySpec privateKeySpec;
+ if (null == p || null == crtCoefficient) {
+ privateKeySpec = new RSAPrivateKeySpec(n, d);
+ } else {
+ BigInteger q = crtCoefficient.modInverse(p);
+ BigInteger pE = d.mod(p.subtract(BigInteger.ONE));
+ BigInteger qE = d.mod(q.subtract(BigInteger.ONE));
+ privateKeySpec = new RSAPrivateCrtKeySpec(n, e, d, p, q, pE, qE, crtCoefficient);
+
+ }
+
+ RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
+
+ keyPair = generateKeyPair("RSA", privateKeySpec, publicKeySpec);
+ } else if (DSASHA1Verify.get().getKeyFormat().equals(keyType)) {
+ BigInteger p = trEnc.readMPINT();
+ BigInteger q = trEnc.readMPINT();
+ BigInteger g = trEnc.readMPINT();
+ BigInteger y = trEnc.readMPINT();
+ BigInteger x = trEnc.readMPINT();
+
+ DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g);
+ DSAPublicKeySpec publicKeySpec = new DSAPublicKeySpec(y, p, q, g);
+
+ keyPair = generateKeyPair("DSA", privateKeySpec, publicKeySpec);
+ } else {
+ throw new IOException("Unknown key type " + keyType);
+ }
+
+ byte[] comment = trEnc.readByteString();
+
+ // Make sure the padding is correct first.
+ int remaining = tr.remain();
+ for (int i = 1; i <= remaining; i++) {
+ if (i != tr.readByte()) {
+ throw new IOException("Bad padding value on decrypted private keys");
+ }
+ }
+
+ return keyPair;
+ }
+
+ throw new IOException("PEM problem: it is of unknown type");
+ }
+
+ /**
+ * Generate a {@code KeyPair} given an {@code algorithm} and {@code KeySpec}.
+ */
+ private static KeyPair generateKeyPair(String algorithm, KeySpec privSpec, KeySpec pubSpec)
+ throws IOException {
+ try {
+ final KeyFactory kf = KeyFactory.getInstance(algorithm);
+ final PublicKey pubKey = kf.generatePublic(pubSpec);
+ final PrivateKey privKey = kf.generatePrivate(privSpec);
+ return new KeyPair(pubKey, privKey);
+ } catch (NoSuchAlgorithmException ex) {
+ throw new IOException(ex);
+ } catch (InvalidKeySpecException ex) {
+ throw new IOException("invalid keyspec", ex);
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/PEMStructure.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/PEMStructure.java
new file mode 100644
index 0000000000..d7bb562920
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/PEMStructure.java
@@ -0,0 +1,17 @@
+
+package com.trilead.ssh2.crypto;
+
+/**
+ * Parsed PEM structure.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: PEMStructure.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+
+public class PEMStructure
+{
+ public int pemType;
+ String dekInfo[];
+ String procType[];
+ public byte[] data;
+}
\ No newline at end of file
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/SimpleDERReader.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/SimpleDERReader.java
new file mode 100644
index 0000000000..dca2e00272
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/SimpleDERReader.java
@@ -0,0 +1,235 @@
+package com.trilead.ssh2.crypto;
+
+import java.io.IOException;
+
+import java.math.BigInteger;
+
+/**
+ * SimpleDERReader.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: SimpleDERReader.java,v 1.1 2007/10/15 12:49:56 cplattne Exp $
+ */
+public class SimpleDERReader
+{
+ private static final int CONSTRUCTED = 0x20;
+
+ byte[] buffer;
+ int pos;
+ int count;
+
+ public SimpleDERReader(byte[] b)
+ {
+ resetInput(b);
+ }
+
+ public SimpleDERReader(byte[] b, int off, int len)
+ {
+ resetInput(b, off, len);
+ }
+
+ public void resetInput(byte[] b)
+ {
+ resetInput(b, 0, b.length);
+ }
+
+ public void resetInput(byte[] b, int off, int len)
+ {
+ buffer = b;
+ pos = off;
+ count = len;
+ }
+
+ private byte readByte() throws IOException
+ {
+ if (count <= 0)
+ throw new IOException("DER byte array: out of data");
+ count--;
+ return buffer[pos++];
+ }
+
+ private byte[] readBytes(int len) throws IOException
+ {
+ if (len > count)
+ throw new IOException("DER byte array: out of data");
+
+ byte[] b = new byte[len];
+
+ System.arraycopy(buffer, pos, b, 0, len);
+
+ pos += len;
+ count -= len;
+
+ return b;
+ }
+
+ public int available()
+ {
+ return count;
+ }
+
+ /* visible for testing */
+ int readLength() throws IOException
+ {
+ int len = readByte() & 0xff;
+
+ if ((len & 0x80) == 0)
+ return len;
+
+ int remain = len & 0x7F;
+
+ if (remain == 0)
+ return -1;
+ else if (remain > 4)
+ return -1;
+
+ len = 0;
+
+ while (remain > 0)
+ {
+ len = len << 8;
+ len = len | (readByte() & 0xff);
+ remain--;
+ }
+
+ if (len < 0)
+ return -1;
+
+ return len;
+ }
+
+ public int ignoreNextObject() throws IOException
+ {
+ int type = readByte() & 0xff;
+
+ int len = readLength();
+
+ if ((len < 0) || len > available())
+ throw new IOException("Illegal len in DER object (" + len + ")");
+
+ readBytes(len);
+
+ return type;
+ }
+
+ public BigInteger readInt() throws IOException
+ {
+ int type = readByte() & 0xff;
+
+ if (type != 0x02)
+ throw new IOException("Expected DER Integer, but found type " + type);
+
+ int len = readLength();
+
+ if ((len < 0) || len > available())
+ throw new IOException("Illegal len in DER object (" + len + ")");
+
+ byte[] b = readBytes(len);
+
+ BigInteger bi = new BigInteger(1, b);
+
+ return bi;
+ }
+
+ public int readConstructedType() throws IOException {
+ int type = readByte() & 0xff;
+
+ if ((type & CONSTRUCTED) != CONSTRUCTED)
+ throw new IOException("Expected constructed type, but was " + type);
+
+ return type & 0x1f;
+ }
+
+ public SimpleDERReader readConstructed() throws IOException
+ {
+ int len = readLength();
+
+ if ((len < 0) || len > available())
+ throw new IOException("Illegal len in DER object (" + len + ")");
+
+ SimpleDERReader cr = new SimpleDERReader(buffer, pos, len);
+
+ pos += len;
+ count -= len;
+
+ return cr;
+ }
+
+ public byte[] readSequenceAsByteArray() throws IOException
+ {
+ int type = readByte() & 0xff;
+
+ if (type != 0x30)
+ throw new IOException("Expected DER Sequence, but found type " + type);
+
+ int len = readLength();
+
+ if ((len < 0) || len > available())
+ throw new IOException("Illegal len in DER object (" + len + ")");
+
+ byte[] b = readBytes(len);
+
+ return b;
+ }
+
+ public String readOid() throws IOException
+ {
+ int type = readByte() & 0xff;
+
+ if (type != 0x06)
+ throw new IOException("Expected DER OID, but found type " + type);
+
+ int len = readLength();
+
+ if ((len < 1) || len > available())
+ throw new IOException("Illegal len in DER object (" + len + ")");
+
+ byte[] b = readBytes(len);
+
+ long value = 0;
+
+ StringBuilder sb = new StringBuilder(64);
+ switch (b[0] / 40) {
+ case 0:
+ sb.append('0');
+ break;
+ case 1:
+ sb.append('1');
+ b[0] -= 40;
+ break;
+ default:
+ sb.append('2');
+ b[0] -= 80;
+ break;
+ }
+
+ for (int i = 0; i < len; i++) {
+ value = (value << 7) + (b[i] & 0x7F);
+ if ((b[i] & 0x80) == 0) {
+ sb.append('.');
+ sb.append(value);
+ value = 0;
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public byte[] readOctetString() throws IOException
+ {
+ int type = readByte() & 0xff;
+
+ if (type != 0x04 && type != 0x03)
+ throw new IOException("Expected DER Octetstring, but found type " + type);
+
+ int len = readLength();
+
+ if ((len < 0) || len > available())
+ throw new IOException("Illegal len in DER object (" + len + ")");
+
+ byte[] b = readBytes(len);
+
+ return b;
+ }
+
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/AES.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/AES.java
new file mode 100644
index 0000000000..fef4fafd32
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/AES.java
@@ -0,0 +1,66 @@
+
+package com.trilead.ssh2.crypto.cipher;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * AES modes for SSH using the JCE.
+ */
+public abstract class AES implements BlockCipher
+{
+ private final int AES_BLOCK_SIZE = 16;
+
+ protected Cipher cipher;
+
+ @Override
+ public void init(boolean forEncryption, byte[] key, byte[] iv) {
+ try {
+ cipher.init(forEncryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE,
+ new SecretKeySpec(key, "AES"),
+ new IvParameterSpec(iv));
+ } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+ throw new IllegalArgumentException("Cannot initialize " + cipher.getAlgorithm(), e);
+ }
+ }
+
+ @Override
+ public int getBlockSize() {
+ return AES_BLOCK_SIZE;
+ }
+
+ @Override
+ public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) {
+ try {
+ cipher.update(src, srcoff, AES_BLOCK_SIZE, dst, dstoff);
+ } catch (ShortBufferException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ public static class CBC extends AES {
+ public CBC() throws IllegalArgumentException {
+ try {
+ cipher = Cipher.getInstance("AES/CBC/NoPadding");
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ throw new IllegalArgumentException("Cannot initialize AES/CBC/NoPadding", e);
+ }
+ }
+ }
+
+ public static class CTR extends AES {
+ public CTR() throws IllegalArgumentException {
+ try {
+ cipher = Cipher.getInstance("AES/CTR/NoPadding");
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ throw new IllegalArgumentException("Cannot initialize AES/CBC/NoPadding", e);
+ }
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipher.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipher.java
new file mode 100644
index 0000000000..7c1abd4179
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipher.java
@@ -0,0 +1,16 @@
+package com.trilead.ssh2.crypto.cipher;
+
+/**
+ * BlockCipher.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: BlockCipher.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $
+ */
+public interface BlockCipher
+{
+ void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException;
+
+ int getBlockSize();
+
+ void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff);
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipherFactory.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipherFactory.java
new file mode 100644
index 0000000000..be734aaf35
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/BlockCipherFactory.java
@@ -0,0 +1,103 @@
+
+package com.trilead.ssh2.crypto.cipher;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+
+/**
+ * BlockCipherFactory.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: BlockCipherFactory.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
+ */
+public class BlockCipherFactory
+{
+ private static class CipherEntry
+ {
+ final String type;
+ final int blocksize;
+ final int keysize;
+ final String cipherClass;
+
+ CipherEntry(String type, int blockSize, int keySize, String cipherClass)
+ {
+ this.type = type;
+ this.blocksize = blockSize;
+ this.keysize = keySize;
+ this.cipherClass = cipherClass;
+ }
+ }
+
+ private static final ArrayList ciphers = new ArrayList<>();
+
+ static
+ {
+ /* Higher Priority First */
+
+ ciphers.add(new CipherEntry("aes256-ctr", 16, 32, "com.trilead.ssh2.crypto.cipher.AES$CTR"));
+ ciphers.add(new CipherEntry("aes128-ctr", 16, 16, "com.trilead.ssh2.crypto.cipher.AES$CTR"));
+ ciphers.add(new CipherEntry("blowfish-ctr", 8, 16, "com.trilead.ssh2.crypto.cipher.BlowFish$CTR"));
+
+ ciphers.add(new CipherEntry("aes256-cbc", 16, 32, "com.trilead.ssh2.crypto.cipher.AES$CBC"));
+ ciphers.add(new CipherEntry("aes128-cbc", 16, 16, "com.trilead.ssh2.crypto.cipher.AES$CBC"));
+ ciphers.add(new CipherEntry("blowfish-cbc", 8, 16, "com.trilead.ssh2.crypto.cipher.BlowFish$CBC"));
+
+ ciphers.add(new CipherEntry("3des-ctr", 8, 24, "com.trilead.ssh2.crypto.cipher.DESede$CTR"));
+ ciphers.add(new CipherEntry("3des-cbc", 8, 24, "com.trilead.ssh2.crypto.cipher.DESede$CBC"));
+ }
+
+ public static String[] getDefaultCipherList()
+ {
+ String list[] = new String[ciphers.size()];
+ for (int i = 0; i < ciphers.size(); i++)
+ {
+ CipherEntry ce = ciphers.get(i);
+ list[i] = ce.type;
+ }
+ return list;
+ }
+
+ public static void checkCipherList(String[] cipherCandidates)
+ {
+ for (String cipherCandidate : cipherCandidates)
+ getEntry(cipherCandidate);
+ }
+
+ public static BlockCipher createCipher(String type, boolean encrypt, byte[] key, byte[] iv)
+ {
+ try
+ {
+ CipherEntry ce = getEntry(type);
+ Class cc = Class.forName(ce.cipherClass);
+ Constructor constructor = cc.getConstructor();
+ BlockCipher bc = constructor.newInstance();
+ bc.init(encrypt, key, iv);
+ return bc;
+ }
+ catch (Exception e)
+ {
+ throw new IllegalArgumentException("Cannot instantiate " + type, e);
+ }
+ }
+
+ private static CipherEntry getEntry(String type)
+ {
+ for (CipherEntry ce : ciphers) {
+ if (ce.type.equals(type))
+ return ce;
+ }
+ throw new IllegalArgumentException("Unknown algorithm " + type);
+ }
+
+ public static int getBlockSize(String type)
+ {
+ CipherEntry ce = getEntry(type);
+ return ce.blocksize;
+ }
+
+ public static int getKeySize(String type)
+ {
+ CipherEntry ce = getEntry(type);
+ return ce.keysize;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/BlowFish.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/BlowFish.java
new file mode 100644
index 0000000000..1727fc068b
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/BlowFish.java
@@ -0,0 +1,435 @@
+
+package com.trilead.ssh2.crypto.cipher;
+
+/*
+ * This file was shamelessly taken from the Bouncy Castle Crypto package.
+ * Their licence file states the following:
+ *
+ * Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle
+ * (http://www.bouncycastle.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * A class that provides Blowfish key encryption operations, such as encoding
+ * data and generating keys. All the algorithms herein are from Applied
+ * Cryptography and implement a simplified cryptography interface.
+ *
+ * @author See comments in the source file
+ * @version $Id: BlowFish.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $
+ */
+public class BlowFish implements BlockCipher
+{
+
+ private final static int[] KP = { 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0,
+ 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5,
+ 0xB5470917, 0x9216D5D9, 0x8979FB1B },
+
+ KS0 = { 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947,
+ 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658,
+ 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918,
+ 0xB8DB38EF, 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60,
+ 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 0xA15486AF,
+ 0x7C72E993, 0xB3EE1411, 0x636FBC2A, 0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E, 0xAFD6BA33, 0x6C24CF5C,
+ 0x7A325381, 0x28958677, 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, 0x61D809CC, 0xFB21A991, 0x487CAC60,
+ 0x5DEC8032, 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239,
+ 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, 0x6A51A0D2,
+ 0xD8542F68, 0x960FA728, 0xAB5133A3, 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 0xA1F1651D, 0x39AF0176,
+ 0x66CA593E, 0x82430E88, 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, 0xE06F75D8, 0x85C12073, 0x401A449F,
+ 0x56C16AA6, 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B,
+ 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, 0xC1A94FB6,
+ 0x409F60C4, 0x5E5C9EC2, 0x196A2463, 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 0x6DFC511F, 0x9B30952C,
+ 0xCC814544, 0xAF5EBD09, 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, 0xC0CBA857, 0x45C8740F, 0xD20B5F39,
+ 0xB9D3FBDB, 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279, 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8,
+ 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, 0x9E5C57BB,
+ 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 0x695B27B0, 0xBBCA58C8,
+ 0xE1FFA35D, 0xB8F011A0, 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, 0x9A53E479, 0xB6F84565, 0xD28E49BC,
+ 0x4BFB9790, 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4,
+ 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 0x8FF6E2FB,
+ 0xF2122B64, 0x8888B812, 0x900DF01C, 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 0x2F2F2218, 0xBE0E1777,
+ 0xEA752DFE, 0x8B021FA1, 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81,
+ 0xD2ADA8D9, 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF,
+ 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, 0x2464369B,
+ 0xF009B91E, 0x5563911D, 0x59DFA6AA, 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 0x83260376, 0x6295CFA9,
+ 0x11C81968, 0x4E734A41, 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, 0xD60F573F, 0xBC9BC6E4, 0x2B60A476,
+ 0x81E67400, 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664,
+ 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A },
+
+ KS1 = { 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, 0xECAA8C71,
+ 0x699A17FF, 0x5664526C, 0xC2B19EE1, 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 0x3F54989A, 0x5B429D65,
+ 0x6B8FE4D6, 0x99F73FD6, 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, 0x4CDD2086, 0x8470EB26, 0x6382E9C6,
+ 0x021ECC5E, 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737,
+ 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, 0xAE0CF51A,
+ 0x3CB574B2, 0x25837A58, 0xDC0921BD, 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 0x3AE5E581, 0x37C2DADC,
+ 0xC8B57634, 0x9AF3DDA7, 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1,
+ 0x183EB331, 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF,
+ 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, 0x7A584718,
+ 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 0xEF1C1847, 0x3215D908,
+ 0xDD433B37, 0x24C2BA16, 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, 0x71DFF89E, 0x10314E55, 0x81AC77D6,
+ 0x5F11199B, 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E,
+ 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, 0x803E89D6,
+ 0x5266C825, 0x2E4CC978, 0x9C10B36A, 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 0xF2F74EA7, 0x361D2B3D,
+ 0x1939260F, 0x19C27960, 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, 0xE3BC4595, 0xA67BC883, 0xB17F37D1,
+ 0x018CFF28, 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84,
+ 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, 0xB5735C90,
+ 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 0x648B1EAF, 0x19BDF0CA,
+ 0xA02369B9, 0x655ABB50, 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, 0x9B540B19, 0x875FA099, 0x95F7997E,
+ 0x623D7DA8, 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99,
+ 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, 0x58EBF2EF,
+ 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 0x45EEE2B6, 0xA3AAABEA,
+ 0xDB6C4F15, 0xFACB4FD0, 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, 0xD81E799E, 0x86854DC7, 0xE44B476A,
+ 0x3D816250, 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285,
+ 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, 0x3372F092,
+ 0x8D937E41, 0xD65FECF1, 0x6C223BDB, 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 0xA6078084, 0x19F8509E,
+ 0xE8EFD855, 0x61D99735, 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, 0x9E447A2E, 0xC3453484, 0xFDD56705,
+ 0x0E1E9EC9, 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20,
+ 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7 },
+
+ KS2 = { 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, 0xD4082471,
+ 0x3320F46A, 0x43B7D4B7, 0x500061AF, 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 0x4D95FC1D, 0x96B591AF,
+ 0x70F4DDD3, 0x66A02F45, 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, 0x96EB27B3, 0x55FD3941, 0xDA2547E6,
+ 0xABCA0A9A, 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE,
+ 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, 0x20FE9E35,
+ 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 0x3A6EFA74, 0xDD5B4332,
+ 0x6841E7F7, 0xCA7820FB, 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, 0x55533A3A, 0x20838D87, 0xFE6BA9B7,
+ 0xD096954B, 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C,
+ 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, 0x07F9C9EE,
+ 0x41041F0F, 0x404779A4, 0x5D886E17, 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 0x257B7834, 0x602A9C60,
+ 0xDFF8E8A3, 0x1F636C1B, 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, 0x6B2395E0, 0x333E92E1, 0x3B240B62,
+ 0xEEBEB922, 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0,
+ 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, 0xA812DC60,
+ 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 0xF1290DC7, 0xCC00FFA3,
+ 0xB5390F92, 0x690FED0B, 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, 0xBB132F88, 0x515BAD24, 0x7B9479BF,
+ 0x763BD6EB, 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C,
+ 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, 0x44421659,
+ 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 0x9DBC8057, 0xF0F7C086,
+ 0x60787BF8, 0x6003604D, 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, 0x83426B33, 0xF01EAB71, 0xB0804187,
+ 0x3C005E5F, 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2,
+ 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, 0x466E598E,
+ 0x20B45770, 0x8CD55591, 0xC902DE4C, 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 0xB77F19B6, 0xE0A9DC09,
+ 0x662D09A1, 0xC4324633, 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F,
+ 0x2868F169, 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027,
+ 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, 0x11E69ED7,
+ 0x2338EA63, 0x53C2DD94, 0xC2C21634, 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 0x6F05E409, 0x4B7C0188,
+ 0x39720A3D, 0x7C927C24, 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, 0xED545578, 0x08FCA5B5, 0xD83D7CD3,
+ 0x4DAD0FC4, 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837,
+ 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0 },
+
+ KS3 = { 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, 0xD5118E9D,
+ 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 0x5748AB2F, 0xBC946E79,
+ 0xC6A376D2, 0x6549C2C8, 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, 0x2939BBDB, 0xA9BA4650, 0xAC9526E8,
+ 0xBE5EE304, 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4,
+ 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, 0xC72FEFD3,
+ 0xF752F7DA, 0x3F046F69, 0x77FA0A59, 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 0xE990FD5A, 0x9E34D797,
+ 0x2CF0B7D9, 0x022B8B51, 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472,
+ 0x5A88F54C, 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28,
+ 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, 0xC3EB9E15,
+ 0x3C9057A2, 0x97271AEC, 0xA93A072A, 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 0x7533D928, 0xB155FDF5,
+ 0x03563482, 0x8ABA3CBB, 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, 0x4DE81751, 0x3830DC8E, 0x379D5862,
+ 0x9320F991, 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680,
+ 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, 0x5BBEF7DD,
+ 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 0x72EACEA8, 0xFA6484BB,
+ 0x8D6612AE, 0xBF3C6F47, 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, 0x740E0D8D, 0xE75B1357, 0xF8721671,
+ 0xAF537D5D, 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048,
+ 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, 0xA08839E1,
+ 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 0x1A908749, 0xD44FBD9A,
+ 0xD0DADECB, 0xD50ADA38, 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF,
+ 0x27D9459C, 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1,
+ 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, 0x9F1F9532,
+ 0xE0D392DF, 0xD3A0342B, 0x8971F21E, 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 0xDF359F8D, 0x9B992F2E,
+ 0xE60B6F47, 0x0FE3F11D, 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, 0x1618B166, 0xFD2C1D05, 0x848FD2C5,
+ 0xF6FB2299, 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC,
+ 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, 0xC9AA53FD,
+ 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 0x53113EC0, 0x1640E3D3,
+ 0x38ABBD60, 0x2547ADF0, 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0,
+ 0x4CF9AA7E, 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F,
+ 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6 };
+
+ // ====================================
+ // Useful constants
+ // ====================================
+
+ private static final int ROUNDS = 16;
+ private static final int BLOCK_SIZE = 8; // bytes = 64 bits
+ private static final int SBOX_SK = 256;
+ private static final int P_SZ = ROUNDS + 2;
+
+ private final int[] S0, S1, S2, S3; // the s-boxes
+ private final int[] P; // the p-array
+
+ private boolean doEncrypt = false;
+
+ private byte[] workingKey = null;
+
+ public BlowFish()
+ {
+ S0 = new int[SBOX_SK];
+ S1 = new int[SBOX_SK];
+ S2 = new int[SBOX_SK];
+ S3 = new int[SBOX_SK];
+ P = new int[P_SZ];
+ }
+
+ /**
+ * initialise a Blowfish cipher.
+ *
+ * @param encrypting
+ * whether or not we are for encryption.
+ * @param key
+ * the key required to set up the cipher.
+ * @param iv
+ * initial vector; not used for stream ciphers
+ * @exception IllegalArgumentException
+ * if the params argument is inappropriate.
+ */
+ @Override
+ public void init(boolean encrypting, byte[] key, byte[] iv)
+ {
+ this.doEncrypt = encrypting;
+ this.workingKey = key;
+ setKey(this.workingKey);
+ }
+
+ public String getAlgorithmName()
+ {
+ return "Blowfish";
+ }
+
+ @Override
+ public final void transformBlock(byte[] in, int inOff, byte[] out, int outOff)
+ {
+ if (workingKey == null)
+ {
+ throw new IllegalStateException("Blowfish not initialised");
+ }
+
+ if (doEncrypt)
+ {
+ encryptBlock(in, inOff, out, outOff);
+ }
+ else
+ {
+ decryptBlock(in, inOff, out, outOff);
+ }
+ }
+
+ @Override
+ public int getBlockSize()
+ {
+ return BLOCK_SIZE;
+ }
+
+ // ==================================
+ // Private Implementation
+ // ==================================
+
+ private int F(int x)
+ {
+ return (((S0[(x >>> 24)] + S1[(x >>> 16) & 0xff]) ^ S2[(x >>> 8) & 0xff]) + S3[x & 0xff]);
+ }
+
+ /**
+ * apply the encryption cycle to each value pair in the table.
+ */
+ private void processTable(int xl, int xr, int[] table)
+ {
+ int size = table.length;
+
+ for (int s = 0; s < size; s += 2)
+ {
+ xl ^= P[0];
+
+ for (int i = 1; i < ROUNDS; i += 2)
+ {
+ xr ^= F(xl) ^ P[i];
+ xl ^= F(xr) ^ P[i + 1];
+ }
+
+ xr ^= P[ROUNDS + 1];
+
+ table[s] = xr;
+ table[s + 1] = xl;
+
+ xr = xl; // end of cycle swap
+ xl = table[s];
+ }
+ }
+
+ private void setKey(byte[] key)
+ {
+ /*
+ * - comments are from _Applied Crypto_, Schneier, p338 please be
+ * careful comparing the two, AC numbers the arrays from 1, the enclosed
+ * code from 0.
+ *
+ * (1) Initialise the S-boxes and the P-array, with a fixed string This
+ * string contains the hexadecimal digits of pi (3.141...)
+ */
+ System.arraycopy(KS0, 0, S0, 0, SBOX_SK);
+ System.arraycopy(KS1, 0, S1, 0, SBOX_SK);
+ System.arraycopy(KS2, 0, S2, 0, SBOX_SK);
+ System.arraycopy(KS3, 0, S3, 0, SBOX_SK);
+
+ System.arraycopy(KP, 0, P, 0, P_SZ);
+
+ /*
+ * (2) Now, XOR P[0] with the first 32 bits of the key, XOR P[1] with
+ * the second 32-bits of the key, and so on for all bits of the key (up
+ * to P[17]). Repeatedly cycle through the key bits until the entire
+ * P-array has been XOR-ed with the key bits
+ */
+ int keyLength = key.length;
+ int keyIndex = 0;
+
+ for (int i = 0; i < P_SZ; i++)
+ {
+ // get the 32 bits of the key, in 4 * 8 bit chunks
+ int data = 0x0000000;
+ for (int j = 0; j < 4; j++)
+ {
+ // create a 32 bit block
+ data = (data << 8) | (key[keyIndex++] & 0xff);
+
+ // wrap when we get to the end of the key
+ if (keyIndex >= keyLength)
+ {
+ keyIndex = 0;
+ }
+ }
+ // XOR the newly created 32 bit chunk onto the P-array
+ P[i] ^= data;
+ }
+
+ /*
+ * (3) Encrypt the all-zero string with the Blowfish algorithm, using
+ * the subkeys described in (1) and (2)
+ *
+ * (4) Replace P1 and P2 with the output of step (3)
+ *
+ * (5) Encrypt the output of step(3) using the Blowfish algorithm, with
+ * the modified subkeys.
+ *
+ * (6) Replace P3 and P4 with the output of step (5)
+ *
+ * (7) Continue the process, replacing all elements of the P-array and
+ * then all four S-boxes in order, with the output of the continuously
+ * changing Blowfish algorithm
+ */
+
+ processTable(0, 0, P);
+ processTable(P[P_SZ - 2], P[P_SZ - 1], S0);
+ processTable(S0[SBOX_SK - 2], S0[SBOX_SK - 1], S1);
+ processTable(S1[SBOX_SK - 2], S1[SBOX_SK - 1], S2);
+ processTable(S2[SBOX_SK - 2], S2[SBOX_SK - 1], S3);
+ }
+
+ /**
+ * Encrypt the given input starting at the given offset and place the result
+ * in the provided buffer starting at the given offset. The input will be an
+ * exact multiple of our blocksize.
+ */
+ private void encryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex)
+ {
+ int xl = BytesTo32bits(src, srcIndex);
+ int xr = BytesTo32bits(src, srcIndex + 4);
+
+ xl ^= P[0];
+
+ for (int i = 1; i < ROUNDS; i += 2)
+ {
+ xr ^= F(xl) ^ P[i];
+ xl ^= F(xr) ^ P[i + 1];
+ }
+
+ xr ^= P[ROUNDS + 1];
+
+ Bits32ToBytes(xr, dst, dstIndex);
+ Bits32ToBytes(xl, dst, dstIndex + 4);
+ }
+
+ /**
+ * Decrypt the given input starting at the given offset and place the result
+ * in the provided buffer starting at the given offset. The input will be an
+ * exact multiple of our blocksize.
+ */
+ private void decryptBlock(byte[] src, int srcIndex, byte[] dst, int dstIndex)
+ {
+ int xl = BytesTo32bits(src, srcIndex);
+ int xr = BytesTo32bits(src, srcIndex + 4);
+
+ xl ^= P[ROUNDS + 1];
+
+ for (int i = ROUNDS; i > 0; i -= 2)
+ {
+ xr ^= F(xl) ^ P[i];
+ xl ^= F(xr) ^ P[i - 1];
+ }
+
+ xr ^= P[0];
+
+ Bits32ToBytes(xr, dst, dstIndex);
+ Bits32ToBytes(xl, dst, dstIndex + 4);
+ }
+
+ private int BytesTo32bits(byte[] b, int i)
+ {
+ return ((b[i] & 0xff) << 24) | ((b[i + 1] & 0xff) << 16) | ((b[i + 2] & 0xff) << 8) | ((b[i + 3] & 0xff));
+ }
+
+ private void Bits32ToBytes(int in, byte[] b, int offset)
+ {
+ b[offset + 3] = (byte) in;
+ b[offset + 2] = (byte) (in >> 8);
+ b[offset + 1] = (byte) (in >> 16);
+ b[offset] = (byte) (in >> 24);
+ }
+
+ private abstract static class Wrapper implements BlockCipher {
+ protected BlockCipher bc;
+
+ @Override
+ public int getBlockSize() {
+ return bc.getBlockSize();
+ }
+
+ @Override
+ public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) {
+ bc.transformBlock(src, srcoff, dst, dstoff);
+ }
+ }
+
+ public static class CBC extends Wrapper {
+ @Override
+ public void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException {
+ BlockCipher rawCipher = new BlowFish();
+ rawCipher.init(forEncryption, key, iv);
+ bc = new CBCMode(rawCipher, iv, forEncryption);
+ }
+ }
+
+ public static class CTR extends Wrapper {
+ @Override
+ public void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException {
+ BlockCipher rawCipher = new BlowFish();
+ rawCipher.init(true, key, iv);
+ bc = new CTRMode(rawCipher, iv, forEncryption);
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CBCMode.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CBCMode.java
new file mode 100644
index 0000000000..078aacdc37
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CBCMode.java
@@ -0,0 +1,78 @@
+package com.trilead.ssh2.crypto.cipher;
+
+/**
+ * CBCMode.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: CBCMode.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $
+ */
+public class CBCMode implements BlockCipher
+{
+ BlockCipher tc;
+ int blockSize;
+ boolean doEncrypt;
+
+ byte[] cbc_vector;
+ byte[] tmp_vector;
+
+ public void init(boolean forEncryption, byte[] key, byte[] iv)
+ {
+ }
+
+ public CBCMode(BlockCipher tc, byte[] iv, boolean doEncrypt)
+ throws IllegalArgumentException
+ {
+ this.tc = tc;
+ this.blockSize = tc.getBlockSize();
+ this.doEncrypt = doEncrypt;
+
+ if (this.blockSize != iv.length)
+ throw new IllegalArgumentException("IV must be " + blockSize
+ + " bytes long! (currently " + iv.length + ")");
+
+ this.cbc_vector = new byte[blockSize];
+ this.tmp_vector = new byte[blockSize];
+ System.arraycopy(iv, 0, cbc_vector, 0, blockSize);
+ }
+
+ public int getBlockSize()
+ {
+ return blockSize;
+ }
+
+ private void encryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff)
+ {
+ for (int i = 0; i < blockSize; i++)
+ cbc_vector[i] ^= src[srcoff + i];
+
+ tc.transformBlock(cbc_vector, 0, dst, dstoff);
+
+ System.arraycopy(dst, dstoff, cbc_vector, 0, blockSize);
+ }
+
+ private void decryptBlock(byte[] src, int srcoff, byte[] dst, int dstoff)
+ {
+ /* Assume the worst, src and dst are overlapping... */
+
+ System.arraycopy(src, srcoff, tmp_vector, 0, blockSize);
+
+ tc.transformBlock(src, srcoff, dst, dstoff);
+
+ for (int i = 0; i < blockSize; i++)
+ dst[dstoff + i] ^= cbc_vector[i];
+
+ /* ...that is why we need a tmp buffer. */
+
+ byte[] swap = cbc_vector;
+ cbc_vector = tmp_vector;
+ tmp_vector = swap;
+ }
+
+ public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff)
+ {
+ if (doEncrypt)
+ encryptBlock(src, srcoff, dst, dstoff);
+ else
+ decryptBlock(src, srcoff, dst, dstoff);
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CTRMode.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CTRMode.java
new file mode 100644
index 0000000000..2c68f4abaa
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CTRMode.java
@@ -0,0 +1,62 @@
+
+package com.trilead.ssh2.crypto.cipher;
+
+/**
+ * This is CTR mode as described in draft-ietf-secsh-newmodes-XY.txt
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: CTRMode.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $
+ */
+public class CTRMode implements BlockCipher
+{
+ byte[] X;
+ byte[] Xenc;
+
+ BlockCipher bc;
+ int blockSize;
+ boolean doEncrypt;
+
+ int count = 0;
+
+ public void init(boolean forEncryption, byte[] key, byte[] iv)
+ {
+ }
+
+ public CTRMode(BlockCipher tc, byte[] iv, boolean doEnc) throws IllegalArgumentException
+ {
+ bc = tc;
+ blockSize = bc.getBlockSize();
+ doEncrypt = doEnc;
+
+ if (blockSize != iv.length)
+ throw new IllegalArgumentException("IV must be " + blockSize + " bytes long! (currently " + iv.length + ")");
+
+ X = new byte[blockSize];
+ Xenc = new byte[blockSize];
+
+ System.arraycopy(iv, 0, X, 0, blockSize);
+ }
+
+ public final int getBlockSize()
+ {
+ return blockSize;
+ }
+
+ public final void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff)
+ {
+ bc.transformBlock(X, 0, Xenc, 0);
+
+ for (int i = 0; i < blockSize; i++)
+ {
+ dst[dstoff + i] = (byte) (src[srcoff + i] ^ Xenc[i]);
+ }
+
+ for (int i = (blockSize - 1); i >= 0; i--)
+ {
+ X[i]++;
+ if (X[i] != 0)
+ break;
+
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CipherInputStream.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CipherInputStream.java
new file mode 100644
index 0000000000..851cc2843a
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CipherInputStream.java
@@ -0,0 +1,133 @@
+
+package com.trilead.ssh2.crypto.cipher;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * CipherInputStream.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: CipherInputStream.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $
+ */
+public class CipherInputStream
+{
+ private BlockCipher currentCipher;
+ private final BufferedInputStream bi;
+ private byte[] buffer;
+ private byte[] enc;
+ private int blockSize;
+ private int pos;
+
+ public CipherInputStream(BlockCipher tc, InputStream bi)
+ {
+ if (bi instanceof BufferedInputStream) {
+ this.bi = (BufferedInputStream) bi;
+ } else {
+ this.bi = new BufferedInputStream(bi);
+ }
+ changeCipher(tc);
+ }
+
+ public void changeCipher(BlockCipher bc)
+ {
+ this.currentCipher = bc;
+ blockSize = bc.getBlockSize();
+ buffer = new byte[blockSize];
+ enc = new byte[blockSize];
+ pos = blockSize;
+ }
+
+ private void getBlock() throws IOException
+ {
+ int n = 0;
+ while (n < blockSize)
+ {
+ int len = bi.read(enc, n, blockSize - n);
+ if (len < 0)
+ throw new IOException("Cannot read full block, EOF reached.");
+ n += len;
+ }
+
+ try
+ {
+ currentCipher.transformBlock(enc, 0, buffer, 0);
+ }
+ catch (Exception e)
+ {
+ throw new IOException("Error while decrypting block.");
+ }
+ pos = 0;
+ }
+
+ public int read(byte[] dst) throws IOException
+ {
+ return read(dst, 0, dst.length);
+ }
+
+ public int read(byte[] dst, int off, int len) throws IOException
+ {
+ int count = 0;
+
+ while (len > 0)
+ {
+ if (pos >= blockSize)
+ getBlock();
+
+ int avail = blockSize - pos;
+ int copy = Math.min(avail, len);
+ System.arraycopy(buffer, pos, dst, off, copy);
+ pos += copy;
+ off += copy;
+ len -= copy;
+ count += copy;
+ }
+ return count;
+ }
+
+ public int read() throws IOException
+ {
+ if (pos >= blockSize)
+ {
+ getBlock();
+ }
+ return buffer[pos++] & 0xff;
+ }
+
+ public int readPlain(byte[] b, int off, int len) throws IOException
+ {
+ if (pos != blockSize)
+ throw new IOException("Cannot read plain since crypto buffer is not aligned.");
+ int n = 0;
+ while (n < len)
+ {
+ int cnt = bi.read(b, off + n, len - n);
+ if (cnt < 0)
+ throw new IOException("Cannot fill buffer, EOF reached.");
+ n += cnt;
+ }
+ return n;
+ }
+
+ public int peekPlain(byte[] b, int off, int len) throws IOException
+ {
+ if (pos != blockSize)
+ throw new IOException("Cannot read plain since crypto buffer is not aligned.");
+ int n = 0;
+
+ bi.mark(len);
+ try {
+ while (n < len) {
+ int cnt = bi.read(b, off + n, len - n);
+ if (cnt < 0)
+ throw new IOException("Cannot fill buffer, EOF reached.");
+ n += cnt;
+ }
+ } finally {
+ bi.reset();
+ }
+
+ return n;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CipherOutputStream.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CipherOutputStream.java
new file mode 100644
index 0000000000..b01ec3a45d
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/CipherOutputStream.java
@@ -0,0 +1,120 @@
+
+package com.trilead.ssh2.crypto.cipher;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * CipherOutputStream.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: CipherOutputStream.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $
+ */
+public class CipherOutputStream
+{
+ private BlockCipher currentCipher;
+ private final BufferedOutputStream bo;
+ private byte[] buffer;
+ private byte[] enc;
+ private int blockSize;
+ private int pos;
+ private boolean recordingOutput;
+ private final ByteArrayOutputStream recordingOutputStream = new ByteArrayOutputStream();
+
+ public CipherOutputStream(BlockCipher tc, OutputStream bo)
+ {
+ if (bo instanceof BufferedOutputStream) {
+ this.bo = (BufferedOutputStream) bo;
+ } else {
+ this.bo = new BufferedOutputStream(bo);
+ }
+ changeCipher(tc);
+ }
+
+ public void flush() throws IOException
+ {
+ if (pos != 0)
+ throw new IOException("FATAL: cannot flush since crypto buffer is not aligned.");
+
+ bo.flush();
+ }
+
+ public void changeCipher(BlockCipher bc)
+ {
+ this.currentCipher = bc;
+ blockSize = bc.getBlockSize();
+ buffer = new byte[blockSize];
+ enc = new byte[blockSize];
+ pos = 0;
+ }
+
+ public void startRecording() {
+ recordingOutput = true;
+ }
+
+ public byte[] getRecordedOutput() {
+ recordingOutput = false;
+ byte[] recordedOutput = recordingOutputStream.toByteArray();
+ recordingOutputStream.reset();
+ return recordedOutput;
+ }
+
+ private void writeBlock() throws IOException
+ {
+ try
+ {
+ currentCipher.transformBlock(buffer, 0, enc, 0);
+ }
+ catch (Exception e)
+ {
+ throw new IOException("Error while decrypting block.", e);
+ }
+
+ bo.write(enc, 0, blockSize);
+ pos = 0;
+
+ if (recordingOutput) {
+ recordingOutputStream.write(enc, 0, blockSize);
+ }
+ }
+
+ public void write(byte[] src, int off, int len) throws IOException
+ {
+ while (len > 0)
+ {
+ int avail = blockSize - pos;
+ int copy = Math.min(avail, len);
+
+ System.arraycopy(src, off, buffer, pos, copy);
+ pos += copy;
+ off += copy;
+ len -= copy;
+
+ if (pos >= blockSize)
+ writeBlock();
+ }
+ }
+
+ public void write(int b) throws IOException
+ {
+ buffer[pos++] = (byte) b;
+ if (pos >= blockSize)
+ writeBlock();
+ }
+
+ public void writePlain(int b) throws IOException
+ {
+ if (pos != 0)
+ throw new IOException("Cannot write plain since crypto buffer is not aligned.");
+ bo.write(b);
+ }
+
+ public void writePlain(byte[] b, int off, int len) throws IOException
+ {
+ if (pos != 0)
+ throw new IOException("Cannot write plain since crypto buffer is not aligned.");
+ bo.write(b, off, len);
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/DES.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/DES.java
new file mode 100644
index 0000000000..81caa9944b
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/DES.java
@@ -0,0 +1,392 @@
+
+package com.trilead.ssh2.crypto.cipher;
+
+/*
+ * This file is based on the 3DES implementation from the Bouncy Castle Crypto package.
+ * Their licence file states the following:
+ *
+ * Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle
+ * (http://www.bouncycastle.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * DES.
+ *
+ * @author See comments in the source file
+ * @version $Id: DES.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $
+ *
+ */
+public class DES implements BlockCipher
+{
+ private int[] workingKey = null;
+
+ /**
+ * standard constructor.
+ */
+ public DES()
+ {
+ }
+
+ /**
+ * initialise a DES cipher.
+ *
+ * @param encrypting
+ * whether or not we are for encryption.
+ * @param key
+ * the parameters required to set up the cipher.
+ * @exception IllegalArgumentException
+ * if the params argument is inappropriate.
+ */
+ @Override
+ public void init(boolean encrypting, byte[] key, byte[] iv)
+ {
+ this.workingKey = generateWorkingKey(encrypting, key, 0);
+ }
+
+ public String getAlgorithmName()
+ {
+ return "DES";
+ }
+
+ @Override
+ public int getBlockSize()
+ {
+ return 8;
+ }
+
+ public void transformBlock(byte[] in, int inOff, byte[] out, int outOff)
+ {
+ if (workingKey == null)
+ {
+ throw new IllegalStateException("DES engine not initialised!");
+ }
+
+ desFunc(workingKey, in, inOff, out, outOff);
+ }
+
+ /**
+ * what follows is mainly taken from "Applied Cryptography", by Bruce
+ * Schneier, however it also bears great resemblance to Richard
+ * Outerbridge's D3DES...
+ */
+
+ static short[] Df_Key = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32,
+ 0x10, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67 };
+
+ static short[] bytebit = { 0200, 0100, 040, 020, 010, 04, 02, 01 };
+
+ static int[] bigbyte = { 0x800000, 0x400000, 0x200000, 0x100000, 0x80000, 0x40000, 0x20000, 0x10000, 0x8000,
+ 0x4000, 0x2000, 0x1000, 0x800, 0x400, 0x200, 0x100, 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 };
+
+ /*
+ * Use the key schedule specified in the Standard (ANSI X3.92-1981).
+ */
+
+ static byte[] pc1 = { 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2,
+ 59, 51, 43, 35, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 60, 52, 44, 36, 28, 20, 12,
+ 4, 27, 19, 11, 3 };
+
+ static byte[] totrot = { 1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28 };
+
+ static byte[] pc2 = { 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, 40,
+ 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 };
+
+ static int[] SP1 = { 0x01010400, 0x00000000, 0x00010000, 0x01010404, 0x01010004, 0x00010404, 0x00000004,
+ 0x00010000, 0x00000400, 0x01010400, 0x01010404, 0x00000400, 0x01000404, 0x01010004, 0x01000000, 0x00000004,
+ 0x00000404, 0x01000400, 0x01000400, 0x00010400, 0x00010400, 0x01010000, 0x01010000, 0x01000404, 0x00010004,
+ 0x01000004, 0x01000004, 0x00010004, 0x00000000, 0x00000404, 0x00010404, 0x01000000, 0x00010000, 0x01010404,
+ 0x00000004, 0x01010000, 0x01010400, 0x01000000, 0x01000000, 0x00000400, 0x01010004, 0x00010000, 0x00010400,
+ 0x01000004, 0x00000400, 0x00000004, 0x01000404, 0x00010404, 0x01010404, 0x00010004, 0x01010000, 0x01000404,
+ 0x01000004, 0x00000404, 0x00010404, 0x01010400, 0x00000404, 0x01000400, 0x01000400, 0x00000000, 0x00010004,
+ 0x00010400, 0x00000000, 0x01010004 };
+
+ static int[] SP2 = { 0x80108020, 0x80008000, 0x00008000, 0x00108020, 0x00100000, 0x00000020, 0x80100020,
+ 0x80008020, 0x80000020, 0x80108020, 0x80108000, 0x80000000, 0x80008000, 0x00100000, 0x00000020, 0x80100020,
+ 0x00108000, 0x00100020, 0x80008020, 0x00000000, 0x80000000, 0x00008000, 0x00108020, 0x80100000, 0x00100020,
+ 0x80000020, 0x00000000, 0x00108000, 0x00008020, 0x80108000, 0x80100000, 0x00008020, 0x00000000, 0x00108020,
+ 0x80100020, 0x00100000, 0x80008020, 0x80100000, 0x80108000, 0x00008000, 0x80100000, 0x80008000, 0x00000020,
+ 0x80108020, 0x00108020, 0x00000020, 0x00008000, 0x80000000, 0x00008020, 0x80108000, 0x00100000, 0x80000020,
+ 0x00100020, 0x80008020, 0x80000020, 0x00100020, 0x00108000, 0x00000000, 0x80008000, 0x00008020, 0x80000000,
+ 0x80100020, 0x80108020, 0x00108000 };
+
+ static int[] SP3 = { 0x00000208, 0x08020200, 0x00000000, 0x08020008, 0x08000200, 0x00000000, 0x00020208,
+ 0x08000200, 0x00020008, 0x08000008, 0x08000008, 0x00020000, 0x08020208, 0x00020008, 0x08020000, 0x00000208,
+ 0x08000000, 0x00000008, 0x08020200, 0x00000200, 0x00020200, 0x08020000, 0x08020008, 0x00020208, 0x08000208,
+ 0x00020200, 0x00020000, 0x08000208, 0x00000008, 0x08020208, 0x00000200, 0x08000000, 0x08020200, 0x08000000,
+ 0x00020008, 0x00000208, 0x00020000, 0x08020200, 0x08000200, 0x00000000, 0x00000200, 0x00020008, 0x08020208,
+ 0x08000200, 0x08000008, 0x00000200, 0x00000000, 0x08020008, 0x08000208, 0x00020000, 0x08000000, 0x08020208,
+ 0x00000008, 0x00020208, 0x00020200, 0x08000008, 0x08020000, 0x08000208, 0x00000208, 0x08020000, 0x00020208,
+ 0x00000008, 0x08020008, 0x00020200 };
+
+ static int[] SP4 = { 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802080, 0x00800081, 0x00800001,
+ 0x00002001, 0x00000000, 0x00802000, 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00800080, 0x00800001,
+ 0x00000001, 0x00002000, 0x00800000, 0x00802001, 0x00000080, 0x00800000, 0x00002001, 0x00002080, 0x00800081,
+ 0x00000001, 0x00002080, 0x00800080, 0x00002000, 0x00802080, 0x00802081, 0x00000081, 0x00800080, 0x00800001,
+ 0x00802000, 0x00802081, 0x00000081, 0x00000000, 0x00000000, 0x00802000, 0x00002080, 0x00800080, 0x00800081,
+ 0x00000001, 0x00802001, 0x00002081, 0x00002081, 0x00000080, 0x00802081, 0x00000081, 0x00000001, 0x00002000,
+ 0x00800001, 0x00002001, 0x00802080, 0x00800081, 0x00002001, 0x00002080, 0x00800000, 0x00802001, 0x00000080,
+ 0x00800000, 0x00002000, 0x00802080 };
+
+ static int[] SP5 = { 0x00000100, 0x02080100, 0x02080000, 0x42000100, 0x00080000, 0x00000100, 0x40000000,
+ 0x02080000, 0x40080100, 0x00080000, 0x02000100, 0x40080100, 0x42000100, 0x42080000, 0x00080100, 0x40000000,
+ 0x02000000, 0x40080000, 0x40080000, 0x00000000, 0x40000100, 0x42080100, 0x42080100, 0x02000100, 0x42080000,
+ 0x40000100, 0x00000000, 0x42000000, 0x02080100, 0x02000000, 0x42000000, 0x00080100, 0x00080000, 0x42000100,
+ 0x00000100, 0x02000000, 0x40000000, 0x02080000, 0x42000100, 0x40080100, 0x02000100, 0x40000000, 0x42080000,
+ 0x02080100, 0x40080100, 0x00000100, 0x02000000, 0x42080000, 0x42080100, 0x00080100, 0x42000000, 0x42080100,
+ 0x02080000, 0x00000000, 0x40080000, 0x42000000, 0x00080100, 0x02000100, 0x40000100, 0x00080000, 0x00000000,
+ 0x40080000, 0x02080100, 0x40000100 };
+
+ static int[] SP6 = { 0x20000010, 0x20400000, 0x00004000, 0x20404010, 0x20400000, 0x00000010, 0x20404010,
+ 0x00400000, 0x20004000, 0x00404010, 0x00400000, 0x20000010, 0x00400010, 0x20004000, 0x20000000, 0x00004010,
+ 0x00000000, 0x00400010, 0x20004010, 0x00004000, 0x00404000, 0x20004010, 0x00000010, 0x20400010, 0x20400010,
+ 0x00000000, 0x00404010, 0x20404000, 0x00004010, 0x00404000, 0x20404000, 0x20000000, 0x20004000, 0x00000010,
+ 0x20400010, 0x00404000, 0x20404010, 0x00400000, 0x00004010, 0x20000010, 0x00400000, 0x20004000, 0x20000000,
+ 0x00004010, 0x20000010, 0x20404010, 0x00404000, 0x20400000, 0x00404010, 0x20404000, 0x00000000, 0x20400010,
+ 0x00000010, 0x00004000, 0x20400000, 0x00404010, 0x00004000, 0x00400010, 0x20004010, 0x00000000, 0x20404000,
+ 0x20000000, 0x00400010, 0x20004010 };
+
+ static int[] SP7 = { 0x00200000, 0x04200002, 0x04000802, 0x00000000, 0x00000800, 0x04000802, 0x00200802,
+ 0x04200800, 0x04200802, 0x00200000, 0x00000000, 0x04000002, 0x00000002, 0x04000000, 0x04200002, 0x00000802,
+ 0x04000800, 0x00200802, 0x00200002, 0x04000800, 0x04000002, 0x04200000, 0x04200800, 0x00200002, 0x04200000,
+ 0x00000800, 0x00000802, 0x04200802, 0x00200800, 0x00000002, 0x04000000, 0x00200800, 0x04000000, 0x00200800,
+ 0x00200000, 0x04000802, 0x04000802, 0x04200002, 0x04200002, 0x00000002, 0x00200002, 0x04000000, 0x04000800,
+ 0x00200000, 0x04200800, 0x00000802, 0x00200802, 0x04200800, 0x00000802, 0x04000002, 0x04200802, 0x04200000,
+ 0x00200800, 0x00000000, 0x00000002, 0x04200802, 0x00000000, 0x00200802, 0x04200000, 0x00000800, 0x04000002,
+ 0x04000800, 0x00000800, 0x00200002 };
+
+ static int[] SP8 = { 0x10001040, 0x00001000, 0x00040000, 0x10041040, 0x10000000, 0x10001040, 0x00000040,
+ 0x10000000, 0x00040040, 0x10040000, 0x10041040, 0x00041000, 0x10041000, 0x00041040, 0x00001000, 0x00000040,
+ 0x10040000, 0x10000040, 0x10001000, 0x00001040, 0x00041000, 0x00040040, 0x10040040, 0x10041000, 0x00001040,
+ 0x00000000, 0x00000000, 0x10040040, 0x10000040, 0x10001000, 0x00041040, 0x00040000, 0x00041040, 0x00040000,
+ 0x10041000, 0x00001000, 0x00000040, 0x10040040, 0x00001000, 0x00041040, 0x10001000, 0x00000040, 0x10000040,
+ 0x10040000, 0x10040040, 0x10000000, 0x00040000, 0x10001040, 0x00000000, 0x10041040, 0x00040040, 0x10000040,
+ 0x10040000, 0x10001000, 0x10001040, 0x00000000, 0x10041040, 0x00041000, 0x00041000, 0x00001040, 0x00001040,
+ 0x00040040, 0x10000000, 0x10041000 };
+
+ /**
+ * generate an integer based working key based on our secret key and what we
+ * processing we are planning to do.
+ *
+ * Acknowledgements for this routine go to James Gillogly & Phil Karn.
+ * (whoever, and wherever they are!).
+ */
+ protected int[] generateWorkingKey(boolean encrypting, byte[] key, int off)
+ {
+ int[] newKey = new int[32];
+ boolean[] pc1m = new boolean[56], pcr = new boolean[56];
+
+ for (int j = 0; j < 56; j++)
+ {
+ int l = pc1[j];
+
+ pc1m[j] = ((key[off + (l >>> 3)] & bytebit[l & 07]) != 0);
+ }
+
+ for (int i = 0; i < 16; i++)
+ {
+ int l, m, n;
+
+ if (encrypting)
+ {
+ m = i << 1;
+ }
+ else
+ {
+ m = (15 - i) << 1;
+ }
+
+ n = m + 1;
+ newKey[m] = newKey[n] = 0;
+
+ for (int j = 0; j < 28; j++)
+ {
+ l = j + totrot[i];
+ if (l < 28)
+ {
+ pcr[j] = pc1m[l];
+ }
+ else
+ {
+ pcr[j] = pc1m[l - 28];
+ }
+ }
+
+ for (int j = 28; j < 56; j++)
+ {
+ l = j + totrot[i];
+ if (l < 56)
+ {
+ pcr[j] = pc1m[l];
+ }
+ else
+ {
+ pcr[j] = pc1m[l - 28];
+ }
+ }
+
+ for (int j = 0; j < 24; j++)
+ {
+ if (pcr[pc2[j]])
+ {
+ newKey[m] |= bigbyte[j];
+ }
+
+ if (pcr[pc2[j + 24]])
+ {
+ newKey[n] |= bigbyte[j];
+ }
+ }
+ }
+
+ //
+ // store the processed key
+ //
+ for (int i = 0; i != 32; i += 2)
+ {
+ int i1, i2;
+
+ i1 = newKey[i];
+ i2 = newKey[i + 1];
+
+ newKey[i] = ((i1 & 0x00fc0000) << 6) | ((i1 & 0x00000fc0) << 10) | ((i2 & 0x00fc0000) >>> 10)
+ | ((i2 & 0x00000fc0) >>> 6);
+
+ newKey[i + 1] = ((i1 & 0x0003f000) << 12) | ((i1 & 0x0000003f) << 16) | ((i2 & 0x0003f000) >>> 4)
+ | (i2 & 0x0000003f);
+ }
+
+ return newKey;
+ }
+
+ /**
+ * the DES engine.
+ */
+ protected void desFunc(int[] wKey, byte[] in, int inOff, byte[] out, int outOff)
+ {
+ int work, right, left;
+
+ left = (in[inOff + 0] & 0xff) << 24;
+ left |= (in[inOff + 1] & 0xff) << 16;
+ left |= (in[inOff + 2] & 0xff) << 8;
+ left |= (in[inOff + 3] & 0xff);
+
+ right = (in[inOff + 4] & 0xff) << 24;
+ right |= (in[inOff + 5] & 0xff) << 16;
+ right |= (in[inOff + 6] & 0xff) << 8;
+ right |= (in[inOff + 7] & 0xff);
+
+ work = ((left >>> 4) ^ right) & 0x0f0f0f0f;
+ right ^= work;
+ left ^= (work << 4);
+ work = ((left >>> 16) ^ right) & 0x0000ffff;
+ right ^= work;
+ left ^= (work << 16);
+ work = ((right >>> 2) ^ left) & 0x33333333;
+ left ^= work;
+ right ^= (work << 2);
+ work = ((right >>> 8) ^ left) & 0x00ff00ff;
+ left ^= work;
+ right ^= (work << 8);
+ right = ((right << 1) | ((right >>> 31) & 1)) & 0xffffffff;
+ work = (left ^ right) & 0xaaaaaaaa;
+ left ^= work;
+ right ^= work;
+ left = ((left << 1) | ((left >>> 31) & 1)) & 0xffffffff;
+
+ for (int round = 0; round < 8; round++)
+ {
+ int fval;
+
+ work = (right << 28) | (right >>> 4);
+ work ^= wKey[round * 4 + 0];
+ fval = SP7[work & 0x3f];
+ fval |= SP5[(work >>> 8) & 0x3f];
+ fval |= SP3[(work >>> 16) & 0x3f];
+ fval |= SP1[(work >>> 24) & 0x3f];
+ work = right ^ wKey[round * 4 + 1];
+ fval |= SP8[work & 0x3f];
+ fval |= SP6[(work >>> 8) & 0x3f];
+ fval |= SP4[(work >>> 16) & 0x3f];
+ fval |= SP2[(work >>> 24) & 0x3f];
+ left ^= fval;
+ work = (left << 28) | (left >>> 4);
+ work ^= wKey[round * 4 + 2];
+ fval = SP7[work & 0x3f];
+ fval |= SP5[(work >>> 8) & 0x3f];
+ fval |= SP3[(work >>> 16) & 0x3f];
+ fval |= SP1[(work >>> 24) & 0x3f];
+ work = left ^ wKey[round * 4 + 3];
+ fval |= SP8[work & 0x3f];
+ fval |= SP6[(work >>> 8) & 0x3f];
+ fval |= SP4[(work >>> 16) & 0x3f];
+ fval |= SP2[(work >>> 24) & 0x3f];
+ right ^= fval;
+ }
+
+ right = (right << 31) | (right >>> 1);
+ work = (left ^ right) & 0xaaaaaaaa;
+ left ^= work;
+ right ^= work;
+ left = (left << 31) | (left >>> 1);
+ work = ((left >>> 8) ^ right) & 0x00ff00ff;
+ right ^= work;
+ left ^= (work << 8);
+ work = ((left >>> 2) ^ right) & 0x33333333;
+ right ^= work;
+ left ^= (work << 2);
+ work = ((right >>> 16) ^ left) & 0x0000ffff;
+ left ^= work;
+ right ^= (work << 16);
+ work = ((right >>> 4) ^ left) & 0x0f0f0f0f;
+ left ^= work;
+ right ^= (work << 4);
+
+ out[outOff + 0] = (byte) ((right >>> 24) & 0xff);
+ out[outOff + 1] = (byte) ((right >>> 16) & 0xff);
+ out[outOff + 2] = (byte) ((right >>> 8) & 0xff);
+ out[outOff + 3] = (byte) (right & 0xff);
+ out[outOff + 4] = (byte) ((left >>> 24) & 0xff);
+ out[outOff + 5] = (byte) ((left >>> 16) & 0xff);
+ out[outOff + 6] = (byte) ((left >>> 8) & 0xff);
+ out[outOff + 7] = (byte) (left & 0xff);
+ }
+
+ public static class CBC implements BlockCipher {
+ protected BlockCipher bc;
+
+ @Override
+ public void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException {
+ BlockCipher rawCipher = new DESede();
+ rawCipher.init(forEncryption, key, iv);
+ bc = new CBCMode(rawCipher, iv, forEncryption);
+ }
+
+ @Override
+ public int getBlockSize() {
+ return bc.getBlockSize();
+ }
+
+ @Override
+ public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) {
+ bc.transformBlock(src, srcoff, dst, dstoff);
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/DESede.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/DESede.java
new file mode 100644
index 0000000000..480a7fe640
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/DESede.java
@@ -0,0 +1,130 @@
+
+package com.trilead.ssh2.crypto.cipher;
+
+/*
+ * This file was shamelessly taken (and modified) from the Bouncy Castle Crypto package.
+ * Their licence file states the following:
+ *
+ * Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle
+ * (http://www.bouncycastle.org)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * DESede.
+ *
+ * @author See comments in the source file
+ * @version $Id: DESede.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $
+ *
+ */
+public class DESede extends DES
+{
+ private int[] key1 = null;
+ private int[] key2 = null;
+ private int[] key3 = null;
+
+ private boolean encrypt;
+
+ /**
+ * standard constructor.
+ */
+ public DESede()
+ {
+ }
+
+ /**
+ * initialise a DES cipher.
+ *
+ * @param encrypting
+ * whether or not we are for encryption.
+ * @param key
+ * the parameters required to set up the cipher.
+ * @exception IllegalArgumentException
+ * if the params argument is inappropriate.
+ */
+ @Override
+ public void init(boolean encrypting, byte[] key, byte[] iv)
+ {
+ key1 = generateWorkingKey(encrypting, key, 0);
+ key2 = generateWorkingKey(!encrypting, key, 8);
+ key3 = generateWorkingKey(encrypting, key, 16);
+
+ encrypt = encrypting;
+ }
+
+ public String getAlgorithmName()
+ {
+ return "DESede";
+ }
+
+ @Override
+ public void transformBlock(byte[] in, int inOff, byte[] out, int outOff)
+ {
+ if (key1 == null)
+ {
+ throw new IllegalStateException("DESede engine not initialised!");
+ }
+
+ if (encrypt)
+ {
+ desFunc(key1, in, inOff, out, outOff);
+ desFunc(key2, out, outOff, out, outOff);
+ desFunc(key3, out, outOff, out, outOff);
+ }
+ else
+ {
+ desFunc(key3, in, inOff, out, outOff);
+ desFunc(key2, out, outOff, out, outOff);
+ desFunc(key1, out, outOff, out, outOff);
+ }
+ }
+
+ private abstract static class Wrapper implements BlockCipher {
+ protected BlockCipher bc;
+
+ @Override
+ public int getBlockSize() {
+ return bc.getBlockSize();
+ }
+
+ @Override
+ public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff) {
+ bc.transformBlock(src, srcoff, dst, dstoff);
+ }
+ }
+
+ public static class CBC extends Wrapper {
+ @Override
+ public void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException {
+ BlockCipher rawCipher = new DESede();
+ rawCipher.init(forEncryption, key, iv);
+ bc = new CBCMode(rawCipher, iv, forEncryption);
+ }
+ }
+
+ public static class CTR extends Wrapper {
+ @Override
+ public void init(boolean forEncryption, byte[] key, byte[] iv) throws IllegalArgumentException {
+ BlockCipher rawCipher = new DESede();
+ rawCipher.init(true, key, iv);
+ bc = new CTRMode(rawCipher, iv, forEncryption);
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/EtmCipher.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/EtmCipher.java
new file mode 100644
index 0000000000..174b20e0b6
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/EtmCipher.java
@@ -0,0 +1,4 @@
+package com.trilead.ssh2.crypto.cipher;
+
+public interface EtmCipher {
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/NullCipher.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/NullCipher.java
new file mode 100644
index 0000000000..4617a3b081
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/cipher/NullCipher.java
@@ -0,0 +1,35 @@
+package com.trilead.ssh2.crypto.cipher;
+
+/**
+ * NullCipher.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: NullCipher.java,v 1.1 2007/10/15 12:49:55 cplattne Exp $
+ */
+public class NullCipher implements BlockCipher
+{
+ private int blockSize = 8;
+
+ public NullCipher()
+ {
+ }
+
+ public NullCipher(int blockSize)
+ {
+ this.blockSize = blockSize;
+ }
+
+ public void init(boolean forEncryption, byte[] key, byte[] iv)
+ {
+ }
+
+ public int getBlockSize()
+ {
+ return blockSize;
+ }
+
+ public void transformBlock(byte[] src, int srcoff, byte[] dst, int dstoff)
+ {
+ System.arraycopy(src, srcoff, dst, dstoff, blockSize);
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/Curve25519Exchange.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/Curve25519Exchange.java
new file mode 100644
index 0000000000..01d4ab472a
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/Curve25519Exchange.java
@@ -0,0 +1,85 @@
+package com.trilead.ssh2.crypto.dh;
+
+import com.google.crypto.tink.subtle.X25519;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+
+/**
+ * Created by Kenny Root on 1/23/16.
+ */
+public class Curve25519Exchange extends GenericDhExchange {
+ public static final String NAME = "curve25519-sha256";
+ public static final String ALT_NAME = "curve25519-sha256@libssh.org";
+ public static final int KEY_SIZE = 32;
+
+ private byte[] clientPublic;
+ private byte[] clientPrivate;
+ private byte[] serverPublic;
+
+ public Curve25519Exchange() {
+ super();
+ }
+
+ /*
+ * Used to test known vectors.
+ */
+ public Curve25519Exchange(byte[] secret) throws InvalidKeyException {
+ if (secret.length != KEY_SIZE) {
+ throw new AssertionError("secret must be key size");
+ }
+ clientPrivate = secret.clone();
+ }
+
+ @Override
+ public void init(String name) throws IOException {
+ if (!NAME.equals(name) && !ALT_NAME.equals(name)) {
+ throw new IOException("Invalid name " + name);
+ }
+
+ clientPrivate = X25519.generatePrivateKey();
+ try {
+ clientPublic = X25519.publicFromPrivate(clientPrivate);
+ } catch (InvalidKeyException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public byte[] getE() {
+ return clientPublic.clone();
+ }
+
+ @Override
+ protected byte[] getServerE() {
+ return serverPublic.clone();
+ }
+
+ @Override
+ public void setF(byte[] f) throws IOException {
+ if (f.length != KEY_SIZE) {
+ throw new IOException("Server sent invalid key length " + f.length + " (expected " +
+ KEY_SIZE + ")");
+ }
+ serverPublic = f.clone();
+ try {
+ byte[] sharedSecretBytes = X25519.computeSharedSecret(clientPrivate, serverPublic);
+ int allBytes = 0;
+ for (int i = 0; i < sharedSecretBytes.length; i++) {
+ allBytes |= sharedSecretBytes[i];
+ }
+ if (allBytes == 0) {
+ throw new IOException("Invalid key computed; all zeroes");
+ }
+ sharedSecret = new BigInteger(1, sharedSecretBytes);
+ } catch (InvalidKeyException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public String getHashAlgo() {
+ return "SHA-256";
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/DhExchange.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/DhExchange.java
new file mode 100644
index 0000000000..08b82eef91
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/DhExchange.java
@@ -0,0 +1,212 @@
+/**
+ *
+ */
+package com.trilead.ssh2.crypto.dh;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPublicKeySpec;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+
+/**
+ * @author kenny
+ *
+ */
+public class DhExchange extends GenericDhExchange {
+
+ /* Given by the standard */
+
+ private static final BigInteger P1 = new BigInteger(
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" +
+ "FFFFFFFFFFFFFFFF", 16);
+
+ private static final BigInteger P14 = new BigInteger(
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" +
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" +
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" +
+ "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16);
+
+ private static final BigInteger P16 = new BigInteger(
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" +
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" +
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" +
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" +
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" +
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" +
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" +
+ "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" +
+ "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" +
+ "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" +
+ "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" +
+ "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" +
+ "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" +
+ "FFFFFFFFFFFFFFFF", 16);
+
+ private static final BigInteger P18 = new BigInteger(
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" +
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" +
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" +
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" +
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" +
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" +
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" +
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" +
+ "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" +
+ "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" +
+ "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" +
+ "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" +
+ "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" +
+ "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" +
+ "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" +
+ "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" +
+ "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" +
+ "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" +
+ "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" +
+ "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" +
+ "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" +
+ "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" +
+ "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" +
+ "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" +
+ "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" +
+ "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" +
+ "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" +
+ "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" +
+ "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" +
+ "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" +
+ "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" +
+ "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" +
+ "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" +
+ "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" +
+ "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" +
+ "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", 16);
+
+ private static final BigInteger G = BigInteger.valueOf(2);
+
+ /* Hash algorithm to use */
+ private String hashAlgo;
+
+ /* Client public and private */
+
+ private DHPrivateKey clientPrivate;
+ private DHPublicKey clientPublic;
+
+ /* Server public */
+
+ private DHPublicKey serverPublic;
+
+ @Override
+ public void init(String name) throws IOException {
+ final DHParameterSpec spec;
+ if ("diffie-hellman-group18-sha512".equals(name)) {
+ spec = new DHParameterSpec(P18, G);
+ hashAlgo = "SHA-512";
+ } else if ("diffie-hellman-group16-sha512".equals(name)) {
+ spec = new DHParameterSpec(P16, G);
+ hashAlgo = "SHA-512";
+ } else if ("diffie-hellman-group14-sha256".equals(name)) {
+ spec = new DHParameterSpec(P14, G);
+ hashAlgo = "SHA-256";
+ } else if ("diffie-hellman-group14-sha1".equals(name)) {
+ spec = new DHParameterSpec(P14, G);
+ hashAlgo = "SHA-1";
+ } else if ("diffie-hellman-group1-sha1".equals(name)) {
+ spec = new DHParameterSpec(P1, G);
+ hashAlgo = "SHA-1";
+ } else {
+ throw new IllegalArgumentException("Unknown DH group " + name);
+ }
+
+ try {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
+ kpg.initialize(spec);
+ KeyPair pair = kpg.generateKeyPair();
+ clientPrivate = (DHPrivateKey) pair.getPrivate();
+ clientPublic = (DHPublicKey) pair.getPublic();
+ } catch (NoSuchAlgorithmException e) {
+ throw new IOException("No DH keypair generator", e);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new IOException("Invalid DH parameters", e);
+ }
+ }
+
+ @Override
+ public byte[] getE() {
+ if (clientPublic == null)
+ throw new IllegalStateException("DhExchange not initialized!");
+
+ return clientPublic.getY().toByteArray();
+ }
+
+ @Override
+ protected byte[] getServerE() {
+ if (serverPublic == null)
+ throw new IllegalStateException("DhExchange not initialized!");
+
+ return serverPublic.getY().toByteArray();
+ }
+
+ @Override
+ public void setF(byte[] f) throws IOException {
+ if (clientPublic == null)
+ throw new IllegalStateException("DhExchange not initialized!");
+
+ final KeyAgreement ka;
+ try {
+ KeyFactory kf = KeyFactory.getInstance("DH");
+ DHParameterSpec params = clientPublic.getParams();
+ this.serverPublic = (DHPublicKey) kf.generatePublic(new DHPublicKeySpec(
+ new BigInteger(1, f), params.getP(), params.getG()));
+
+ ka = KeyAgreement.getInstance("DH");
+ ka.init(clientPrivate);
+ ka.doPhase(serverPublic, true);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IOException("No DH key agreement method", e);
+ } catch (InvalidKeyException | InvalidKeySpecException e) {
+ throw new IOException("Invalid DH key", e);
+ }
+
+ sharedSecret = new BigInteger(1, ka.generateSecret());
+ }
+
+ @Override
+ public String getHashAlgo() {
+ return hashAlgo;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/DhGroupExchange.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/DhGroupExchange.java
new file mode 100644
index 0000000000..1356c57e23
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/DhGroupExchange.java
@@ -0,0 +1,113 @@
+
+package com.trilead.ssh2.crypto.dh;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import com.trilead.ssh2.DHGexParameters;
+import com.trilead.ssh2.crypto.digest.HashForSSH2Types;
+
+
+/**
+ * DhGroupExchange.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: DhGroupExchange.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $
+ */
+public class DhGroupExchange
+{
+ /* Given by the standard */
+
+ private BigInteger p;
+ private BigInteger g;
+
+ /* Client public and private */
+
+ private BigInteger e;
+ private BigInteger x;
+
+ /* Server public */
+
+ private BigInteger f;
+
+ /* Shared secret */
+
+ private BigInteger k;
+
+ public DhGroupExchange(BigInteger p, BigInteger g)
+ {
+ this.p = p;
+ this.g = g;
+ }
+
+ public void init(SecureRandom rnd)
+ {
+ k = null;
+
+ x = new BigInteger(p.bitLength() - 1, rnd);
+ e = g.modPow(x, p);
+ }
+
+ /**
+ * @return Returns the e.
+ */
+ public BigInteger getE()
+ {
+ if (e == null)
+ throw new IllegalStateException("Not initialized!");
+
+ return e;
+ }
+
+ /**
+ * @return Returns the shared secret k.
+ */
+ public BigInteger getK()
+ {
+ if (k == null)
+ throw new IllegalStateException("Shared secret not yet known, need f first!");
+
+ return k;
+ }
+
+ /**
+ * Sets f and calculates the shared secret.
+ */
+ public void setF(BigInteger f)
+ {
+ if (e == null)
+ throw new IllegalStateException("Not initialized!");
+
+ BigInteger zero = BigInteger.valueOf(0);
+
+ if (zero.compareTo(f) >= 0 || p.compareTo(f) <= 0)
+ throw new IllegalArgumentException("Invalid f specified!");
+
+ this.f = f;
+ this.k = f.modPow(x, p);
+ }
+
+ public byte[] calculateH(String hashAlgo, byte[] clientversion, byte[] serverversion,
+ byte[] clientKexPayload, byte[] serverKexPayload, byte[] hostKey, DHGexParameters para)
+ {
+ HashForSSH2Types hash = new HashForSSH2Types(hashAlgo);
+
+ hash.updateByteString(clientversion);
+ hash.updateByteString(serverversion);
+ hash.updateByteString(clientKexPayload);
+ hash.updateByteString(serverKexPayload);
+ hash.updateByteString(hostKey);
+ if (para.getMin_group_len() > 0)
+ hash.updateUINT32(para.getMin_group_len());
+ hash.updateUINT32(para.getPref_group_len());
+ if (para.getMax_group_len() > 0)
+ hash.updateUINT32(para.getMax_group_len());
+ hash.updateBigInt(p);
+ hash.updateBigInt(g);
+ hash.updateBigInt(e);
+ hash.updateBigInt(f);
+ hash.updateBigInt(k);
+
+ return hash.getDigest();
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/EcDhExchange.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/EcDhExchange.java
new file mode 100644
index 0000000000..e6e30f6a38
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/EcDhExchange.java
@@ -0,0 +1,106 @@
+package com.trilead.ssh2.crypto.dh;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+
+import javax.crypto.KeyAgreement;
+
+import com.trilead.ssh2.signature.ECDSASHA2Verify;
+
+/**
+ * @author kenny
+ *
+ */
+public class EcDhExchange extends GenericDhExchange {
+ private ECPrivateKey clientPrivate;
+ private ECPublicKey clientPublic;
+ private ECPublicKey serverPublic;
+
+ @Override
+ public void init(String name) throws IOException {
+ final ECParameterSpec spec;
+
+ if ("ecdh-sha2-nistp256".equals(name)) {
+ spec = ECDSASHA2Verify.ECDSASHA2NISTP256Verify.get().getParameterSpec();
+ } else if ("ecdh-sha2-nistp384".equals(name)) {
+ spec = ECDSASHA2Verify.ECDSASHA2NISTP384Verify.get().getParameterSpec();
+ } else if ("ecdh-sha2-nistp521".equals(name)) {
+ spec = ECDSASHA2Verify.ECDSASHA2NISTP521Verify.get().getParameterSpec();
+ } else {
+ throw new IllegalArgumentException("Unknown EC curve " + name);
+ }
+
+ KeyPairGenerator kpg;
+ try {
+ kpg = KeyPairGenerator.getInstance("EC");
+ kpg.initialize(spec);
+ KeyPair pair = kpg.generateKeyPair();
+ clientPrivate = (ECPrivateKey) pair.getPrivate();
+ clientPublic = (ECPublicKey) pair.getPublic();
+ } catch (NoSuchAlgorithmException e) {
+ throw new IOException("No DH keypair generator", e);
+ } catch (InvalidAlgorithmParameterException e) {
+ throw new IOException("Invalid DH parameters", e);
+ }
+ }
+
+ @Override
+ public byte[] getE() {
+ return ECDSASHA2Verify.encodeECPoint(clientPublic.getW(), clientPublic.getParams()
+ .getCurve());
+ }
+
+ @Override
+ protected byte[] getServerE() {
+ return ECDSASHA2Verify.encodeECPoint(serverPublic.getW(), serverPublic.getParams()
+ .getCurve());
+ }
+
+ @Override
+ public void setF(byte[] f) throws IOException {
+
+ if (clientPublic == null)
+ throw new IllegalStateException("DhDsaExchange not initialized!");
+
+ final KeyAgreement ka;
+ try {
+ KeyFactory kf = KeyFactory.getInstance("EC");
+ ECDSASHA2Verify verifier = ECDSASHA2Verify.getVerifierForKey(clientPublic);
+ if (verifier == null) {
+ throw new IOException("No such EC group");
+ }
+
+ ECPoint serverPoint = verifier.decodeECPoint(f);
+ ECParameterSpec params = verifier.getParameterSpec();
+ this.serverPublic = (ECPublicKey) kf.generatePublic(new ECPublicKeySpec(serverPoint,
+ params));
+
+ ka = KeyAgreement.getInstance("ECDH");
+ ka.init(clientPrivate);
+ ka.doPhase(serverPublic, true);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IOException("No ECDH key agreement method", e);
+ } catch (InvalidKeyException | InvalidKeySpecException e) {
+ throw new IOException("Invalid ECDH key", e);
+ }
+
+ sharedSecret = new BigInteger(1, ka.generateSecret());
+ }
+
+ @Override
+ public String getHashAlgo() {
+ return ECDSASHA2Verify.getDigestAlgorithmForParams(clientPublic);
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java
new file mode 100644
index 0000000000..0ea0ab1e12
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/dh/GenericDhExchange.java
@@ -0,0 +1,96 @@
+
+package com.trilead.ssh2.crypto.dh;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+
+import com.trilead.ssh2.crypto.digest.HashForSSH2Types;
+import com.trilead.ssh2.log.Logger;
+
+
+/**
+ * DhExchange.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: DhExchange.java,v 1.2 2008/04/01 12:38:09 cplattne Exp $
+ */
+public abstract class GenericDhExchange
+{
+ private static final Logger log = Logger.getLogger(GenericDhExchange.class);
+
+ /* Shared secret */
+
+ BigInteger sharedSecret;
+
+ protected GenericDhExchange()
+ {
+ }
+
+ public static GenericDhExchange getInstance(String algo) {
+ if (Curve25519Exchange.NAME.equals(algo) || Curve25519Exchange.ALT_NAME.equals(algo)) {
+ return new Curve25519Exchange();
+ }
+ if (algo.startsWith("ecdh-sha2-")) {
+ return new EcDhExchange();
+ } else {
+ return new DhExchange();
+ }
+ }
+
+ public abstract void init(String name) throws IOException;
+
+ /**
+ * @return Returns the e (public value)
+ * @throws IllegalStateException
+ */
+ public abstract byte[] getE();
+
+ /**
+ * @return Returns the server's e (public value)
+ * @throws IllegalStateException
+ */
+ protected abstract byte[] getServerE();
+
+ /**
+ * @return Returns the shared secret k.
+ * @throws IllegalStateException
+ */
+ public BigInteger getK()
+ {
+ if (sharedSecret == null)
+ throw new IllegalStateException("Shared secret not yet known, need f first!");
+
+ return sharedSecret;
+ }
+
+ /**
+ * @param f
+ */
+ public abstract void setF(byte[] f) throws IOException;
+
+ public byte[] calculateH(byte[] clientversion, byte[] serverversion, byte[] clientKexPayload,
+ byte[] serverKexPayload, byte[] hostKey) throws UnsupportedEncodingException
+ {
+ HashForSSH2Types hash = new HashForSSH2Types(getHashAlgo());
+
+ if (log.isEnabled())
+ {
+ log.log(90, "Client: '" + new String(clientversion) + "'");
+ log.log(90, "Server: '" + new String(serverversion) + "'");
+ }
+
+ hash.updateByteString(clientversion);
+ hash.updateByteString(serverversion);
+ hash.updateByteString(clientKexPayload);
+ hash.updateByteString(serverKexPayload);
+ hash.updateByteString(hostKey);
+ hash.updateByteString(getE());
+ hash.updateByteString(getServerE());
+ hash.updateBigInt(sharedSecret);
+
+ return hash.getDigest();
+ }
+
+ public abstract String getHashAlgo();
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/HMAC.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/HMAC.java
new file mode 100644
index 0000000000..c6ee1293e8
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/HMAC.java
@@ -0,0 +1,166 @@
+
+package com.trilead.ssh2.crypto.digest;
+
+import javax.crypto.Mac;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * MAC.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: MAC.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $
+ */
+public final class HMAC implements MAC
+{
+ private static final String ETM_SUFFIX = "-etm@openssh.com";
+
+ /**
+ * From http://tools.ietf.org/html/rfc4253
+ */
+ static final String HMAC_MD5 = "hmac-md5";
+
+ /**
+ * From http://tools.ietf.org/html/rfc4253
+ */
+ static final String HMAC_MD5_96 = "hmac-md5-96";
+
+ /**
+ * From http://tools.ietf.org/html/rfc4253
+ */
+ static final String HMAC_SHA1 = "hmac-sha1";
+
+ /**
+ * From https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL
+ */
+ static final String HMAC_SHA1_ETM = "hmac-sha1-etm@openssh.com";
+
+ /**
+ * From http://tools.ietf.org/html/rfc4253
+ */
+ static final String HMAC_SHA1_96 = "hmac-sha1-96";
+
+ /**
+ * From https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL
+ */
+ static final String HMAC_SHA2_256_ETM = "hmac-sha2-256-etm@openssh.com";
+
+ /**
+ * From https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL
+ */
+ static final String HMAC_SHA2_512_ETM = "hmac-sha2-512-etm@openssh.com";
+
+ /**
+ * From http://tools.ietf.org/html/rfc6668
+ */
+ static final String HMAC_SHA2_256 = "hmac-sha2-256";
+
+ /**
+ * From http://tools.ietf.org/html/rfc6668
+ */
+ static final String HMAC_SHA2_512 = "hmac-sha2-512";
+
+ private final Mac mac;
+ private final int outSize;
+ private final boolean encryptThenMac;
+ private final byte[] buffer;
+
+ public HMAC(String type, byte[] key)
+ {
+ try {
+ if (HMAC_SHA1.equals(type) || HMAC_SHA1_96.equals(type))
+ {
+ mac = Mac.getInstance("HmacSHA1");
+ encryptThenMac = false;
+ }
+ else if (HMAC_SHA1_ETM.equals(type))
+ {
+ mac = Mac.getInstance("HmacSHA1");
+ encryptThenMac = true;
+ }
+ else if (HMAC_MD5.equals(type) || HMAC_MD5_96.equals(type))
+ {
+ mac = Mac.getInstance("HmacMD5");
+ encryptThenMac = false;
+ }
+ else if (HMAC_SHA2_256.equals(type))
+ {
+ mac = Mac.getInstance("HmacSHA256");
+ encryptThenMac = false;
+ }
+ else if (HMAC_SHA2_256_ETM.equals(type))
+ {
+ mac = Mac.getInstance("HmacSHA256");
+ encryptThenMac = true;
+ }
+ else if (HMAC_SHA2_512.equals(type))
+ {
+ mac = Mac.getInstance("HmacSHA512");
+ encryptThenMac = false;
+ }
+ else if (HMAC_SHA2_512_ETM.equals(type))
+ {
+ mac = Mac.getInstance("HmacSHA512");
+ encryptThenMac = true;
+ }
+ else
+ throw new IllegalArgumentException("Unknown algorithm " + type);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException("Unknown algorithm " + type, e);
+ }
+
+ int macSize = mac.getMacLength();
+ if (type.endsWith("-96")) {
+ outSize = 12;
+ buffer = new byte[macSize];
+ } else {
+ outSize = macSize;
+ buffer = null;
+ }
+
+ try {
+ mac.init(new SecretKeySpec(key, type));
+ } catch (InvalidKeyException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public final void initMac(int seq)
+ {
+ mac.reset();
+ mac.update((byte) (seq >> 24));
+ mac.update((byte) (seq >> 16));
+ mac.update((byte) (seq >> 8));
+ mac.update((byte) (seq));
+ }
+
+ public final void update(byte[] packetdata, int off, int len)
+ {
+ mac.update(packetdata, off, len);
+ }
+
+ public final void getMac(byte[] out, int off)
+ {
+ try {
+ if (buffer != null) {
+ mac.doFinal(buffer, 0);
+ System.arraycopy(buffer, 0, out, off, out.length - off);
+ } else {
+ mac.doFinal(out, off);
+ }
+ } catch (ShortBufferException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public final int size()
+ {
+ return outSize;
+ }
+
+ public boolean isEncryptThenMac() {
+ return encryptThenMac;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/HashForSSH2Types.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/HashForSSH2Types.java
new file mode 100644
index 0000000000..fad40efdc3
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/HashForSSH2Types.java
@@ -0,0 +1,91 @@
+
+package com.trilead.ssh2.crypto.digest;
+
+import java.math.BigInteger;
+import java.security.DigestException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * HashForSSH2Types.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: HashForSSH2Types.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $
+ */
+public class HashForSSH2Types
+{
+ MessageDigest md;
+
+ public HashForSSH2Types(String type)
+ {
+ try {
+ md = MessageDigest.getInstance(type);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Unsupported algorithm " + type);
+ }
+ }
+
+ public void updateByte(byte b)
+ {
+ /* HACK - to test it with J2ME */
+ byte[] tmp = new byte[1];
+ tmp[0] = b;
+ md.update(tmp);
+ }
+
+ public void updateBytes(byte[] b)
+ {
+ md.update(b);
+ }
+
+ public void updateUINT32(int v)
+ {
+ md.update((byte) (v >> 24));
+ md.update((byte) (v >> 16));
+ md.update((byte) (v >> 8));
+ md.update((byte) (v));
+ }
+
+ public void updateByteString(byte[] b)
+ {
+ updateUINT32(b.length);
+ updateBytes(b);
+ }
+
+ public void updateBigInt(BigInteger b)
+ {
+ updateByteString(b.toByteArray());
+ }
+
+ public void reset()
+ {
+ md.reset();
+ }
+
+ public int getDigestLength()
+ {
+ return md.getDigestLength();
+ }
+
+ public byte[] getDigest()
+ {
+ byte[] tmp = new byte[md.getDigestLength()];
+ getDigest(tmp);
+ return tmp;
+ }
+
+ public void getDigest(byte[] out)
+ {
+ getDigest(out, 0);
+ }
+
+ public void getDigest(byte[] out, int off)
+ {
+ try {
+ md.digest(out, off, out.length - off);
+ } catch (DigestException e) {
+ // TODO is this right?!
+ throw new RuntimeException("Unable to digest", e);
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/MAC.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/MAC.java
new file mode 100644
index 0000000000..ea7fcecfd4
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/MAC.java
@@ -0,0 +1,12 @@
+package com.trilead.ssh2.crypto.digest;
+
+/**
+ * Created by kenny on 2/12/17.
+ */
+public interface MAC {
+ void initMac(int seq);
+ void update(byte[] packetdata, int off, int len);
+ void getMac(byte[] out, int off);
+ int size();
+ boolean isEncryptThenMac();
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/MACs.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/MACs.java
new file mode 100644
index 0000000000..1238761aa4
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/digest/MACs.java
@@ -0,0 +1,50 @@
+
+package com.trilead.ssh2.crypto.digest;
+
+/**
+ * MAC.
+ *
+ * @author Christian Plattner, plattner@trilead.com
+ * @version $Id: MAC.java,v 1.1 2007/10/15 12:49:57 cplattne Exp $
+ */
+public final class MACs
+{
+ /* Higher Priority First */
+ private static final String[] MAC_LIST = {
+ HMAC.HMAC_SHA2_256_ETM,
+ HMAC.HMAC_SHA2_512_ETM,
+ HMAC.HMAC_SHA1_ETM,
+ HMAC.HMAC_SHA2_256,
+ HMAC.HMAC_SHA2_512,
+ HMAC.HMAC_SHA1,
+ };
+
+ public final static String[] getMacList()
+ {
+ return MAC_LIST;
+ }
+
+ public final static void checkMacList(String[] macs)
+ {
+ for (int i = 0; i < macs.length; i++) {
+ getKeyLen(macs[i]);
+ }
+ }
+
+ public final static int getKeyLen(String type)
+ {
+ if (type == null)
+ throw new IllegalArgumentException("type == null");
+
+ if (type.startsWith(HMAC.HMAC_SHA1))
+ return 20;
+ if (type.startsWith(HMAC.HMAC_MD5))
+ return 16;
+ if (type.startsWith(HMAC.HMAC_SHA2_256))
+ return 32;
+ if (type.startsWith(HMAC.HMAC_SHA2_512))
+ return 64;
+
+ throw new IllegalArgumentException("Unknown algorithm " + type);
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactory.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactory.java
new file mode 100644
index 0000000000..585331a7a2
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyFactory.java
@@ -0,0 +1,39 @@
+package com.trilead.ssh2.crypto.keys;
+
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+public class Ed25519KeyFactory extends KeyFactorySpi {
+ @Override
+ protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
+ if (keySpec instanceof X509EncodedKeySpec) {
+ return new Ed25519PublicKey((X509EncodedKeySpec) keySpec);
+ }
+ throw new InvalidKeySpecException("Unrecognized key spec: " + keySpec.getClass());
+ }
+
+ @Override
+ protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException {
+ if (keySpec instanceof PKCS8EncodedKeySpec) {
+ return new Ed25519PrivateKey((PKCS8EncodedKeySpec) keySpec);
+ }
+ throw new InvalidKeySpecException("Unrecognized key spec: " + keySpec.getClass());
+ }
+
+ @Override
+ protected T engineGetKeySpec(Key key, Class keySpec) throws InvalidKeySpecException {
+ throw new InvalidKeySpecException("not implemented yet " + key + " " + keySpec);
+ }
+
+ @Override
+ protected Key engineTranslateKey(Key key) throws InvalidKeyException {
+ throw new InvalidKeyException("No other EdDSA key providers known");
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyPairGenerator.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyPairGenerator.java
new file mode 100644
index 0000000000..6328d2c42b
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519KeyPairGenerator.java
@@ -0,0 +1,25 @@
+package com.trilead.ssh2.crypto.keys;
+
+import com.google.crypto.tink.subtle.Ed25519Sign;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGeneratorSpi;
+import java.security.SecureRandom;
+
+public class Ed25519KeyPairGenerator extends KeyPairGeneratorSpi {
+ @Override
+ public void initialize(int keySize, SecureRandom secureRandom) {
+ // ignored.
+ }
+
+ @Override
+ public KeyPair generateKeyPair() {
+ try {
+ Ed25519Sign.KeyPair kp = Ed25519Sign.KeyPair.newKeyPair();
+ return new KeyPair(new Ed25519PublicKey(kp.getPublicKey()), new Ed25519PrivateKey(kp.getPrivateKey()));
+ } catch (GeneralSecurityException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519PrivateKey.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519PrivateKey.java
new file mode 100644
index 0000000000..c4eba83bd4
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519PrivateKey.java
@@ -0,0 +1,137 @@
+package com.trilead.ssh2.crypto.keys;
+
+import com.trilead.ssh2.packets.TypesReader;
+import com.trilead.ssh2.packets.TypesWriter;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+
+import javax.security.auth.DestroyFailedException;
+
+public class Ed25519PrivateKey implements PrivateKey {
+ private static final byte[] ED25519_OID = new byte[] {43, 101, 112};
+ private static final int KEY_BYTES_LENGTH = 32;
+ private static final int ENCODED_SIZE = 48;
+
+ private final byte[] seed;
+ private boolean destroyed;
+
+ public Ed25519PrivateKey(byte[] hash) {
+ this.seed = hash;
+ }
+
+ public Ed25519PrivateKey(PKCS8EncodedKeySpec keySpec) throws InvalidKeySpecException {
+ this.seed = decode(keySpec);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(seed);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Ed25519PrivateKey)) {
+ return false;
+ }
+
+ Ed25519PrivateKey other = (Ed25519PrivateKey) o;
+
+ if (seed == null || other.seed == null || seed.length != other.seed.length) {
+ return false;
+ }
+
+ int difference = 0;
+ for (int i = 0; i < seed.length; i++) {
+ difference |= seed[i] ^ other.seed[i];
+ }
+ return difference == 0;
+ }
+
+ @Override
+ public String getAlgorithm() {
+ return "EdDSA";
+ }
+
+ @Override
+ public String getFormat() {
+ return "PKCS#8";
+ }
+
+ public byte[] getSeed() {
+ return seed;
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ // From RFC 8410 section 7 "Private Key Format"
+ TypesWriter tw = new TypesWriter();
+ // ASN.1 Sequence
+ tw.writeByte(0x30);
+ tw.writeByte(11 + ED25519_OID.length + seed.length); // Length
+ // Key version type
+ tw.writeByte(0x02); // ASN.1 Integer
+ tw.writeByte(1); // Length
+ tw.writeByte(0); // v1 == RFC 5208 format
+ // Algorithm OID - ASN.1 Sequence
+ tw.writeByte(0x30);
+ tw.writeByte(ED25519_OID.length + 2); // OID
+ tw.writeByte(0x06); // ASN.1 OID type
+ tw.writeByte(ED25519_OID.length);
+ tw.writeBytes(ED25519_OID);
+ // Private key sequence
+ tw.writeByte(0x04); // ASN.1 Octet string
+ tw.writeByte(2 + seed.length);
+ tw.writeByte(0x04); // ASN.1 Octet string
+ tw.writeByte(seed.length);
+ tw.writeBytes(seed);
+
+ return tw.getBytes();
+ }
+
+ private static byte[] decode(PKCS8EncodedKeySpec keySpec) throws InvalidKeySpecException {
+ byte[] encoded = keySpec.getEncoded();
+ if (encoded.length != ENCODED_SIZE) {
+ throw new InvalidKeySpecException("Key spec is of invalid size");
+ }
+ try {
+ TypesReader tr = new TypesReader(keySpec.getEncoded());
+ if (tr.readByte() != 0x30 || // ASN.1 sequence
+ tr.readByte() != ENCODED_SIZE - 2 || // Expected size
+ tr.readByte() != 0x02 || // ASN.1 Integer
+ tr.readByte() != 1 || // length
+ tr.readByte() != 0 || // v1
+ tr.readByte() != 0x30 || // ASN.1 Sequence
+ tr.readByte() != ED25519_OID.length + 2 || // OID length
+ tr.readByte() != 0x06 || // ASN.1 OID
+ tr.readByte() != ED25519_OID.length) {
+ throw new InvalidKeySpecException("Key was not encoded correctly");
+ }
+ byte[] oid = tr.readBytes(ED25519_OID.length);
+ if (!Arrays.equals(ED25519_OID, oid) ||
+ tr.readByte() != 0x04 || // ASN.1 octet string
+ tr.readByte() != KEY_BYTES_LENGTH + 2 || // length
+ tr.readByte() != 0x04 || // ASN.1 octet string
+ tr.readByte() != KEY_BYTES_LENGTH) {
+ throw new InvalidKeySpecException("Key was not encoded correctly");
+ }
+ return tr.readBytes(KEY_BYTES_LENGTH);
+ } catch (IOException e) {
+ throw new InvalidKeySpecException("Key was not encoded correctly", e);
+ }
+ }
+
+ @Override
+ public void destroy() throws DestroyFailedException {
+ Arrays.fill(seed, (byte) 0);
+ destroyed = true;
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return destroyed;
+ }
+}
diff --git a/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519Provider.java b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519Provider.java
new file mode 100644
index 0000000000..369ca72b0a
--- /dev/null
+++ b/core/function-impl/mogo-core-function-devatools-rviz/src/main/java/com/trilead/ssh2/crypto/keys/Ed25519Provider.java
@@ -0,0 +1,42 @@
+package com.trilead.ssh2.crypto.keys;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.Provider;
+import java.security.Security;
+
+public class Ed25519Provider extends Provider {
+ public static final String KEY_ALGORITHM = "Ed25519";
+ private static final Object sInitLock = new Object();
+ private static boolean sInitialized = false;
+
+ public Ed25519Provider() {
+ super("ConnectBot Ed25519 Provider", 1.0, "Not for use elsewhere");
+ AccessController.doPrivileged((PrivilegedAction