1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-04-23 14:42:11 +02:00

bug 143417 - Fix notifications for lost ssh sessions

This commit is contained in:
Martin Oberhuber 2006-05-30 19:09:02 +00:00
parent a8614a0cd3
commit 49c9ea405a
6 changed files with 332 additions and 31 deletions

View file

@ -14,6 +14,7 @@ package org.eclipse.rse.connectorservice.ssh;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket; import java.net.Socket;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -22,17 +23,32 @@ import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.rse.core.SystemBasePlugin;
import org.eclipse.rse.core.subsystems.AbstractConnectorService; import org.eclipse.rse.core.subsystems.AbstractConnectorService;
import org.eclipse.rse.core.subsystems.CommunicationsEvent;
import org.eclipse.rse.core.subsystems.IConnectorService;
import org.eclipse.rse.core.subsystems.SubSystemConfiguration;
import org.eclipse.rse.model.IHost; import org.eclipse.rse.model.IHost;
import org.eclipse.rse.model.ISystemRegistry;
import org.eclipse.rse.services.clientserver.messages.SystemMessage;
import org.eclipse.rse.services.ssh.ISshSessionProvider; import org.eclipse.rse.services.ssh.ISshSessionProvider;
import org.eclipse.rse.ui.ISystemMessages;
import org.eclipse.rse.ui.RSEUIPlugin;
import org.eclipse.rse.ui.messages.SystemMessageDialog;
import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin; import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin;
import org.eclipse.team.internal.ccvs.core.util.Util; import org.eclipse.team.internal.ccvs.core.util.Util;
import org.eclipse.team.internal.ccvs.ssh2.CVSSSH2Plugin; import org.eclipse.team.internal.ccvs.ssh2.CVSSSH2Plugin;
import org.eclipse.team.internal.ccvs.ssh2.ISSHContants; import org.eclipse.team.internal.ccvs.ssh2.ISSHContants;
import org.eclipse.team.internal.ccvs.ui.KeyboardInteractiveDialog; import org.eclipse.team.internal.ccvs.ui.KeyboardInteractiveDialog;
import org.eclipse.team.internal.ccvs.ui.UserValidationDialog; import org.eclipse.team.internal.ccvs.ui.UserValidationDialog;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException; import com.jcraft.jsch.JSchException;
@ -52,6 +68,7 @@ public class SshConnectorService extends AbstractConnectorService implements ISs
private static final int SSH_DEFAULT_PORT = 22; private static final int SSH_DEFAULT_PORT = 22;
private static JSch jsch=new JSch(); private static JSch jsch=new JSch();
private Session session; private Session session;
private SessionLostHandler fSessionLostHandler;
public SshConnectorService(IHost host) { public SshConnectorService(IHost host) {
//TODO the port parameter doesnt really make sense here since //TODO the port parameter doesnt really make sense here since
@ -59,6 +76,7 @@ public class SshConnectorService extends AbstractConnectorService implements ISs
//setPort() on our base class -- I assume the port is meant to //setPort() on our base class -- I assume the port is meant to
//be a local port. //be a local port.
super("SSH Connector Service", "SSH Connector Service Description", host, 0); super("SSH Connector Service", "SSH Connector Service Description", host, 0);
fSessionLostHandler = null;
} }
//---------------------------------------------------------------------- //----------------------------------------------------------------------
@ -243,35 +261,61 @@ public class SshConnectorService extends AbstractConnectorService implements ISs
session.setTimeout(getSshTimeoutInMillis()); session.setTimeout(getSshTimeoutInMillis());
String password = getPasswordInformation().getPassword(); String password = getPasswordInformation().getPassword();
session.setPassword(password); session.setPassword(password);
MyUserInfo ui = new MyUserInfo(user, password); MyUserInfo userInfo = new MyUserInfo(user, password);
session.setUserInfo(ui); session.setUserInfo(userInfo);
session.setSocketFactory(new ResponsiveSocketFacory(monitor)); session.setSocketFactory(new ResponsiveSocketFacory(monitor));
//java.util.Hashtable config=new java.util.Hashtable(); //java.util.Hashtable config=new java.util.Hashtable();
//config.put("StrictHostKeyChecking", "no"); //config.put("StrictHostKeyChecking", "no");
//session.setConfig(config); //session.setConfig(config);
ui.aboutToConnect(); userInfo.aboutToConnect();
try { try {
Activator.trace("connecting..."); //$NON-NLS-1$ Activator.trace("SshConnectorService.connecting..."); //$NON-NLS-1$
session.connect(); session.connect();
Activator.trace("connected"); //$NON-NLS-1$ Activator.trace("SshConnectorService.connected"); //$NON-NLS-1$
} catch (JSchException e) { } catch (JSchException e) {
Activator.trace("connect failed: "+e.toString()); //$NON-NLS-1$ Activator.trace("SshConnectorService.connect failed: "+e.toString()); //$NON-NLS-1$
if (session.isConnected()) if (session.isConnected())
session.disconnect(); session.disconnect();
throw e; throw e;
} }
ui.connectionMade(); userInfo.connectionMade();
fSessionLostHandler = new SessionLostHandler(this);
notifyConnection();
} }
public void internalDisconnect(IProgressMonitor monitor) public void internalDisconnect(IProgressMonitor monitor) throws Exception
{ {
//TODO: Check, Is disconnect being called because the network (connection) went down? //TODO Will services like the sftp service be disconnected too? Or notified?
//TODO: Fire communication event (aboutToDisconnect) -- see DStoreConnectorService.internalDisconnect() Activator.trace("SshConnectorService.disconnect"); //$NON-NLS-1$
//TODO: Wrap exception in an InvocationTargetException -- see DStoreConnectorService.internalDisconnect() try
//Will services like the sftp service be disconnected too? Or notified? {
Activator.trace("disconnect"); //$NON-NLS-1$ if (session != null) {
session.disconnect(); // Is disconnect being called because the network (connection) went down?
if (fSessionLostHandler != null && fSessionLostHandler.isSessionLost()) {
notifyError();
}
else {
// Fire comm event to signal state about to change
fireCommunicationsEvent(CommunicationsEvent.BEFORE_DISCONNECT);
}
session.disconnect();
// Fire comm event to signal state changed
notifyDisconnection();
//TODO MOB - keep the session to avoid NPEs in services (disables gc for the session!)
// session = null;
fSessionLostHandler = null;
// DKM - no need to clear uid cache
clearPasswordCache(false); // clear in-memory password
//clearUserIdCache(); // Clear any cached local user IDs
}
}
catch (Exception exc)
{
throw new java.lang.reflect.InvocationTargetException(exc);
}
} }
//TODO avoid having jsch type "Session" in the API. //TODO avoid having jsch type "Session" in the API.
@ -282,7 +326,206 @@ public class SshConnectorService extends AbstractConnectorService implements ISs
return session; return session;
} }
private static Display getStandardDisplay() { /**
* Handle session-lost events.
* This is generic for any sort of connector service.
* Most of this is extracted from dstore's ConnectionStatusListener.
*
* TODO should be refactored to make it generally available, and allow
* dstore to derive from it.
*/
public static class SessionLostHandler implements Runnable, IRunnableWithProgress
{
private IConnectorService _connection;
private boolean fSessionLost;
public SessionLostHandler(IConnectorService cs)
{
_connection = cs;
fSessionLost = false;
}
/**
* Notify that the connection has been lost. This may be called
* multiple times from multiple subsystems. The SessionLostHandler
* ensures that actual user feedback and disconnect actions are
* done only once, on the first invocation.
*/
public void sessionLost()
{
//avoid duplicate execution of sessionLost
boolean showSessionLostDlg=false;
synchronized(this) {
if (!fSessionLost) {
fSessionLost = true;
showSessionLostDlg=true;
}
}
if (showSessionLostDlg) {
Display.getDefault().asyncExec(this);
}
}
public synchronized boolean isSessionLost() {
return fSessionLost;
}
public void run()
{
Shell shell = getShell();
//TODO need a more correct message for "session lost"
//TODO allow users to reconnect from this dialog
//SystemMessage msg = RSEUIPlugin.getPluginMessage(ISystemMessages.MSG_CONNECT_UNKNOWNHOST);
SystemMessage msg = RSEUIPlugin.getPluginMessage(ISystemMessages.MSG_CONNECT_CANCELLED);
msg.makeSubstitution(_connection.getPrimarySubSystem().getHost().getAliasName());
SystemMessageDialog dialog = new SystemMessageDialog(getShell(), msg);
dialog.open();
try
{
IRunnableContext runnableContext = getRunnableContext(getShell());
// will do this.run(IProgressMonitor mon)
runnableContext.run(false,true,this); // inthread, cancellable, IRunnableWithProgress
_connection.reset();
ISystemRegistry sr = RSEUIPlugin.getDefault().getSystemRegistry();
sr.connectedStatusChange(_connection.getPrimarySubSystem(), false, true, true);
}
catch (InterruptedException exc) // user cancelled
{
if (shell != null)
showDisconnectCancelledMessage(shell, _connection.getHostName(), _connection.getPort());
}
catch (java.lang.reflect.InvocationTargetException invokeExc) // unexpected error
{
Exception exc = (Exception)invokeExc.getTargetException();
if (shell != null)
showDisconnectErrorMessage(shell, _connection.getHostName(), _connection.getPort(), exc);
}
catch (Exception e)
{
SystemBasePlugin.logError("ConnectionStatusListener: Error disconnecting", e);
}
}
public void run(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException
{
String message = null;
message = SubSystemConfiguration.getDisconnectingMessage(
_connection.getHostName(), _connection.getPort());
monitor.beginTask(message, IProgressMonitor.UNKNOWN);
try {
_connection.disconnect(monitor);
} catch (Exception exc) {
if (exc instanceof java.lang.reflect.InvocationTargetException)
throw (java.lang.reflect.InvocationTargetException) exc;
if (exc instanceof java.lang.InterruptedException)
throw (java.lang.InterruptedException) exc;
throw new java.lang.reflect.InvocationTargetException(exc);
} finally {
monitor.done();
}
}
public Shell getShell() {
Shell activeShell = SystemBasePlugin.getActiveWorkbenchShell();
if (activeShell != null) {
return activeShell;
}
IWorkbenchWindow window = null;
try {
window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
} catch (Exception e) {
return null;
}
if (window == null) {
IWorkbenchWindow[] windows = PlatformUI.getWorkbench()
.getWorkbenchWindows();
if (windows != null && windows.length > 0) {
return windows[0].getShell();
}
} else {
return window.getShell();
}
return null;
}
/**
* Get the progress monitor dialog for this operation. We try to use one
* for all phases of a single operation, such as connecting and
* resolving.
*/
protected IRunnableContext getRunnableContext(Shell rshell) {
Shell shell = getShell();
// for other cases, use statusbar
IWorkbenchWindow win = SystemBasePlugin.getActiveWorkbenchWindow();
if (win != null) {
Shell winShell = RSEUIPlugin.getDefault().getWorkbench()
.getActiveWorkbenchWindow().getShell();
if (winShell != null && !winShell.isDisposed()
&& winShell.isVisible()) {
SystemBasePlugin
.logInfo("Using active workbench window as runnable context");
shell = winShell;
return win;
} else {
win = null;
}
}
if (shell == null || shell.isDisposed() || !shell.isVisible()) {
SystemBasePlugin
.logInfo("Using progress monitor dialog with given shell as parent");
shell = rshell;
}
IRunnableContext dlg = new ProgressMonitorDialog(rshell);
return dlg;
}
/**
* Show an error message when the disconnection fails. Shows a common
* message by default. Overridable.
*/
protected void showDisconnectErrorMessage(Shell shell, String hostName, int port, Exception exc)
{
//SystemMessage.displayMessage(SystemMessage.MSGTYPE_ERROR,shell,RSEUIPlugin.getResourceBundle(),
// ISystemMessages.MSG_DISCONNECT_FAILED,
// hostName, exc.getMessage());
//RSEUIPlugin.logError("Disconnect failed",exc); // temporary
SystemMessageDialog msgDlg = new SystemMessageDialog(shell,
RSEUIPlugin.getPluginMessage(ISystemMessages.MSG_DISCONNECT_FAILED).makeSubstitution(hostName,exc));
msgDlg.setException(exc);
msgDlg.open();
}
/**
* Show an error message when the user cancels the disconnection.
* Shows a common message by default.
* Overridable.
*/
protected void showDisconnectCancelledMessage(Shell shell, String hostName, int port)
{
//SystemMessage.displayMessage(SystemMessage.MSGTYPE_ERROR, shell, RSEUIPlugin.getResourceBundle(),
// ISystemMessages.MSG_DISCONNECT_CANCELLED, hostName);
SystemMessageDialog msgDlg = new SystemMessageDialog(shell,
RSEUIPlugin.getPluginMessage(ISystemMessages.MSG_DISCONNECT_CANCELLED).makeSubstitution(hostName));
msgDlg.open();
}
};
/**
* Notification from sub-services that our session was lost.
* Notify all subsystems properly.
* TODO allow user to try and reconnect?
*/
public void handleSessionLost() {
Activator.trace("SshConnectorService: handleSessionLost"); //$NON-NLS-1$
if (fSessionLostHandler!=null) {
fSessionLostHandler.sessionLost();
}
}
private static Display getStandardDisplay() {
Display display = Display.getCurrent(); Display display = Display.getCurrent();
if( display==null ) { if( display==null ) {
display = Display.getDefault(); display = Display.getDefault();
@ -404,7 +647,14 @@ public class SshConnectorService extends AbstractConnectorService implements ISs
} }
public boolean isConnected() { public boolean isConnected() {
return (session!=null && session.isConnected()); if (session!=null) {
if (session.isConnected()) {
return true;
} else if (fSessionLostHandler!=null) {
fSessionLostHandler.sessionLost();
}
}
return false;
} }
public boolean hasRemoteServerLauncherProperties() { public boolean hasRemoteServerLauncherProperties() {

View file

@ -25,23 +25,26 @@ __Usage:__
Window > Preferences > Team > CVS > SSh2 Connection Method > General Window > Preferences > Team > CVS > SSh2 Connection Method > General
to set the ssh home directory, and private key types to be used. to set the ssh home directory, and private key types to be used.
* Select the "Shells" node and choose Contextmenu > Launch Shell. * Select the "Shells" node and choose Contextmenu > Launch Shell.
* Enter your username on the remote system. For the password, just * Enter your username and password on the remote system. If you want
enter anything (this is not checked, since ssh has its own method to use private-key authentication, just enter any dummy password -
of acquiring password information). you will be asked for the passphrase of your keyring later, or
the connection will succeed without further prompting if your key
ring has an empty passphrase.
* When asked to accept remote host authenticity, press OK. * When asked to accept remote host authenticity, press OK.
* Enter the correct password for ssh on the remote system (this is only * Enter the correct password for ssh on the remote system (this is only
necessary if you are not using a private key). necessary if you are not using a private key).
__Known Limitations:__ __Known Limitations:__
* Symbolic Links are not resolved (readlink not supported by jsch-0.1.28) * Symbolic Links are not resolved (readlink not supported by jsch-0.1.28)
* Ssh passwords can not be stored (no key ring; use private keys instead)
* Ssh timeouts are not observed (no automatic reconnect) * Ssh timeouts are not observed (no automatic reconnect)
- after auto-logout, shell just doesnt react to input any more - after auto-logout, further actions will show a "connect cancel"
message and connection will go down
* Password and passphrase internal handling has not been checked for * Password and passphrase internal handling has not been checked for
security against malicious reading from other Eclipse plugins. security against malicious reading from other Eclipse plugins.
__Known Issues:__ __Known Issues:__
* A dummy password must be entered on initial connect (can be saved) * A dummy password must be entered on initial connect, empty
password should be allowed if private key authentication is used
* After some time, the connection may freeze and need to be * After some time, the connection may freeze and need to be
disconnected. disconnected.
* Command service should be provided in addition to the remote shell service. * Command service should be provided in addition to the remote shell service.
@ -49,14 +52,15 @@ __Known Issues:__
due to memory exhaustion (ArrayIndexOutOfBoundsException) due to memory exhaustion (ArrayIndexOutOfBoundsException)
* "Break" can not be sent to the remote system in order to cancel * "Break" can not be sent to the remote system in order to cancel
long-running jobs on the remote side. long-running jobs on the remote side.
* Moving files with a space in the name doesn't currently work.
Renaming them works though.
* Copy&paste, or drag&drop of remote files remotely does'nt currently work.
* The plugin currently uses some "internal" classes from the * The plugin currently uses some "internal" classes from the
org.eclipse.team.cvs.ui plugin. This needs to be cleaned up. org.eclipse.team.cvs.ui plugin. This needs to be cleaned up.
* For other internal coding issues, see TODO items in the code. * For other internal coding issues, see TODO items in the code.
__Changelog:__ __Changelog:__
v0.3:
* support Keyboard Interactive Authentication.
* Fix interaction with RSE passwords.
* Fix connection lost notifications
v0.2: v0.2:
* Re-use Team/CVS/ssh2 preferences for ssh2 home and private keys specification * Re-use Team/CVS/ssh2 preferences for ssh2 home and private keys specification
Allows to do the ssh login without password if private/public key are set up. Allows to do the ssh login without password if private/public key are set up.

View file

@ -4,5 +4,10 @@ import com.jcraft.jsch.Session;
public interface ISshSessionProvider public interface ISshSessionProvider
{ {
/* Return an active SSH session from a ConnectorService. */
public Session getSession(); public Session getSession();
/* Inform the connectorService that a session has been lost. */
public void handleSessionLost();
} }

View file

@ -62,6 +62,7 @@ public class SftpFileService extends AbstractFileService implements IFileService
return "Access a remote file system via Ssh / Sftp protocol"; return "Access a remote file system via Ssh / Sftp protocol";
} }
//TODO specify Exception more clearly
public void connect() throws Exception { public void connect() throws Exception {
Activator.trace("SftpFileService.connecting..."); //$NON-NLS-1$ Activator.trace("SftpFileService.connecting..."); //$NON-NLS-1$
try { try {
@ -77,16 +78,35 @@ public class SftpFileService extends AbstractFileService implements IFileService
} }
} }
protected ChannelSftp getChannel(String task) { //TODO specify Exception more clearly
protected ChannelSftp getChannel(String task) throws Exception
{
Activator.trace(task); Activator.trace(task);
if (fChannelSftp==null || !fChannelSftp.isConnected()) { if (fChannelSftp==null || !fChannelSftp.isConnected()) {
Activator.trace(task + ": channel not connected: "+fChannelSftp); //$NON-NLS-1$ Activator.trace(task + ": channel not connected: "+fChannelSftp); //$NON-NLS-1$
Session session = fSessionProvider.getSession();
if (session!=null) {
if (!session.isConnected()) {
//notify of lost session. May reconnect asynchronously later.
fSessionProvider.handleSessionLost();
//dont throw an exception here, expect jsch to throw something useful
} else {
//session connected but channel not: try to reconnect
//(may throw Exception)
connect();
}
}
//TODO might throw NPE if session has been disconnected
} }
return fChannelSftp; return fChannelSftp;
} }
public void disconnect() { public void disconnect() {
getChannel("SftpFileService.disconnect").disconnect(); //$NON-NLS-1$ try {
getChannel("SftpFileService.disconnect").disconnect(); //$NON-NLS-1$
} catch(Exception e) {
/*nothing to do*/
}
fChannelSftp = null; fChannelSftp = null;
} }
@ -113,7 +133,12 @@ public class SftpFileService extends AbstractFileService implements IFileService
} }
public boolean isConnected() { public boolean isConnected() {
return getChannel("SftpFileService.isConnected()").isConnected(); //$NON-NLS-1$ try {
return getChannel("SftpFileService.isConnected()").isConnected(); //$NON-NLS-1$
} catch(Exception e) {
/*cannot be connected when we cannot get a channel*/
}
return false;
} }
protected IHostFile[] internalFetch(IProgressMonitor monitor, String parentPath, String fileFilter, int fileType) protected IHostFile[] internalFetch(IProgressMonitor monitor, String parentPath, String fileFilter, int fileType)

