Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoland Schulz2014-09-30 04:36:51 +0000
committerJohn Eblen2014-10-17 16:29:45 +0000
commitc563ea3f0e8967bd7a57ba454e95704797b1e9e3 (patch)
tree23da811a5019ec126cf128b5b0325a17f6f0430f
parent5ee4fa818a1313d838220e3737431625c9e29fea (diff)
downloadorg.eclipse.remote-c563ea3f0e8967bd7a57ba454e95704797b1e9e3.tar.gz
org.eclipse.remote-c563ea3f0e8967bd7a57ba454e95704797b1e9e3.tar.xz
org.eclipse.remote-c563ea3f0e8967bd7a57ba454e95704797b1e9e3.zip
Bug 345329 - Add SSH proxy
Allows to connect over any ssh server as gateway or over any other proxy by executing a local or remote command (such as netcat, corkscrew, ...). Change-Id: I413c22cb588d8560d8db9ac877fb04f8331456aa
-rw-r--r--bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/ArgumentParser.java367
-rw-r--r--bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnection.java67
-rw-r--r--bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionAttributes.java2
-rw-r--r--bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionProxyFactory.java311
-rw-r--r--bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionWorkingCopy.java42
-rwxr-xr-xbundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/messages/Messages.java4
-rwxr-xr-xbundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/messages/messages.properties4
-rwxr-xr-xbundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/messages/Messages.java6
-rwxr-xr-xbundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/messages/messages.properties6
-rwxr-xr-xbundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/wizards/JSchConnectionPage.java99
10 files changed, 893 insertions, 15 deletions
diff --git a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/ArgumentParser.java b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/ArgumentParser.java
new file mode 100644
index 0000000..3fa3515
--- /dev/null
+++ b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/ArgumentParser.java
@@ -0,0 +1,367 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.remote.internal.jsch.core;
+
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Utilitiy class for managing command line arguments.
+ *
+ */
+public class ArgumentParser {
+ private static List<String> parseCommandline(String commandline) {
+ ArrayList<String> result = new ArrayList<String>();
+ StringCharacterIterator iterator = new StringCharacterIterator(commandline);
+
+ for (iterator.first(); iterator.current() != CharacterIterator.DONE; iterator.next()) {
+
+ // Restart to skip white space
+ if (Character.isWhitespace(iterator.current())) {
+ continue;
+ }
+
+ // Read token
+ StringBuffer buffer = new StringBuffer();
+ token_reader: for (; iterator.current() != CharacterIterator.DONE; iterator.next()) {
+ char tokenChar = iterator.current();
+
+ // A white space terminates the token
+ if (Character.isWhitespace(tokenChar)) {
+ break token_reader;
+ }
+
+ // Handle character that composes the token
+ switch (tokenChar) {
+ case '"': {
+ /*
+ * Read all text within double quotes or until end of
+ * string. Allows escaping.
+ */
+ iterator.next(); // Skip quote
+ quoted_reader: while ((iterator.current() != CharacterIterator.DONE) && (iterator.current() != '"')) {
+ char innerChar = iterator.current();
+ switch (innerChar) {
+ case '\\':
+ char nextChar = iterator.next();
+ switch (nextChar) {
+ case CharacterIterator.DONE:
+ break quoted_reader;
+ case '"':
+ // Add the character, but remove the escape
+ buffer.append(nextChar);
+ iterator.next();
+ continue quoted_reader;
+ default:
+ // Add the character and keep escape
+ buffer.append(innerChar);
+ buffer.append(nextChar);
+ iterator.next();
+ continue quoted_reader;
+ }
+ default:
+ buffer.append(innerChar);
+ iterator.next();
+ continue quoted_reader;
+ }
+ }
+ continue token_reader;
+ }
+ case '\'': {
+ /*
+ * Read all text within single quotes or until end of
+ * string. No escaping.
+ */
+ iterator.next(); // Skip the quote
+ while ((iterator.current() != CharacterIterator.DONE) && (iterator.current() != '\'')) {
+ buffer.append(iterator.current());
+ iterator.next();
+ }
+ continue token_reader;
+ }
+ case '\\': {
+ /*
+ * Read escaped char.
+ */
+ char nextChar = iterator.next();
+ switch (nextChar) {
+ case CharacterIterator.DONE:
+ break token_reader;
+ case '\n':
+ // Ignore newline. Both lines are concatenated.
+ continue token_reader;
+ default:
+ // Add the character, but remove the escape
+ buffer.append(nextChar);
+ continue token_reader;
+ }
+ }
+ default:
+ /*
+ * Any other char, add to the buffer.
+ */
+ buffer.append(tokenChar);
+ continue token_reader;
+ }
+ }
+ result.add(buffer.toString());
+ }
+
+ return result;
+ }
+
+ private final List<String> tokens;
+
+ /**
+ * Create a command line representation from an array of strings. The first
+ * element of the list is assumed to be the command, the remaining, the
+ * arguments. The elements are not parsed not (un)escaped., but taked as the
+ * are.
+ *
+ */
+ public ArgumentParser(List<String> tokenList) {
+ this.tokens = new ArrayList<String>(tokenList);
+ }
+
+ /**
+ * Create a command line representation from the string with a shell command
+ * line. The command line is parsed and split on spaces. Quoted or escaped
+ * spaces are preserved..
+ *
+ */
+ public ArgumentParser(String commandline) {
+ this.tokens = parseCommandline(commandline);
+ }
+
+ /**
+ * Create a command line representation from an array of strings. The first
+ * element of the array is assumed to be the command, the remaining, the
+ * arguments. The elements are not parsed not (un)escaped., but taked as the
+ * are.
+ *
+ */
+ public ArgumentParser(String tokenArray[]) {
+ this(Arrays.asList(tokenArray));
+ }
+
+ /**
+ * Create a command line representation from the command and an list of
+ * parameters. The elements are not parsed not (un)escaped., but taked as
+ * the are.
+ *
+ */
+ public ArgumentParser(String command, List<String> parameterList) {
+ this.tokens = new ArrayList<String>();
+ this.tokens.add(command);
+ this.tokens.addAll(parameterList);
+ }
+
+ /**
+ * Create a command line representation from the command and an array of
+ * parameters. The elements are not parsed not (un)escaped., but taked as
+ * the are.
+ *
+ */
+ public ArgumentParser(String command, String parameterArray[]) {
+ this(command, Arrays.asList(parameterArray));
+ }
+
+ private StringBuffer escapeToken(String token, boolean fullEscape) {
+ StringBuffer buffer = new StringBuffer();
+ StringCharacterIterator iter = new StringCharacterIterator(token);
+ for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
+ if (Character.isWhitespace(c)) {
+ buffer.append('\\');
+ buffer.append(c);
+ continue;
+ }
+ switch (c) {
+ case '(':
+ case ')':
+ case '[':
+ case ']':
+ case '{':
+ case '}':
+ case '|':
+ case '\\':
+ case '*':
+ case '&':
+ case '^':
+ case '%':
+ case '$':
+ case '#':
+ case '@':
+ case '!':
+ case '~':
+ case '`':
+ case '\'':
+ case '"':
+ case ':':
+ case ';':
+ case '?':
+ case '>':
+ case '<':
+ case '\n':
+ if (fullEscape) {
+ buffer.append('\\');
+ }
+ buffer.append(c);
+ continue;
+ case ' ':
+ buffer.append('\\');
+ buffer.append(c);
+ continue;
+ default:
+ buffer.append(c);
+ continue;
+ }
+ }
+ return buffer;
+ }
+
+ /**
+ * Returns the command of the command line, assuming that the first entry is
+ * always the command.
+ *
+ * @return The command or null if the command lines has no command nor
+ * arguments.
+ */
+ public String getCommand() {
+ if (this.tokens.size() == 0) {
+ return null;
+ }
+ return this.tokens.get(0);
+ }
+
+ /**
+ * Convert all tokens in a full command line that can be executed in a
+ * shell.
+ *
+ * @param fullEscape
+ * If every special character shall be escaped. If false, only
+ * white spaces are escaped and the shell will interpret the
+ * special chars. If true, then all special chars are quoted.
+ */
+ public String getCommandLine(boolean fullEscape) {
+ StringBuffer buffer = new StringBuffer();
+ Iterator<String> iterator = this.tokens.iterator();
+ boolean first = true;
+ while (iterator.hasNext()) {
+ String token = iterator.next();
+ if (!first) {
+ buffer.append(' ');
+ } else {
+ first = false;
+ }
+ buffer.append(escapeToken(token, fullEscape));
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Returns the command of the command line, assuming that the first entry is
+ * always the command.
+ *
+ * @return The command or null if the command lines has no command nor
+ * arguments.
+ * @param fullEscape
+ * If every special character shall be escaped. If false, only
+ * white spaces are escaped and the shell will interpret the
+ * special chars. If true, then all special chars are quoted.
+ */
+ public String getEscapedCommand(boolean fullEscalpe) {
+ if (this.tokens.size() == 0) {
+ return null;
+ }
+ return escapeToken(this.tokens.get(0), fullEscalpe).toString();
+ }
+
+ /**
+ * Returns a list of all arguments, assuming that the first entry is the
+ * command name.
+ *
+ * @return The Array or null if the command lines has no command nor
+ * arguments.
+ */
+ public String[] getParameterArray() {
+ if (this.tokens.size() == 0) {
+ return null;
+ }
+ return this.tokens.subList(1, this.tokens.size()).toArray(new String[this.tokens.size() - 1]);
+ }
+
+ /**
+ * Returns a list of all arguments, assuming that the first entry is the
+ * command name.
+ *
+ * @return The List or null if the command lines has no command nor
+ * arguments.
+ */
+ public List<String> getParameterList() {
+ if (this.tokens.size() == 0) {
+ return null;
+ }
+ return new ArrayList<String>(this.tokens.subList(1, this.tokens.size()));
+ }
+
+ /**
+ * Returns the total number of entries.
+ *
+ * @return
+ */
+ public int getSize() {
+ return this.tokens.size();
+ }
+
+ /**
+ * Returns a List of all entries of the command line.
+ *
+ * @return The Array
+ */
+ public String[] getTokenArray() {
+ return this.tokens.toArray(new String[this.tokens.size()]);
+ }
+
+ /**
+ * Returns a List of all entries of the command line.
+ *
+ * @return The List
+ */
+ public List<String> getTokenList() {
+ return new ArrayList<String>(this.tokens);
+ }
+
+ /**
+ * Returns a representation of the command line for debug purposes.
+ */
+ @Override
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("<"); //$NON-NLS-1$
+ Iterator<String> iterator = this.tokens.iterator();
+ boolean first = true;
+ while (iterator.hasNext()) {
+ String token = iterator.next();
+ if (!first) {
+ buffer.append('\n');
+ } else {
+ first = false;
+ }
+ buffer.append(token);
+ }
+ buffer.append(">"); //$NON-NLS-1$
+ return buffer.toString();
+ }
+}
diff --git a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnection.java b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnection.java
index 1dc07a8..7df7a7a 100644
--- a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnection.java
+++ b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnection.java
@@ -27,18 +27,21 @@ import org.eclipse.osgi.util.NLS;
import org.eclipse.remote.core.IRemoteConnection;
import org.eclipse.remote.core.IRemoteConnectionChangeEvent;
import org.eclipse.remote.core.IRemoteConnectionChangeListener;
+import org.eclipse.remote.core.IRemoteConnectionManager;
import org.eclipse.remote.core.IRemoteConnectionWorkingCopy;
import org.eclipse.remote.core.IRemoteFileManager;
import org.eclipse.remote.core.IRemoteProcess;
import org.eclipse.remote.core.IRemoteProcessBuilder;
import org.eclipse.remote.core.IRemoteServices;
import org.eclipse.remote.core.IUserAuthenticator;
+import org.eclipse.remote.core.RemoteServices;
import org.eclipse.remote.core.exception.AddressInUseException;
import org.eclipse.remote.core.exception.RemoteConnectionException;
import org.eclipse.remote.core.exception.UnableToForwardPortException;
import org.eclipse.remote.internal.jsch.core.commands.ExecCommand;
import org.eclipse.remote.internal.jsch.core.messages.Messages;
+import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
@@ -388,7 +391,7 @@ public class JSchConnection implements IRemoteConnection {
/*
* (non-Javadoc)
*
- * @see org.eclipse.remote.core.IRemoteConnection#forwardRemotePort(java. lang.String, int,
+ * @see org.eclipse.remote.core.IRemoteConnection#forwardRemotePort(java.lang.String, int,
* org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
@@ -581,6 +584,38 @@ public class JSchConnection implements IRemoteConnection {
return fProperties.get(key);
}
+ /**
+ * Gets the proxy command. For no proxy command an empty string is returned.
+ *
+ * @return proxy command
+ */
+ public String getProxyCommand() {
+ return fAttributes.getAttribute(JSchConnectionAttributes.PROXYCOMMAND_ATTR, EMPTY_STRING);
+ }
+
+ /**
+ * Gets the proxy connection. If no proxy is used it returns a local connection.
+ *
+ * @return proxy connection
+ */
+ public IRemoteConnection getProxyConnection() {
+ String proxyConnectionName = getProxyConnectionName();
+ if (proxyConnectionName.equals(EMPTY_STRING)) {
+ return RemoteServices.getLocalServices().getConnectionManager().getConnection(
+ IRemoteConnectionManager.LOCAL_CONNECTION_NAME);
+ }
+ return fManager.getConnection(proxyConnectionName);
+ }
+
+ /**
+ * Gets the proxy connection name
+ *
+ * @return proxy connection name
+ */
+ public String getProxyConnectionName() {
+ return fAttributes.getAttribute(JSchConnectionAttributes.PROXYCONNECTION_ATTR, EMPTY_STRING);
+ }
+
/*
* (non-Javadoc)
*
@@ -612,6 +647,22 @@ public class JSchConnection implements IRemoteConnection {
return fSftpChannel;
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jcraft.jsch.Session#getStreamForwarder(java.lang.String, int)
+ */
+ public Channel getStreamForwarder(String host, int port) throws RemoteConnectionException
+ {
+ try {
+ Channel channel = fSessions.get(0).getStreamForwarder(host, port);
+ channel.connect();
+ return channel;
+ } catch (JSchException e) {
+ throw new RemoteConnectionException(e);
+ }
+ }
+
public int getTimeout() {
return fAttributes.getInt(JSchConnectionAttributes.TIMEOUT_ATTR, DEFAULT_TIMEOUT);
}
@@ -792,7 +843,19 @@ public class JSchConnection implements IRemoteConnection {
if (isPasswordAuth()) {
session.setPassword(getPassword());
}
- fJSchService.connect(session, getTimeout() * 1000, progress.newChild(10));
+ if (getProxyCommand().equals(EMPTY_STRING) && getProxyConnectionName().equals(EMPTY_STRING)) {
+ fJSchService.connect(session, getTimeout() * 1000, progress.newChild(10)); // connect without proxy
+ } else {
+ if (getProxyCommand().equals(EMPTY_STRING)) {
+ session.setProxy(JSchConnectionProxyFactory.createForwardProxy(getProxyConnection(),
+ progress.newChild(10)));
+ fJSchService.connect(session, getTimeout() * 1000, progress.newChild(10));
+ } else {
+ session.setProxy(JSchConnectionProxyFactory.createCommandProxy(getProxyConnection(), getProxyCommand(),
+ progress.newChild(10)));
+ session.connect(getTimeout() * 1000); // the fJSchService doesn't pass the timeout correctly
+ }
+ }
if (!progress.isCanceled()) {
fSessions.add(session);
return session;
diff --git a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionAttributes.java b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionAttributes.java
index 75ae166..ef6b43d 100644
--- a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionAttributes.java
+++ b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionAttributes.java
@@ -31,6 +31,8 @@ public class JSchConnectionAttributes {
public static final String USERNAME_ATTR = "JSCH_USERNAME_ATTR"; //$NON-NLS-1$
public static final String PASSWORD_ATTR = "JSCH_PASSWORD_ATTR"; //$NON-NLS-1$
public static final String PORT_ATTR = "JSCH_PORT_ATTR"; //$NON-NLS-1$
+ public static final String PROXYCONNECTION_ATTR = "JSCH_PROXYCONNECTION_ATTR"; //$NON-NLS-1$
+ public static final String PROXYCOMMAND_ATTR = "JSCH_PROXYCOMMAND_ATTR"; //$NON-NLS-1$
public static final String IS_PASSWORD_ATTR = "JSCH_IS_PASSWORD_ATTR"; //$NON-NLS-1$
public static final String PASSPHRASE_ATTR = "JSCH_PASSPHRASE_ATTR"; //$NON-NLS-1$
public static final String KEYFILE_ATTR = "JSCH_KEYFILE_ATTR"; //$NON-NLS-1$
diff --git a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionProxyFactory.java b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionProxyFactory.java
new file mode 100644
index 0000000..d0e33d4
--- /dev/null
+++ b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionProxyFactory.java
@@ -0,0 +1,311 @@
+/*******************************************************************************
+ * Copyright (c) 2014 University of Tennessee and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * University of Tennessee (Roland Schulz) - Initial API and implementation
+ *******************************************************************************/
+package org.eclipse.remote.internal.jsch.core;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.text.MessageFormat;
+import java.util.List;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.remote.core.IRemoteConnection;
+import org.eclipse.remote.core.IRemoteProcess;
+import org.eclipse.remote.core.IRemoteProcessBuilder;
+import org.eclipse.remote.core.exception.RemoteConnectionException;
+import org.eclipse.remote.internal.jsch.core.messages.Messages;
+
+import com.jcraft.jsch.Channel;
+import com.jcraft.jsch.Proxy;
+import com.jcraft.jsch.SocketFactory;
+
+/**
+ * Creates a JSch Proxy. Supports both command proxies, as well as the ssh build-in
+ * stream forwarding.
+ *
+ * @author rschulz
+ *
+ */
+public class JSchConnectionProxyFactory {
+ private static class CommandProxy implements Proxy {
+ private String command;
+ private IRemoteProcess process;
+ private IRemoteConnection connection;
+ private IProgressMonitor monitor;
+
+ private CommandProxy(IRemoteConnection connection, String command, IProgressMonitor monitor) {
+ if (command == null || connection == null || monitor == null)
+ throw new IllegalArgumentException();
+ this.command = command;
+ this.connection = connection;
+ this.monitor = monitor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jcraft.jsch.Proxy#close()
+ */
+ @Override
+ public void close() {
+ process.destroy();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jcraft.jsch.Proxy#connect(com.jcraft.jsch.SocketFactory, java.lang.String, int, int)
+ */
+ @Override
+ public void connect(SocketFactory socket_factory, String host,
+ int port, int timeout) throws IOException {
+ assert connection != null : "connect should only be called once"; //$NON-NLS-1$
+ try {
+ if (timeout == 0) {
+ timeout = 10000; // default to 10s
+ }
+ final int waitTime = 50;
+ final int waitSteps = timeout / waitTime;
+ SubMonitor subMon;
+
+ // Open connection if it isn't already opened
+ if (!connection.isOpen()) {
+ subMon = SubMonitor.convert(monitor, waitSteps * 2);
+ try {
+ connection.open(subMon.newChild(waitSteps));
+ } catch (RemoteConnectionException e) {
+ throw new IOException(e);
+ }
+ } else {
+ subMon = SubMonitor.convert(monitor, waitSteps);
+ }
+
+ // Start command
+ command = command.replace("%h", host); //$NON-NLS-1$
+ command = command.replace("%p", Integer.toString(port)); //$NON-NLS-1$
+ List<String> cmd = new ArgumentParser(command).getTokenList();
+ IRemoteProcessBuilder pb = connection.getProcessBuilder(cmd);
+ process = pb.start();
+
+ // Wait on command to produce stdout output
+ long endTime = System.currentTimeMillis() + timeout;
+ boolean bOutputAvailable, bProcessComplete, bTimedOut, bCanceled;
+ do {
+ try {
+ Thread.sleep(waitTime);
+ subMon.worked(1);
+ } catch (InterruptedException e) {
+ /* ignore */
+ }
+ bOutputAvailable = (getInputStream().available() != 0);
+ bProcessComplete = process.isCompleted();
+ bTimedOut = System.currentTimeMillis() > endTime;
+ bCanceled = subMon.isCanceled();
+ } while (!bOutputAvailable && !bProcessComplete && !bTimedOut && !bCanceled);
+
+ // If no output was produced before process died, throw an exception with the stderr output
+ final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+ if (getInputStream().available() == 0 || process.isCompleted()) {
+ String msg = ""; //$NON-NLS-1$
+ while (bufferedReader.ready()) {
+ msg += (char) bufferedReader.read();
+ }
+ msg = msg.trim();
+
+ if (!process.isCompleted()) {
+ process.destroy();
+ }
+
+ String cause = Messages.JSchConnectionProxyFactory_failed;
+ if (bTimedOut) {
+ cause = Messages.JSchConnectionProxyFactory_timedOut;
+ } else if (bCanceled) {
+ cause = Messages.JSchConnectionProxyFactory_wasCanceled;
+ }
+ throw new IOException(MessageFormat.format(Messages.JSchConnectionProxyFactory_ProxyCommandFailed,
+ command, cause, msg));
+ }
+
+ // Dump the stderr to log
+ new Thread() {
+ @Override
+ public void run() {
+ final ILog log = Activator.getDefault().getLog();
+ String line;
+ try {
+ while ((line = bufferedReader.readLine()) != null) {
+ log.log(new Status(IStatus.INFO, Activator.getUniqueIdentifier(),
+ IStatus.OK, line, null));
+ }
+ } catch (IOException e) {
+ Activator.log(e);
+ }
+ };
+ }.start();
+ } finally {
+ // Not valid to call connect again or for any other command to access these variables. Setting to null to ensure.
+ connection = null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jcraft.jsch.Proxy#getInputStream()
+ */
+ @Override
+ public InputStream getInputStream() {
+ return process.getInputStream();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jcraft.jsch.Proxy#getOutputStream()
+ */
+ @Override
+ public OutputStream getOutputStream() {
+ return process.getOutputStream();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jcraft.jsch.Proxy#getSocket()
+ */
+ @Override
+ public Socket getSocket() {
+ return null;
+ }
+ }
+
+ private static class SSHForwardProxy implements Proxy {
+ private Channel channel;
+ private JSchConnection connection;
+ private IProgressMonitor monitor;
+
+ private SSHForwardProxy(IRemoteConnection proxyConnection, IProgressMonitor monitor) {
+ if (proxyConnection == null || monitor == null)
+ throw new IllegalArgumentException();
+ this.connection = (JSchConnection) proxyConnection; // only JSch supported for ForwardProxy
+ this.monitor = monitor;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jcraft.jsch.Proxy#close()
+ */
+ @Override
+ public void close() {
+ channel.disconnect();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jcraft.jsch.Proxy#connect(com.jcraft.jsch.SocketFactory, java.lang.String, int, int)
+ */
+ @Override
+ public void connect(SocketFactory socket_factory, String host, int port,
+ int timeout) throws Exception {
+ assert connection != null : "connect should only be called once"; //$NON-NLS-1$
+ try {
+ if (!connection.isOpen()) {
+ try {
+ connection.open(monitor);
+ } catch (RemoteConnectionException e) {
+ throw new IOException(e);
+ }
+ }
+ channel = connection.getStreamForwarder(host, port);
+ } finally {
+ // Not valid to call connect again or for any other command to access these variables. Setting to null to ensure.
+ connection = null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jcraft.jsch.Proxy#getInputStream()
+ */
+ @Override
+ public InputStream getInputStream() {
+ try {
+ return channel.getInputStream();
+ } catch (IOException e) {
+ Activator.log(e);
+ return null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jcraft.jsch.Proxy#getOutputStream()
+ */
+ @Override
+ public OutputStream getOutputStream() {
+ try {
+ return channel.getOutputStream();
+ } catch (IOException e) {
+ Activator.log(e);
+ return null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jcraft.jsch.Proxy#getSocket()
+ */
+ @Override
+ public Socket getSocket() {
+ return null;
+ }
+ }
+
+ /**
+ * Creates a (local or remote) command proxy.
+ *
+ * @param connection
+ * Any (local or remote) connection. Cannot be null.
+ * @param command
+ * A valid proxy command. Cannot be null or empty.
+ * @param monitor
+ * A valid progress monitor. Cannot be null.
+ * @return ssh proxy
+ */
+ public static Proxy createCommandProxy(IRemoteConnection connection, String command, IProgressMonitor monitor) {
+ return new CommandProxy(connection, command, monitor);
+ }
+
+ /**
+ * Creates a ssh forward proxy.
+ *
+ * @param proxyConnection
+ * The Jsch proxy connection. Cannot be null.
+ * @param monitor
+ * A valid progress monitor. Cannot be null.
+ * @return ssh proxy
+ */
+ public static Proxy createForwardProxy(IRemoteConnection proxyConnection, IProgressMonitor monitor) {
+ return new SSHForwardProxy(proxyConnection, monitor);
+ }
+}
diff --git a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionWorkingCopy.java b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionWorkingCopy.java
index 6b6fd8e..8a75fed 100644
--- a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionWorkingCopy.java
+++ b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/JSchConnectionWorkingCopy.java
@@ -110,6 +110,26 @@ public class JSchConnectionWorkingCopy extends JSchConnection implements IRemote
/*
* (non-Javadoc)
*
+ * @see org.eclipse.remote.internal.jsch.core.JSchConnection#getProxyCommand()
+ */
+ @Override
+ public String getProxyCommand() {
+ return fWorkingAttributes.getAttribute(JSchConnectionAttributes.PROXYCOMMAND_ATTR, EMPTY_STRING);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.remote.internal.jsch.core.JSchConnection#getProxyConnectionName()
+ */
+ @Override
+ public String getProxyConnectionName() {
+ return fWorkingAttributes.getAttribute(JSchConnectionAttributes.PROXYCONNECTION_ATTR, EMPTY_STRING);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
* @see org.eclipse.remote.internal.jsch.core.JSchConnection#getTimeout()
*/
@Override
@@ -255,6 +275,28 @@ public class JSchConnectionWorkingCopy extends JSchConnection implements IRemote
fWorkingAttributes.setAttribute(JSchConnectionAttributes.PORT_ATTR, Integer.toString(port));
}
+ /**
+ * Sets the proxy command. Set to empty string for no command
+ *
+ * @param proxyCommand
+ * proxy command
+ */
+ public void setProxyCommand(String proxyCommand) {
+ fIsDirty = true;
+ fWorkingAttributes.setAttribute(JSchConnectionAttributes.PROXYCOMMAND_ATTR, proxyCommand);
+ }
+
+ /**
+ * Sets the proxy connection name. Set to empty string for no proxy connection
+ *
+ * @param proxyCommand
+ * proxy connection name
+ */
+ public void setProxyConnectionName(String proxyConnectionName) {
+ fIsDirty = true;
+ fWorkingAttributes.setAttribute(JSchConnectionAttributes.PROXYCONNECTION_ATTR, proxyConnectionName);
+ }
+
public void setTimeout(int timeout) {
fIsDirty = true;
fWorkingAttributes.setAttribute(JSchConnectionAttributes.TIMEOUT_ATTR, Integer.toString(timeout));
diff --git a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/messages/Messages.java b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/messages/Messages.java
index 30993cb..221a3e8 100755
--- a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/messages/Messages.java
+++ b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/messages/Messages.java
@@ -38,6 +38,10 @@ public class Messages extends NLS {
public static String JSchConnectionManager_connection_with_name_exists;
public static String JSchConnectionManager_cannotRemoveOpenConnection;
public static String JSchConnectionManager_invalidConnectionType;
+ public static String JSchConnectionProxyFactory_failed;
+ public static String JSchConnectionProxyFactory_ProxyCommandFailed;
+ public static String JSchConnectionProxyFactory_timedOut;
+ public static String JSchConnectionProxyFactory_wasCanceled;
public static String JSchProcessBuilder_Connection_is_not_open;
public static String JschFileStore_Connection_is_not_open;
public static String JschFileStore_File_doesnt_exist;
diff --git a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/messages/messages.properties b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/messages/messages.properties
index aaf04da..b9152f9 100755
--- a/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/messages/messages.properties
+++ b/bundles/org.eclipse.remote.jsch.core/src/org/eclipse/remote/internal/jsch/core/messages/messages.properties
@@ -28,6 +28,10 @@ JSchConnection_username_must_be_set=Username must be set before opening connecti
JSchConnectionManager_connection_with_name_exists=A connection with name \"{0}\" already exists
JSchConnectionManager_cannotRemoveOpenConnection=Cannot remove an open connection
JSchConnectionManager_invalidConnectionType=Invalid connection type
+JSchConnectionProxyFactory_failed=failed
+JSchConnectionProxyFactory_ProxyCommandFailed=Proxy command "{0}" {1} and printed message "{2}"
+JSchConnectionProxyFactory_timedOut=timed out
+JSchConnectionProxyFactory_wasCanceled=was canceled
JSchProcessBuilder_Connection_is_not_open=Connection is not open
JschFileStore_Connection_is_not_open=Connection is not open
JschFileStore_File_doesnt_exist=File {0} doesn't exist
diff --git a/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/messages/Messages.java b/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/messages/Messages.java
index 24e9db4..d14e5e2 100755
--- a/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/messages/Messages.java
+++ b/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/messages/Messages.java
@@ -25,6 +25,12 @@ public class Messages extends NLS {
public static String JSchConnectionPage_Edit_Connection;
public static String JSchConnectionPage_Edit_properties_of_an_existing_connection;
public static String JSchConnectionPage_Please_enter_name_for_connection;
+ public static String JSchConnectionPage_Proxy;
+ public static String JSchConnectionPage_Help;
+ public static String JSchConnectionPage_SelectCommand;
+ public static String JSchConnectionPage_SelectConnection;
+ public static String JSchConnectionPage_Settings0;
+ public static String JSchConnectionPage_selectProxyConnection;
public static String JSchFileSystemContributor_0;
public static String JSchNewConnectionPage_Advanced;
public static String JSchNewConnectionPage_Connection_name;
diff --git a/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/messages/messages.properties b/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/messages/messages.properties
index 89e22f3..6f02a26 100755
--- a/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/messages/messages.properties
+++ b/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/messages/messages.properties
@@ -12,6 +12,12 @@ JSchConnectionPage_A_connection_with_that_name_already_exists=A connection with
JSchConnectionPage_Edit_Connection=Edit Connection
JSchConnectionPage_Edit_properties_of_an_existing_connection=Edit properties of an existing connection
JSchConnectionPage_Please_enter_name_for_connection=Please enter a name for the connection
+JSchConnectionPage_Proxy=SSH Proxy Settings
+JSchConnectionPage_Help=If 'Local' is selected and proxy command is empty, no proxy is used.\nSee <a href=\"org.eclipse.ui.net.NetPreferences\">Network Connections</a> for SOCKS and HTTP proxy options.
+JSchConnectionPage_SelectCommand=Enter a local or remote command such as 'nc %h %p'. Can be empty for an ssh gateway.
+JSchConnectionPage_SelectConnection=Select 'Remote' for an ssh gateway or a remote proxy command.
+JSchConnectionPage_Settings0=Connection Settings
+JSchConnectionPage_selectProxyConnection=Please select a proxy connection
JSchFileSystemContributor_0=Browse File System
JSchNewConnectionPage_Advanced=Advanced
JSchNewConnectionPage_Connection_name=Connection name:
diff --git a/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/wizards/JSchConnectionPage.java b/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/wizards/JSchConnectionPage.java
index d45f0e8..efad260 100755
--- a/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/wizards/JSchConnectionPage.java
+++ b/bundles/org.eclipse.remote.jsch.ui/src/org/eclipse/remote/internal/jsch/ui/wizards/JSchConnectionPage.java
@@ -17,6 +17,7 @@ import java.util.Map;
import java.util.Set;
import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.remote.core.IRemoteConnection;
import org.eclipse.remote.core.IRemoteConnectionManager;
import org.eclipse.remote.core.RemoteServices;
import org.eclipse.remote.core.exception.RemoteConnectionException;
@@ -25,6 +26,7 @@ import org.eclipse.remote.internal.jsch.core.JSchConnection;
import org.eclipse.remote.internal.jsch.core.JSchConnectionAttributes;
import org.eclipse.remote.internal.jsch.core.JSchConnectionWorkingCopy;
import org.eclipse.remote.internal.jsch.ui.messages.Messages;
+import org.eclipse.remote.ui.widgets.RemoteConnectionWidget;
import org.eclipse.remote.ui.widgets.RemoteFileWidget;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
@@ -41,6 +43,7 @@ import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.events.IExpansionListener;
@@ -74,6 +77,8 @@ public class JSchConnectionPage extends WizardPage {
private final IRemoteConnectionManager fConnectionManager;
private final DataModifyListener fDataModifyListener = new DataModifyListener();
+ private RemoteConnectionWidget fProxyConnectionWidget;
+ private Text fProxyCommandText;
public JSchConnectionPage(IRemoteConnectionManager connMgr) {
super(Messages.JSchNewConnectionPage_New_Connection);
@@ -82,7 +87,7 @@ public class JSchConnectionPage extends WizardPage {
}
/**
- * Create controls for the bottom (hideable) composite
+ * Create controls for the bottom (hideable) advanced composite
*
* @param mold
*
@@ -96,13 +101,15 @@ public class JSchConnectionPage extends WizardPage {
@Override
public void expansionStateChanged(ExpansionEvent e) {
- Point newSize = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT);
- Point currentSize = parent.getSize();
- int deltaY = newSize.y - currentSize.y;
- Point shellSize = getShell().getSize();
- shellSize.y += deltaY;
- getShell().setSize(shellSize);
- getShell().layout(true, true);
+ for (int i = 0; i < 2; i++) { // sometimes the size compute isn't correct on first try
+ Point newSize = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ Point currentSize = parent.getSize();
+ int deltaY = newSize.y - currentSize.y;
+ Point shellSize = getShell().getSize();
+ shellSize.y += deltaY;
+ getShell().setSize(shellSize);
+ getShell().layout(true, true);
+ }
}
@Override
@@ -112,25 +119,36 @@ public class JSchConnectionPage extends WizardPage {
});
Composite advancedComp = new Composite(expComp, SWT.NONE);
- advancedComp.setLayout(new GridLayout(2, false));
+ advancedComp.setLayout(new GridLayout(1, false));
advancedComp.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
- Label portLabel = new Label(advancedComp, SWT.NONE);
+ Group settingsComp = new Group(advancedComp, SWT.NONE);
+ settingsComp.setText(Messages.JSchConnectionPage_Settings0);
+ settingsComp.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ settingsComp.setLayout(new GridLayout(2, false));
+
+ Label portLabel = new Label(settingsComp, SWT.NONE);
portLabel.setText(Messages.JSchNewConnectionPage_Port);
portLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
- fPortText = new Text(advancedComp, SWT.BORDER | SWT.SINGLE);
+ fPortText = new Text(settingsComp, SWT.BORDER | SWT.SINGLE);
fPortText.setText(Integer.toString(JSchConnection.DEFAULT_PORT));
fPortText.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
setTextFieldWidthInChars(fPortText, 5);
- Label timeoutLabel = new Label(advancedComp, SWT.NONE);
+ Label timeoutLabel = new Label(settingsComp, SWT.NONE);
timeoutLabel.setText(Messages.JSchNewConnectionPage_Timeout);
timeoutLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
- fTimeoutText = new Text(advancedComp, SWT.BORDER | SWT.SINGLE);
+ fTimeoutText = new Text(settingsComp, SWT.BORDER | SWT.SINGLE);
fTimeoutText.setText(Integer.toString(JSchConnection.DEFAULT_TIMEOUT));
fTimeoutText.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
setTextFieldWidthInChars(fTimeoutText, 5);
+ Group proxyComp = new Group(advancedComp, SWT.NONE);
+ proxyComp.setText(Messages.JSchConnectionPage_Proxy);
+ proxyComp.setLayout(new GridLayout(1, false));
+ proxyComp.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ createProxyControls(proxyComp);
+
expComp.setClient(advancedComp);
}
@@ -248,6 +266,33 @@ public class JSchConnectionPage extends WizardPage {
updateEnablement();
}
+ /**
+ * Create controls for the bottom (hideable) proxy composite
+ *
+ * @param mold
+ *
+ */
+ private void createProxyControls(final Composite proxyComp) {
+ Label lblConnection = new Label(proxyComp, SWT.WRAP);
+ lblConnection.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ lblConnection.setText(Messages.JSchConnectionPage_SelectConnection);
+
+ fProxyConnectionWidget = new RemoteConnectionWidget(proxyComp, SWT.NONE, null, 0, null);
+
+ Label lblCommand = new Label(proxyComp, SWT.WRAP);
+ lblCommand.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ lblCommand.setText(Messages.JSchConnectionPage_SelectCommand);
+
+ fProxyCommandText = new Text(proxyComp, SWT.BORDER);
+ fProxyCommandText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+
+ Link link = new Link(proxyComp, SWT.WRAP);
+ final GridData linkLayoutData = new GridData(GridData.FILL_HORIZONTAL);
+ link.setLayoutData(linkLayoutData);
+ linkLayoutData.widthHint = 400;
+ link.setText(Messages.JSchConnectionPage_Help);
+ }
+
public JSchConnectionWorkingCopy getConnection() {
return fConnection;
}
@@ -285,6 +330,9 @@ public class JSchConnectionPage extends WizardPage {
fPassphraseText.setText(fConnection.getPassphrase());
fFileWidget.setLocationPath(fConnection.getKeyFile());
}
+ fProxyCommandText.setText(fConnection.getProxyCommand());
+
+ fProxyConnectionWidget.setConnection(fConnection.getProxyConnection());
} else {
fConnectionName.setText(fInitialName);
String host = fInitialAttributes.get(JSchConnectionAttributes.ADDRESS_ATTR);
@@ -319,6 +367,8 @@ public class JSchConnectionPage extends WizardPage {
if (file != null) {
fFileWidget.setLocationPath(file);
}
+ fProxyConnectionWidget.setConnection(RemoteServices.getLocalServices().getConnectionManager().getConnection(
+ IRemoteConnectionManager.LOCAL_CONNECTION_NAME));
}
}
@@ -331,6 +381,14 @@ public class JSchConnectionPage extends WizardPage {
fPassphraseText.addModifyListener(fDataModifyListener);
fPortText.addModifyListener(fDataModifyListener);
fTimeoutText.addModifyListener(fDataModifyListener);
+ fProxyCommandText.addModifyListener(fDataModifyListener);
+ fProxyConnectionWidget.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ validateFields();
+ getContainer().updateButtons();
+ }
+ });
}
public void setAddress(String address) {
@@ -420,6 +478,17 @@ public class JSchConnectionPage extends WizardPage {
if (fConnection.getPort() != port) {
fConnection.setPort(port);
}
+ if (!fConnection.getProxyCommand().equals(fProxyCommandText.getText().trim())) {
+ fConnection.setProxyCommand(fProxyCommandText.getText().trim());
+ }
+ IRemoteConnection proxyConnection = fProxyConnectionWidget.getConnection();
+ String proxyConnectionName = ""; //$NON-NLS-1$
+ if (proxyConnection != null && proxyConnection.getRemoteServices() != RemoteServices.getLocalServices()) {
+ proxyConnectionName = proxyConnection.getName();
+ }
+ if (!fConnection.getProxyConnectionName().equals(proxyConnectionName)) {
+ fConnection.setProxyConnectionName(proxyConnectionName);
+ }
}
}
@@ -461,9 +530,13 @@ public class JSchConnectionPage extends WizardPage {
if (message == null) {
message = validatePasskey();
}
+ if (message == null && fProxyConnectionWidget.getConnection() == null) {
+ message = Messages.JSchConnectionPage_selectProxyConnection;
+ }
if (message == null) {
message = validateAdvanced();
}
+
setErrorMessage(message);
setPageComplete(message == null);
}

Back to the top