mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-27 06:30:51 +00:00
GP-1481 add support for cancelling hung Ghidra Server connect attempt
This commit is contained in:
parent
948e4edeb1
commit
0ef2367ed4
@ -32,9 +32,9 @@ import ghidra.framework.remote.*;
|
||||
import ghidra.framework.remote.security.SSHKeyManager;
|
||||
import ghidra.net.*;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.exception.UserAccessException;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* <code>ClientUtil</code> allows a user to connect to a Repository Server and obtain its handle.
|
||||
@ -294,16 +294,19 @@ public class ClientUtil {
|
||||
|
||||
/**
|
||||
* Connect to a Ghidra Server and verify compatibility. This method can be used
|
||||
* to affectively "ping" the Ghidra Server to verify the ability to connect.
|
||||
* to effectively "ping" the Ghidra Server to verify the ability to connect.
|
||||
* NOTE: Use of this method when PKI authentication is enabled is not supported.
|
||||
* @param host server hostname
|
||||
* @param port first Ghidra Server port (0=use default)
|
||||
* @param monitor cancellable monitor
|
||||
* @throws IOException thrown if an IO Error occurs (e.g., server not found).
|
||||
* @throws RemoteException if server interface is incompatible or another server-side
|
||||
* error occurs.
|
||||
* @throws CancelledException if connection attempt was cancelled
|
||||
*/
|
||||
public static void checkGhidraServer(String host, int port) throws IOException {
|
||||
ServerConnectTask.getGhidraServerHandle(new ServerInfo(host, port));
|
||||
public static void checkGhidraServer(String host, int port, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
ServerConnectTask.getGhidraServerHandle(new ServerInfo(host, port), monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -319,24 +322,28 @@ public class ClientUtil {
|
||||
* @throws GeneralSecurityException if server authentication fails due to
|
||||
* credential access error (e.g., PKI cert failure)
|
||||
* @throws IOException thrown if an IO Error occurs.
|
||||
* @throws CancelledException if connection cancelled by user (does not apply to Headless use)
|
||||
*/
|
||||
static RemoteRepositoryServerHandle connect(ServerInfo server)
|
||||
throws LoginException, GeneralSecurityException, IOException {
|
||||
throws LoginException, GeneralSecurityException, IOException, CancelledException {
|
||||
|
||||
getClientAuthenticator();
|
||||
boolean allowLoginRetry = (clientAuthenticator instanceof DefaultClientAuthenticator);
|
||||
|
||||
RemoteRepositoryServerHandle hdl = null;
|
||||
ServerConnectTask connectTask = new ServerConnectTask(server, allowLoginRetry);
|
||||
if (!SystemUtilities.isInHeadlessMode() && SystemUtilities.isEventDispatchThread()) {
|
||||
// Must be done in modal dialog to allow possible authentication prompts
|
||||
// from another thread.
|
||||
|
||||
TaskLauncher.launch(connectTask);
|
||||
if (SystemUtilities.isInHeadlessMode()) {
|
||||
connectTask.run(TaskMonitor.DUMMY); // headless - can't cancel
|
||||
}
|
||||
else {
|
||||
connectTask.run(null);
|
||||
// Must be done in modal dialog to allow cancellation and possible authentication prompts
|
||||
// from another thread.
|
||||
TaskLauncher.launch(connectTask);
|
||||
if (connectTask.isCancelled()) {
|
||||
throw new CancelledException();
|
||||
}
|
||||
}
|
||||
|
||||
hdl = connectTask.getRepositoryServerHandle();
|
||||
if (hdl == null) {
|
||||
Exception e = connectTask.getException();
|
||||
|
@ -137,7 +137,13 @@ public class RepositoryServerAdapter {
|
||||
|
||||
Throwable cause = null;
|
||||
try {
|
||||
serverHandle = ClientUtil.connect(server);
|
||||
try {
|
||||
serverHandle = ClientUtil.connect(server);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore
|
||||
Msg.debug(this, "Server connect cancelled by user");
|
||||
}
|
||||
unexpectedDisconnect = false;
|
||||
if (serverHandle != null) {
|
||||
Msg.info(this, "Connected to Ghidra Server at " + serverInfoStr);
|
||||
|
@ -15,7 +15,9 @@
|
||||
*/
|
||||
package ghidra.framework.client;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.rmi.*;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
@ -36,8 +38,8 @@ import ghidra.framework.model.ServerInfo;
|
||||
import ghidra.framework.remote.*;
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.*;
|
||||
|
||||
/**
|
||||
* Task for connecting to server with Swing thread.
|
||||
@ -56,7 +58,7 @@ class ServerConnectTask extends Task {
|
||||
* @param allowLoginRetry true if login retry allowed during authentication
|
||||
*/
|
||||
ServerConnectTask(ServerInfo server, boolean allowLoginRetry) {
|
||||
super("Connecting to " + server.getServerName(), false, false, true);
|
||||
super("Connecting to " + server.getServerName(), true, false, true);
|
||||
this.server = server;
|
||||
this.allowLoginRetry = allowLoginRetry;
|
||||
}
|
||||
@ -64,12 +66,14 @@ class ServerConnectTask extends Task {
|
||||
/**
|
||||
* Completes and necessary authentication and obtains a repository handle.
|
||||
* If a connection error occurs, an exception will be stored ({@link #getException()}.
|
||||
* @throws CancelledException if task cancelled
|
||||
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
|
||||
*/
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) {
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
monitor = TaskMonitor.dummyIfNull(monitor);
|
||||
try {
|
||||
hdl = getRepositoryServerHandle(ClientUtil.getUserName());
|
||||
hdl = getRepositoryServerHandle(ClientUtil.getUserName(), monitor);
|
||||
}
|
||||
catch (RemoteException e) {
|
||||
exc = e;
|
||||
@ -81,6 +85,12 @@ class ServerConnectTask extends Task {
|
||||
catch (Exception e) {
|
||||
exc = e;
|
||||
}
|
||||
finally {
|
||||
if (monitor.isCancelled()) {
|
||||
exc = null;
|
||||
throw new CancelledException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,18 +152,25 @@ class ServerConnectTask extends Task {
|
||||
/**
|
||||
* Obtain a remote instance of the Ghidra Server Handle object
|
||||
* @param server server information
|
||||
* @param monitor cancellable monitor
|
||||
* @return Ghidra Server Handle object
|
||||
* @throws IOException
|
||||
* @throws CancelledException
|
||||
*/
|
||||
public static GhidraServerHandle getGhidraServerHandle(ServerInfo server) throws IOException {
|
||||
public static GhidraServerHandle getGhidraServerHandle(ServerInfo server, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
GhidraServerHandle gsh = null;
|
||||
boolean canCancel = monitor.isCancelEnabled(); // original state
|
||||
try {
|
||||
// Test SSL Handshake to ensure that user is able to decrypt keystore.
|
||||
// This is intended to work around an RMI issue where a continuous
|
||||
// retry condition can occur when a user cancels the password entry
|
||||
// for their keystore which should cancel any connection attempt
|
||||
testServerSSLConnection(server);
|
||||
testServerSSLConnection(server, monitor);
|
||||
|
||||
monitor.setCancelEnabled(false);
|
||||
monitor.setMessage("Connecting...");
|
||||
|
||||
Registry reg;
|
||||
try {
|
||||
@ -191,20 +208,50 @@ class ServerConnectTask extends Task {
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
monitor.setCancelEnabled(canCancel);
|
||||
monitor.setMessage("");
|
||||
}
|
||||
return gsh;
|
||||
}
|
||||
|
||||
private static class ConnectCancelledListener implements CancelledListener, Closeable {
|
||||
|
||||
private TaskMonitor monitor;
|
||||
private CancelledListener callback;
|
||||
|
||||
ConnectCancelledListener(TaskMonitor monitor, CancelledListener callback) {
|
||||
this.monitor = monitor;
|
||||
this.callback = callback;
|
||||
monitor.addCancelledListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelled() {
|
||||
if (callback != null) {
|
||||
callback.cancelled();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
monitor.removeCancelledListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts server connection and completes any necessary authentication.
|
||||
* @param defaultUserID
|
||||
* @return server handle or null if authentication was cancelled by user
|
||||
* @param monitor task monitor for connection cancellation
|
||||
* @return server handle or null if authentication or connection attempt was cancelled by user
|
||||
* @throws IOException
|
||||
* @throws LoginException
|
||||
*/
|
||||
private RemoteRepositoryServerHandle getRepositoryServerHandle(String defaultUserID)
|
||||
throws IOException, LoginException {
|
||||
private RemoteRepositoryServerHandle getRepositoryServerHandle(String defaultUserID,
|
||||
TaskMonitor monitor)
|
||||
throws IOException, LoginException, CancelledException {
|
||||
|
||||
GhidraServerHandle gsh = getGhidraServerHandle(server);
|
||||
GhidraServerHandle gsh = getGhidraServerHandle(server, monitor);
|
||||
if (gsh == null) {
|
||||
return null;
|
||||
}
|
||||
@ -318,19 +365,37 @@ class ServerConnectTask extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
private static void testServerSSLConnection(ServerInfo server) throws IOException {
|
||||
private static void forceClose(Socket s) {
|
||||
try {
|
||||
s.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private static void testServerSSLConnection(ServerInfo server, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
RMIServerPortFactory portFactory = new RMIServerPortFactory(server.getPortNumber());
|
||||
SslRMIClientSocketFactory factory = new SslRMIClientSocketFactory();
|
||||
String serverName = server.getServerName();
|
||||
int sslRmiPort = portFactory.getRMISSLPort();
|
||||
|
||||
try (SSLSocket socket = (SSLSocket) factory.createSocket(serverName, sslRmiPort)) {
|
||||
monitor.setCancelEnabled(true);
|
||||
monitor.setMessage("Checking Server Liveness...");
|
||||
|
||||
try (SSLSocket socket = (SSLSocket) factory.createSocket(serverName, sslRmiPort);
|
||||
ConnectCancelledListener cancelListener =
|
||||
new ConnectCancelledListener(monitor, () -> forceClose(socket))) {
|
||||
// Complete SSL handshake to trigger client keystore access if required
|
||||
// which will give user ability to cancel without involving RMI which
|
||||
// will avoid RMI reconnect attempts
|
||||
socket.startHandshake();
|
||||
}
|
||||
finally {
|
||||
monitor.checkCanceled(); // circumvent any IOException which may have occured
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkServerBindNames(Registry reg) throws RemoteException {
|
||||
|
@ -36,6 +36,7 @@ import ghidra.framework.remote.GhidraServerHandle;
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.server.remote.ServerTestUtil;
|
||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
@Category(PortSensitiveCategory.class)
|
||||
@ -110,7 +111,8 @@ public class GhidraServerSerialFilterFailureTest extends AbstractGhidraHeadlessI
|
||||
|
||||
ServerInfo server = new ServerInfo("localhost", ServerTestUtil.GHIDRA_TEST_SERVER_PORT);
|
||||
|
||||
GhidraServerHandle serverHandle = ServerConnectTask.getGhidraServerHandle(server);
|
||||
GhidraServerHandle serverHandle =
|
||||
ServerConnectTask.getGhidraServerHandle(server, TaskMonitor.DUMMY);
|
||||
|
||||
try {
|
||||
serverHandle.getRepositoryServer(getBogusUserSubject(), new Callback[0]);
|
||||
|
Loading…
Reference in New Issue
Block a user