View file

@ -25,6 +25,7 @@ import org.eclipse.rse.services.shells.IHostShellOutputReader;
import org.eclipse.rse.services.ssh.ISshSessionProvider; import org.eclipse.rse.services.ssh.ISshSessionProvider;
import com.jcraft.jsch.Channel; import com.jcraft.jsch.Channel;
import com.jcraft.jsch.Session;
/** /**
* A Shell subsystem for SSH. * A Shell subsystem for SSH.
@ -62,12 +63,22 @@ public class SshHostShell extends AbstractHostShell {
} }
public boolean isActive() { public boolean isActive() {
return !fChannel.isEOF(); if (fChannel!=null && !fChannel.isEOF()) {
return true;
}
// shell is not active: check for session lost
Session session = fSessionProvider.getSession();
if (session!=null && !session.isConnected()) {
fSessionProvider.handleSessionLost();
}
return false;
} }
public void writeToShell(String command) { public void writeToShell(String command) {
fStdinHandler.println(command); if (isActive()) {
fStdinHandler.flush(); fStdinHandler.println(command);
fStdinHandler.flush();
}
} }
public IHostShellOutputReader getStandardOutputReader() { public IHostShellOutputReader getStandardOutputReader() {

View file

@ -40,6 +40,12 @@ public class SshShellOutputReader extends AbstractHostShellOutputReader
fReader = reader; fReader = reader;
} }
public void dispose() {
super.dispose();
//check for active session and notify lost session if necessary
getHostShell().isActive();
}
protected Object internalReadLine() { protected Object internalReadLine() {
if (fReader == null) { if (fReader == null) {
//Our workaround sets the stderr reader to null, so we never give any stderr output. //Our workaround sets the stderr reader to null, so we never give any stderr output.