mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
Merge remote-tracking branch
'origin/GP-3050-2935-ghidra1_ServerAddressAndConnectTimeout--SQUASHED' (Closes #4924, Closes #4928)
This commit is contained in:
@@ -18,7 +18,6 @@ package ghidra.framework.client;
|
||||
import java.awt.Component;
|
||||
import java.io.IOException;
|
||||
import java.net.Authenticator;
|
||||
import java.net.UnknownHostException;
|
||||
import java.rmi.*;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
@@ -50,7 +49,7 @@ public class ClientUtil {
|
||||
|
||||
/**
|
||||
* Set client authenticator
|
||||
* @param authenticator
|
||||
* @param authenticator client authenticator instance
|
||||
*/
|
||||
public static synchronized void setClientAuthenticator(ClientAuthenticator authenticator) {
|
||||
clientAuthenticator = authenticator;
|
||||
@@ -106,14 +105,6 @@ public class ClientUtil {
|
||||
// ensure that default callback is setup if possible
|
||||
getClientAuthenticator();
|
||||
|
||||
host = host.trim().toLowerCase();
|
||||
try {
|
||||
host = InetNameLookup.getCanonicalHostName(host);
|
||||
}
|
||||
catch (UnknownHostException e) {
|
||||
Msg.warn(ClientUtil.class, "Failed to resolve hostname for " + host);
|
||||
}
|
||||
|
||||
if (port <= 0) {
|
||||
port = GhidraServerHandle.DEFAULT_PORT;
|
||||
}
|
||||
@@ -145,22 +136,14 @@ public class ClientUtil {
|
||||
* Eliminate the specified repository server from the connection cache
|
||||
* @param host host name or IP address
|
||||
* @param port port (0: use default port)
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void clearRepositoryAdapter(String host, int port) throws IOException {
|
||||
host = host.trim().toLowerCase();
|
||||
String hostAddr = host;
|
||||
try {
|
||||
hostAddr = InetNameLookup.getCanonicalHostName(host);
|
||||
}
|
||||
catch (UnknownHostException e) {
|
||||
throw new IOException("Repository server lookup failed: " + host);
|
||||
}
|
||||
public static void clearRepositoryAdapter(String host, int port) {
|
||||
|
||||
if (port == 0) {
|
||||
port = GhidraServerHandle.DEFAULT_PORT;
|
||||
}
|
||||
ServerInfo server = new ServerInfo(hostAddr, port);
|
||||
|
||||
ServerInfo server = new ServerInfo(host, port);
|
||||
RepositoryServerAdapter serverAdapter = serverHandles.remove(server);
|
||||
if (serverAdapter != null) {
|
||||
serverAdapter.disconnect();
|
||||
@@ -170,6 +153,7 @@ public class ClientUtil {
|
||||
/**
|
||||
* Returns default user login name. Actual user name used by repository
|
||||
* should be obtained from RepositoryServerAdapter.getUser
|
||||
* @return default user name
|
||||
*/
|
||||
public static String getUserName() {
|
||||
String name = SystemUtilities.getUserName();
|
||||
@@ -372,7 +356,7 @@ public class ClientUtil {
|
||||
* @param parent dialog parent
|
||||
* @param handle server handle
|
||||
* @param serverInfo server information
|
||||
* @throws IOException
|
||||
* @throws IOException if error occurs while updating password
|
||||
*/
|
||||
public static void changePassword(Component parent, RepositoryServerHandle handle,
|
||||
String serverInfo) throws IOException {
|
||||
@@ -451,7 +435,7 @@ public class ClientUtil {
|
||||
sigCb.getRecognizedAuthorities(), sigCb.getToken());
|
||||
sigCb.sign(signedToken.certChain, signedToken.signature);
|
||||
Msg.info(ClientUtil.class, "PKI Authenticating to " + serverName + " as user '" +
|
||||
signedToken.certChain[0].getSubjectDN() + "'");
|
||||
signedToken.certChain[0].getSubjectX500Principal() + "'");
|
||||
}
|
||||
catch (Exception e) {
|
||||
String msg = e.getMessage();
|
||||
|
||||
@@ -18,10 +18,12 @@ package ghidra.framework.client;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.rmi.*;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.registry.Registry;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.HashSet;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
@@ -46,6 +48,8 @@ import ghidra.util.task.*;
|
||||
*/
|
||||
class ServerConnectTask extends Task {
|
||||
|
||||
private static final int LIVENESS_CHECK_TIMEOUT_MS = 3000;
|
||||
|
||||
private ServerInfo server;
|
||||
//private String defaultUserID;
|
||||
private boolean allowLoginRetry;
|
||||
@@ -98,6 +102,7 @@ class ServerConnectTask extends Task {
|
||||
* if handle is null after running task. If both the exception
|
||||
* and handle are null, it implies the connection attempt was cancelled
|
||||
* by the user.
|
||||
* @return exception which occured during a failed connection attempt, or null
|
||||
*/
|
||||
Exception getException() {
|
||||
return exc;
|
||||
@@ -123,16 +128,6 @@ class ServerConnectTask extends Task {
|
||||
return subj;
|
||||
}
|
||||
|
||||
private static String getPreferredHostname(String name) {
|
||||
try {
|
||||
return InetNameLookup.getCanonicalHostName(name);
|
||||
}
|
||||
catch (UnknownHostException e) {
|
||||
Msg.warn(ServerConnectTask.class, "Failed to resolve hostname for " + name);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private static boolean isSSLHandshakeCancelled(SSLHandshakeException e) throws IOException {
|
||||
if (e.getMessage().indexOf("bad_certificate") > 0) {
|
||||
if (ApplicationKeyManagerFactory.getPreferredKeyStore() == null) {
|
||||
@@ -154,8 +149,8 @@ class ServerConnectTask extends Task {
|
||||
* @param server server information
|
||||
* @param monitor cancellable monitor
|
||||
* @return Ghidra Server Handle object
|
||||
* @throws IOException
|
||||
* @throws CancelledException
|
||||
* @throws IOException if a connection error occurs
|
||||
* @throws CancelledException if connection attempt was cancelled
|
||||
*/
|
||||
public static GhidraServerHandle getGhidraServerHandle(ServerInfo server, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
@@ -163,6 +158,7 @@ class ServerConnectTask extends Task {
|
||||
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
|
||||
@@ -172,17 +168,10 @@ class ServerConnectTask extends Task {
|
||||
monitor.setCancelEnabled(false);
|
||||
monitor.setMessage("Connecting...");
|
||||
|
||||
Registry reg;
|
||||
try {
|
||||
// attempt to connect with older Ghidra Server registry without using SSL/TLS
|
||||
reg = LocateRegistry.getRegistry(server.getServerName(), server.getPortNumber());
|
||||
checkServerBindNames(reg);
|
||||
}
|
||||
catch (IOException e) {
|
||||
reg = LocateRegistry.getRegistry(server.getServerName(), server.getPortNumber(),
|
||||
Registry reg =
|
||||
LocateRegistry.getRegistry(server.getServerName(), server.getPortNumber(),
|
||||
new SslRMIClientSocketFactory());
|
||||
checkServerBindNames(reg);
|
||||
}
|
||||
checkServerBindNames(reg);
|
||||
|
||||
gsh = (GhidraServerHandle) reg.lookup(GhidraServerHandle.BIND_NAME);
|
||||
gsh.checkCompatibility(GhidraServerHandle.INTERFACE_VERSION);
|
||||
@@ -241,20 +230,17 @@ class ServerConnectTask extends Task {
|
||||
|
||||
/**
|
||||
* Attempts server connection and completes any necessary authentication.
|
||||
* @param defaultUserID
|
||||
* @param defaultUserID default user ID (actual ID used established during authentication)
|
||||
* @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
|
||||
* @throws IOException if server connection fails
|
||||
* @throws LoginException login failure
|
||||
*/
|
||||
private RemoteRepositoryServerHandle getRepositoryServerHandle(String defaultUserID,
|
||||
TaskMonitor monitor)
|
||||
throws IOException, LoginException, CancelledException {
|
||||
|
||||
GhidraServerHandle gsh = getGhidraServerHandle(server, monitor);
|
||||
if (gsh == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Callback[] callbacks = null;
|
||||
try {
|
||||
@@ -275,7 +261,6 @@ class ServerConnectTask extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
String serverName = getPreferredHostname(server.getServerName());
|
||||
AnonymousCallback onlyAnonymousCb = null;
|
||||
while (true) {
|
||||
try {
|
||||
@@ -298,8 +283,8 @@ class ServerConnectTask extends Task {
|
||||
// SSH option only available in conjunction with password
|
||||
// based authentication which will be used if SSH attempt fails
|
||||
hasSSHSignatureCallback = false; // only try SSH once
|
||||
ClientUtil.processSSHSignatureCallback(callbacks, serverName,
|
||||
defaultUserID);
|
||||
ClientUtil.processSSHSignatureCallback(callbacks,
|
||||
server.getServerName(), defaultUserID);
|
||||
}
|
||||
else if (pkiSignatureCb != null) {
|
||||
// when using PKI - no other authentication callback will be used
|
||||
@@ -317,14 +302,15 @@ class ServerConnectTask extends Task {
|
||||
}
|
||||
|
||||
loopOK = false; // only try once
|
||||
ClientUtil.processSignatureCallback(serverName, pkiSignatureCb);
|
||||
ClientUtil.processSignatureCallback(server.getServerName(),
|
||||
pkiSignatureCb);
|
||||
}
|
||||
else {
|
||||
// assume all other callback scenarios are password based
|
||||
// anonymous option must be explicitly chosen over username/password
|
||||
// when processing password callback
|
||||
if (!ClientUtil.processPasswordCallbacks(callbacks, serverName,
|
||||
defaultUserID, loginError)) {
|
||||
if (!ClientUtil.processPasswordCallbacks(callbacks,
|
||||
server.getServerName(), defaultUserID, loginError)) {
|
||||
return null; // Cancelled by user
|
||||
}
|
||||
}
|
||||
@@ -336,7 +322,7 @@ class ServerConnectTask extends Task {
|
||||
gsh.getRepositoryServer(getLocalUserSubject(), callbacks);
|
||||
if (rsh.isReadOnly()) {
|
||||
Msg.showInfo(this, null, "Anonymous Server Login",
|
||||
"You have been logged-in anonymously to " + serverName +
|
||||
"You have been logged-in anonymously to " + server.getServerName() +
|
||||
"\nRead-only permission is granted to repositories which allow anonymous access");
|
||||
}
|
||||
return rsh;
|
||||
@@ -374,7 +360,30 @@ class ServerConnectTask extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
private static void testServerSSLConnection(ServerInfo server, TaskMonitor monitor)
|
||||
/**
|
||||
* Socket implementation with very short connect timeout
|
||||
*/
|
||||
private static class FastConnectionFailSocket extends Socket {
|
||||
FastConnectionFailSocket(String host, int port) throws UnknownHostException, IOException {
|
||||
super(host, port);
|
||||
}
|
||||
|
||||
public void connect(SocketAddress endpoint) throws IOException {
|
||||
connect(endpoint, LIVENESS_CHECK_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate an SSLSocket connection in order to ensure that any neccesary client/server
|
||||
* certificate validation is performed.
|
||||
* @param server server to which connection should be verified. For the Ghidra Server
|
||||
* this should correspond to the RMI Registry port {@link GhidraServerHandle#DEFAULT_PORT}.
|
||||
* @param monitor connection task monitor
|
||||
* @return certificate chain of server
|
||||
* @throws IOException if connection failure occurs
|
||||
* @throws CancelledException if connection attempt is cancelled
|
||||
*/
|
||||
private static Certificate[] testServerSSLConnection(ServerInfo server, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
RMIServerPortFactory portFactory = new RMIServerPortFactory(server.getPortNumber());
|
||||
@@ -384,7 +393,18 @@ class ServerConnectTask extends Task {
|
||||
|
||||
monitor.setCancelEnabled(true);
|
||||
monitor.setMessage("Checking Server Liveness...");
|
||||
|
||||
// Perform simple socket test connection with short timeout to verify connectivity.
|
||||
try (Socket socket = new FastConnectionFailSocket(serverName, sslRmiPort);
|
||||
ConnectCancelledListener cancelListener =
|
||||
new ConnectCancelledListener(monitor, () -> forceClose(socket))) {
|
||||
// do nothing - connect occurs during instantiation
|
||||
}
|
||||
finally {
|
||||
monitor.checkCanceled(); // circumvent any IOException which may have occured
|
||||
}
|
||||
|
||||
// Perform secure socket test connection to prime keystore use without RMI involvement
|
||||
try (SSLSocket socket = (SSLSocket) factory.createSocket(serverName, sslRmiPort);
|
||||
ConnectCancelledListener cancelListener =
|
||||
new ConnectCancelledListener(monitor, () -> forceClose(socket))) {
|
||||
@@ -392,6 +412,7 @@ class ServerConnectTask extends Task {
|
||||
// which will give user ability to cancel without involving RMI which
|
||||
// will avoid RMI reconnect attempts
|
||||
socket.startHandshake();
|
||||
return socket.getSession().getPeerCertificates();
|
||||
}
|
||||
finally {
|
||||
monitor.checkCanceled(); // circumvent any IOException which may have occured
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,7 +37,8 @@ public class ServerInfo implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server name.
|
||||
* Get the server hostname or IP address as originally specified.
|
||||
* @return hostname or IP address as originally specified
|
||||
*/
|
||||
public String getServerName() {
|
||||
return host;
|
||||
@@ -46,6 +46,7 @@ public class ServerInfo implements Serializable {
|
||||
|
||||
/**
|
||||
* Get the port number.
|
||||
* @return port number
|
||||
*/
|
||||
public int getPortNumber() {
|
||||
return portNumber;
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.remote;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class InetNameLookup {
|
||||
|
||||
private static final long MAX_TIME_MS = 10000;
|
||||
|
||||
private static volatile boolean lookupEnabled = true;
|
||||
private static volatile boolean disableOnFailure = false;
|
||||
|
||||
private InetNameLookup() {
|
||||
// static use only
|
||||
}
|
||||
|
||||
public static void setDisableOnFailure(boolean state) {
|
||||
disableOnFailure = state;
|
||||
}
|
||||
|
||||
public static void setLookupEnabled(boolean enable) {
|
||||
lookupEnabled = enable;
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return lookupEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fully qualified domain name for this IP address or hostname.
|
||||
* Best effort method, meaning we may not be able to return
|
||||
* the FQDN depending on the underlying system configuration.
|
||||
*
|
||||
* @param host IP address or hostname
|
||||
*
|
||||
* @return the fully qualified domain name for this IP address,
|
||||
* or if the operation is not allowed/fails
|
||||
* the original host name specified.
|
||||
*
|
||||
* @throws UnknownHostException the forward lookup of the specified address
|
||||
* failed
|
||||
*/
|
||||
public static String getCanonicalHostName(String host) throws UnknownHostException {
|
||||
String bestGuess = host;
|
||||
if (lookupEnabled) {
|
||||
// host may have multiple IP addresses
|
||||
boolean found = false;
|
||||
long fastest = Long.MAX_VALUE;
|
||||
for (InetAddress addr : InetAddress.getAllByName(host)) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
String name = addr.getCanonicalHostName();
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
if (!name.equals(addr.getHostAddress())) {
|
||||
if (host.equalsIgnoreCase(name)) {
|
||||
return name; // name found matches original - use it
|
||||
}
|
||||
bestGuess = name; // name found - update best guess
|
||||
found = true;
|
||||
}
|
||||
else {
|
||||
// keep fastest reverse lookup time
|
||||
fastest = Math.min(fastest, elapsedTime);
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// if lookup failed to produce a name - log warning
|
||||
Msg.warn(InetNameLookup.class, "Failed to resolve IP Address: " + host +
|
||||
" (Reverse DNS may not be properly configured or you may have a network problem)");
|
||||
if (disableOnFailure && fastest > MAX_TIME_MS) {
|
||||
// if lookup failed and was slow - disable future lookups if disableOnFailure is true
|
||||
Msg.warn(InetNameLookup.class,
|
||||
"Reverse network name lookup has been disabled automatically due to lookup failure.");
|
||||
lookupEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestGuess;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user