From f82b22249e9f091d24bdf6a036825da63a7ac3b0 Mon Sep 17 00:00:00 2001 From: Martin Oberhuber Date: Thu, 7 Dec 2006 15:25:50 +0000 Subject: [PATCH] Support Terminal Ssh Preferences, Proxy, Passphrase and KI authentication --- .../META-INF/MANIFEST.MF | 4 + .../tm/terminal/ssh/SshConnection.java | 260 ++++++++++++++++-- 2 files changed, 238 insertions(+), 26 deletions(-) diff --git a/org.eclipse.tm.terminal.ssh/META-INF/MANIFEST.MF b/org.eclipse.tm.terminal.ssh/META-INF/MANIFEST.MF index 4f6c1d2bf1b..83df069e842 100644 --- a/org.eclipse.tm.terminal.ssh/META-INF/MANIFEST.MF +++ b/org.eclipse.tm.terminal.ssh/META-INF/MANIFEST.MF @@ -8,6 +8,10 @@ Bundle-Localization: plugin Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime, org.eclipse.tm.terminal, + org.eclipse.ui, + org.eclipse.team.cvs.core;bundle-version="[3.2.0,4.0.0)", + org.eclipse.team.cvs.ssh2;bundle-version="[3.2.0,4.0.0)", + org.eclipse.team.cvs.ui;bundle-version="[3.2.0,4.0.0)", com.jcraft.jsch;bundle-version="[0.1.28,2.0.0)" Eclipse-LazyStart: true Bundle-RequiredExecutionEnvironment: J2SE-1.4 diff --git a/org.eclipse.tm.terminal.ssh/src/org/eclipse/tm/terminal/ssh/SshConnection.java b/org.eclipse.tm.terminal.ssh/src/org/eclipse/tm/terminal/ssh/SshConnection.java index 2db4d66949a..6cf4b0d7793 100644 --- a/org.eclipse.tm.terminal.ssh/src/org/eclipse/tm/terminal/ssh/SshConnection.java +++ b/org.eclipse.tm.terminal.ssh/src/org/eclipse/tm/terminal/ssh/SshConnection.java @@ -13,17 +13,28 @@ package org.eclipse.tm.terminal.ssh; import java.io.IOException; import java.io.InputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Platform; import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.window.Window; import org.eclipse.swt.widgets.Display; +import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin; +import org.eclipse.team.internal.ccvs.ssh2.CVSSSH2Plugin; +import org.eclipse.team.internal.ccvs.ssh2.ISSHContants; +import org.eclipse.team.internal.ccvs.ui.KeyboardInteractiveDialog; +import org.eclipse.team.internal.ccvs.ui.UserValidationDialog; import org.eclipse.tm.terminal.ITerminalControl; import org.eclipse.tm.terminal.Logger; import org.eclipse.tm.terminal.TerminalState; import com.jcraft.jsch.ChannelShell; +import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Proxy; +import com.jcraft.jsch.ProxyHTTP; +import com.jcraft.jsch.ProxySOCKS5; import com.jcraft.jsch.Session; import com.jcraft.jsch.UserInfo; @@ -35,17 +46,142 @@ class SshConnection extends Thread { fConn = conn; fControl.setState(TerminalState.CONNECTING); } + + //---------------------------------------------------------------------- + // + //---------------------------------------------------------------------- + private static String current_ssh_home = null; + private static String current_pkeys = ""; //$NON-NLS-1$ + static String SSH_HOME_DEFAULT = null; + static { + String ssh_dir_name = ".ssh"; //$NON-NLS-1$ + + // Windows doesn't like files or directories starting with a dot. + if (Platform.getOS().equals(Platform.OS_WIN32)) { + ssh_dir_name = "ssh"; //$NON-NLS-1$ + } + SSH_HOME_DEFAULT = System.getProperty("user.home"); //$NON-NLS-1$ + if (SSH_HOME_DEFAULT != null) { + SSH_HOME_DEFAULT = SSH_HOME_DEFAULT + java.io.File.separator + ssh_dir_name; + } + } + + // Load ssh prefs from Team/CVS for now. + // TODO do our own preference page. + static void loadSshPrefs(JSch jsch) + { + IPreferenceStore store = CVSSSH2Plugin.getDefault().getPreferenceStore(); + String ssh_home = store.getString(ISSHContants.KEY_SSH2HOME); + String pkeys = store.getString(ISSHContants.KEY_PRIVATEKEY); + + try { + if (ssh_home.length() == 0) + ssh_home = SSH_HOME_DEFAULT; + + if (current_ssh_home == null || !current_ssh_home.equals(ssh_home)) { + loadKnownHosts(jsch, ssh_home); + current_ssh_home = ssh_home; + } + + if (!current_pkeys.equals(pkeys)) { + java.io.File file; + String[] pkey = pkeys.split(","); //$NON-NLS-1$ + String[] _pkey = current_pkeys.split(","); //$NON-NLS-1$ + current_pkeys = ""; //$NON-NLS-1$ + for (int i = 0; i < pkey.length; i++) { + file = new java.io.File(pkey[i]); + if (!file.isAbsolute()) { + file = new java.io.File(ssh_home, pkey[i]); + } + if (file.exists()) { + boolean notyet = true; + for (int j = 0; j < _pkey.length; j++) { + if (pkey[i].equals(_pkey[j])) { + notyet = false; + break; + } + } + if (notyet) + jsch.addIdentity(file.getPath()); + if (current_pkeys.length() == 0) { + current_pkeys = pkey[i]; + } else { + current_pkeys += ("," + pkey[i]); //$NON-NLS-1$ + } + } + } + } + } catch (Exception e) { + } + + } + + static void loadKnownHosts(JSch jsch, String ssh_home){ + try { + java.io.File file; + file=new java.io.File(ssh_home, "known_hosts"); //$NON-NLS-1$ + jsch.setKnownHosts(file.getPath()); + } catch (Exception e) { + } + } + + static Proxy loadSshProxyPrefs() { + //TODO Get rid of discouraged access when bug 154100 is fixed + boolean useProxy = CVSProviderPlugin.getPlugin().isUseProxy(); + Proxy proxy = null; + if (useProxy) { + String _type = CVSProviderPlugin.getPlugin().getProxyType(); + String _host = CVSProviderPlugin.getPlugin().getProxyHost(); + String _port = CVSProviderPlugin.getPlugin().getProxyPort(); + + boolean useAuth = CVSProviderPlugin.getPlugin().isUseProxyAuth(); + String _user = ""; //$NON-NLS-1$ + String _pass = ""; //$NON-NLS-1$ + + // Retrieve username and password from keyring. + if(useAuth){ + _user=CVSProviderPlugin.getPlugin().getProxyUser(); + _pass=CVSProviderPlugin.getPlugin().getProxyPassword(); + } + + String proxyhost = _host + ":" + _port; //$NON-NLS-1$ + if (_type.equals(CVSProviderPlugin.PROXY_TYPE_HTTP)) { + proxy = new ProxyHTTP(proxyhost); + if (useAuth) { + ((ProxyHTTP) proxy).setUserPasswd(_user, _pass); + } + } else if (_type.equals(CVSProviderPlugin.PROXY_TYPE_SOCKS5)) { + proxy = new ProxySOCKS5(proxyhost); + if (useAuth) { + ((ProxySOCKS5) proxy).setUserPasswd(_user, _pass); + } + } + } + return proxy; + } + + //---------------------------------------------------------------------- + // + //---------------------------------------------------------------------- + public void run() { try { int nTimeout = fConn.getTelnetSettings().getTimeout() * 1000; String host = fConn.getTelnetSettings().getHost(); String user = fConn.getTelnetSettings().getUser(); + String password = fConn.getTelnetSettings().getPassword(); int port=fConn.getTelnetSettings().getPort(); - Session session=fConn.getJsch().getSession(user, host, port); - //session.setPassword("your password"); - // username and password will be given via UserInfo interface. - UserInfo ui=new MyUserInfo(fConn.getTelnetSettings().getPassword()); + loadSshPrefs(fConn.getJsch()); + Proxy proxy = loadSshProxyPrefs(); + Session session=fConn.getJsch().getSession(user, host, port); + if (proxy != null) { + session.setProxy(proxy); + } + session.setTimeout(0); //never time out once connected + + session.setPassword(password); + UserInfo ui=new MyUserInfo(user, password); session.setUserInfo(ui); // java.util.Hashtable config=new java.util.Hashtable(); @@ -56,26 +192,13 @@ class SshConnection extends Thread { session.connect(nTimeout); // making connection with timeout. ChannelShell channel=(ChannelShell) session.openChannel("shell"); //$NON-NLS-1$ - - //hmm, now it gets a bit complicated - // Input and output streams are somehow confusing - PipedInputStream pin = new PipedInputStream(); - PipedOutputStream out = new PipedOutputStream(pin); - - PipedOutputStream pout = new PipedOutputStream(); - PipedInputStream in = new PipedInputStream(pout); - - - channel.setInputStream(pin); - channel.setOutputStream(pout); channel.connect(); - - fConn.setInputStream(in); - fConn.setOutputStream(out); + fConn.setInputStream(channel.getInputStream()); + fConn.setOutputStream(channel.getOutputStream()); fConn.setChannel(channel); fControl.setState(TerminalState.CONNECTED); // read data until the connection gets terminated - readDataForever(in); + readDataForever(fConn.getInputStream()); } catch (JSchException e) { connectFailed(e.getMessage(),e.getMessage()); } catch (IOException e) { @@ -100,10 +223,22 @@ class SshConnection extends Thread { fControl.setState(TerminalState.CLOSED); } + protected static Display getStandardDisplay() { + Display display = Display.getCurrent(); + if( display==null ) { + display = Display.getDefault(); + } + return display; + } + private static class MyUserInfo implements UserInfo { private String fPassword; + private String fPassphrase; + private int fAttemptCount; + private final String fUser; - public MyUserInfo(String password) { + public MyUserInfo(String user, String password) { + fUser = user; fPassword = password; } public String getPassword() { @@ -119,14 +254,38 @@ class SshConnection extends Thread { }); return retval[0]; } + private String promptSecret(final String message) { + final String[] retval = new String[1]; + final String finUser = fUser; + getStandardDisplay().syncExec(new Runnable() { + public void run() { + //TODO discouraged access: Write our own UserValidationDialog + UserValidationDialog uvd = new UserValidationDialog(null, null, + finUser, message); + uvd.setUsernameMutable(false); + if (uvd.open() == Window.OK) { + retval[0] = uvd.getPassword(); + } else { + retval[0] = null; + } + } + }); + return retval[0]; + } public String getPassphrase() { - return null; + return fPassphrase; } public boolean promptPassphrase(String message) { - return true; + fPassphrase = promptSecret(message); + return (fPassphrase!=null); } public boolean promptPassword(final String message) { - return true; + String _password = promptSecret(message); + if (_password!=null) { + fPassword=_password; + return true; + } + return false; } public void showMessage(final String message) { Display.getDefault().syncExec(new Runnable() { @@ -135,6 +294,55 @@ class SshConnection extends Thread { } }); } + public String[] promptKeyboardInteractive(final String destination, + final String name, final String instruction, + final String[] prompt, final boolean[] echo) + { + if (prompt.length == 0) { + // No need to prompt, just return an empty String array + return new String[0]; + } + try{ + if (fAttemptCount == 0 && fPassword != null && prompt.length == 1 && prompt[0].trim().equalsIgnoreCase("password:")) { //$NON-NLS-1$ + // Return the provided password the first time but always prompt on subsequent tries + fAttemptCount++; + return new String[] { fPassword }; + } + final String[][] finResult = new String[1][]; + getStandardDisplay().syncExec(new Runnable() { + public void run() { + //TODO discouraged access: write our own KeyboardInteractiveDialog + KeyboardInteractiveDialog dialog = new KeyboardInteractiveDialog(null, + null, destination, name, instruction, prompt, echo); + dialog.open(); + finResult[0]=dialog.getResult(); + } + }); + String[] result=finResult[0]; + if (result == null) + return null; // canceled + if (result.length == 1 && prompt.length == 1 && prompt[0].trim().equalsIgnoreCase("password:")) { //$NON-NLS-1$ + fPassword = result[0]; + } + fAttemptCount++; + return result; + } + catch(OperationCanceledException e){ + return null; + } + } + /** + * Callback to indicate that a connection is about to be attempted + */ + public void aboutToConnect() { + fAttemptCount = 0; + } + /** + * Callback to indicate that a connection was made + */ + public void connectionMade() { + fAttemptCount = 0; + } } private void connectFailed(String terminalText, String msg) {