diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraSSLServerSocket.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraSSLServerSocket.java
index 536fcc09b5..f4641aea62 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraSSLServerSocket.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraSSLServerSocket.java
@@ -16,10 +16,8 @@
package ghidra.server.remote;
import java.io.IOException;
-import java.net.ServerSocket;
-import java.net.Socket;
+import java.net.*;
import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
import javax.net.ssl.*;
@@ -29,17 +27,6 @@ import javax.net.ssl.*;
*/
public class GhidraSSLServerSocket extends ServerSocket {
- private static ThreadLocal pricipalContext = new ThreadLocal();
-
- /**
- * Get the peer principal corresponding to the RMI client connection established
- * with the current thread.
- * @return RMI client peer principal
- */
- public static Principal getThreadClientPrincipal() {
- return pricipalContext.get();
- }
-
private final SSLSocketFactory sslSocketFactory;
private final String[] enabledCipherSuites;
private final String[] enabledProtocols;
@@ -53,9 +40,9 @@ public class GhidraSSLServerSocket extends ServerSocket {
* @param needClientAuth if true client authentication is required
* @throws IOException
*/
- GhidraSSLServerSocket(int port, String[] enabledCipherSuites, String[] enabledProtocols, boolean needClientAuth)
- throws IOException {
- super(port);
+ GhidraSSLServerSocket(int port, InetAddress bindAddress, String[] enabledCipherSuites,
+ String[] enabledProtocols, boolean needClientAuth) throws IOException {
+ super(port, 0, bindAddress);
this.enabledCipherSuites = enabledCipherSuites;
this.enabledProtocols = enabledProtocols;
this.needClientAuth = needClientAuth;
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java
index 7d439f2191..c0296aec2e 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/GhidraServer.java
@@ -63,15 +63,15 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
private static SslRMIServerSocketFactory serverSocketFactory;
private static SslRMIClientSocketFactory clientSocketFactory;
+ private static InetAddress bindAddress;
private static Logger log;
private static String HELP_FILE = "/ghidra/server/remote/ServerHelp.txt";
private static String USAGE_ARGS =
- " [-p] [-a] [-d] [-u] [-anonymous] [-ssh] [-ip] [-e] [-n] ";
+ " [-p] [-a] [-d] [-u] [-anonymous] [-ssh] [-ip ] [-i ] [-e] [-n] ";
private static final String RMI_SERVER_PROPERTY = "java.rmi.server.hostname";
- // private static final String RMI_CODEBASE_PROPERTY = "java.rmi.server.codebase";
private static final String[] AUTH_MODES =
{ "None", "Password File", "OS Password", "PKI", "OS Password & Password File" };
@@ -175,13 +175,20 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
GhidraServer.server = this;
+ // Start block stream server - use RMI serverSocketFactory
blockStreamServer = BlockStreamServer.getBlockStreamServer();
- blockStreamServer.startServer();
+ ServerSocket streamServerSocket;
+ if (serverSocketFactory != null) {
+ streamServerSocket =
+ serverSocketFactory.createServerSocket(ServerPortFactory.getStreamPort());
+ }
+ else {
+ streamServerSocket = new GhidraSSLServerSocket(ServerPortFactory.getStreamPort(),
+ bindAddress, null, null, authMode == PKI_LOGIN);
+ }
+ blockStreamServer.startServer(streamServerSocket, initRemoteAccessHostname());
}
- /*
- * @see ghidra.framework.remote.GhidraServerHandle#getAuthenticationCallbacks(javax.security.auth.Subject)
- */
@Override
public Callback[] getAuthenticationCallbacks() throws RemoteException {
log.info("Authentication callbacks requested by " + RepositoryManager.getRMIClient());
@@ -203,9 +210,6 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
}
}
- /*
- * @see ghidra.framework.remote.GhidraServerHandle#checkCompatibility(int)
- */
@Override
public void checkCompatibility(int serverInterfaceVersion) throws RemoteException {
if (serverInterfaceVersion > INTERFACE_VERSION) {
@@ -219,9 +223,6 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
}
}
- /*
- * @see ghidra.framework.remote.GhidraServerHandle#getRepositoryServer(javax.security.auth.callback.Callback[])
- */
@Override
public RemoteRepositoryServerHandle getRepositoryServer(Subject user, Callback[] authCallbacks)
throws LoginException, RemoteException {
@@ -419,6 +420,28 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
return null;
}
+ private static String initRemoteAccessHostname() throws UnknownHostException {
+ String hostname = System.getProperty(RMI_SERVER_PROPERTY);
+ if (hostname == null) {
+ if (bindAddress != null) {
+ hostname = bindAddress.getHostAddress();
+ }
+ else {
+ InetAddress localhost = InetAddress.getLocalHost();
+ if (localhost.isLoopbackAddress()) {
+ localhost = findHost();
+ if (localhost == null) {
+ log.fatal("Can't find host ip address!");
+ System.exit(-1);
+ }
+ }
+ hostname = localhost.getHostAddress();
+ }
+ System.setProperty(RMI_SERVER_PROPERTY, hostname);
+ }
+ return hostname;
+ }
+
/**
* Main method for starting the Ghidra server.
*
@@ -475,8 +498,43 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
System.exit(-1);
}
}
- else if (s.startsWith("-ip")) { // Setting server ip address to bind to
- System.setProperty(RMI_SERVER_PROPERTY, s.substring(3));
+ else if (s.startsWith("-ip")) { // setting server remote access hostname
+ int nextArgIndex = i + 1;
+ String hostname;
+ if (s.length() == 3 && nextArgIndex < args.length) {
+ hostname = args[++i];
+ }
+ else {
+ hostname = s.substring(3);
+ }
+ hostname = hostname.trim();
+ if (hostname.length() == 0 || hostname.startsWith("-")) {
+ displayUsage("Missing -ip hostname");
+ System.exit(-1);
+ }
+ System.setProperty(RMI_SERVER_PROPERTY, hostname);
+ }
+ else if (s.startsWith("-i")) { // setting server bind address
+ int nextArgIndex = i + 1;
+ String bindIp;
+ if (s.length() == 2 && nextArgIndex < args.length) {
+ bindIp = args[++i];
+ }
+ else {
+ bindIp = s.substring(2);
+ }
+ bindIp = bindIp.trim();
+ if (bindIp.length() == 0 || bindIp.startsWith("-")) {
+ displayUsage("Missing -i interface bind address");
+ System.exit(-1);
+ }
+ try {
+ bindAddress = InetAddress.getByName(bindIp);
+ }
+ catch (UnknownHostException e) {
+ System.err.println("Unknown server interface bind address: " + bindIp);
+ System.exit(-1);
+ }
}
else if (s.startsWith("-d") && s.length() > 2) { // Login Domain
loginDomain = s.substring(2);
@@ -592,24 +650,8 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
// }
try {
- // Determine IP interface to bind to
- String hostname = System.getProperty(RMI_SERVER_PROPERTY);
- if (hostname == null) {
- InetAddress localhost = InetAddress.getLocalHost();
- if (localhost.isLoopbackAddress()) {
- localhost = findHost();
- if (localhost == null) {
- log.fatal("Can't find host ip address!");
- System.exit(0);
- return;
- }
- }
- System.setProperty(RMI_SERVER_PROPERTY, localhost.getHostAddress());
- hostname = localhost.getCanonicalHostName();
- }
- else {
- log.warn("forcing server to bind to " + hostname);
- }
+ // Ensure that remote access hostname is properly set for RMI registration
+ String hostname = initRemoteAccessHostname();
if (ApplicationKeyManagerFactory.getPreferredKeyStore() == null) {
// keystore has not been identified - use self-signed certificate
@@ -629,8 +671,13 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
// System.setProperty(RMI_CODEBASE_PROPERTY, codeBaseProp);
log.info("Ghidra Server " + Application.getApplicationVersion());
-
- log.info(" Server bound to " + System.getProperty(RMI_SERVER_PROPERTY));
+ log.info(" Server remote access address: " + hostname);
+ if (bindAddress == null) {
+ log.info(" Server listening on all interfaces");
+ }
+ else {
+ log.info(" Server listening on interface: " + bindAddress.getHostAddress());
+ }
log.info(" RMI Registry port: " + ServerPortFactory.getRMIRegistryPort());
log.info(" RMI SSL port: " + ServerPortFactory.getRMISSLPort());
log.info(" Block Stream port: " + ServerPortFactory.getStreamPort());
@@ -659,7 +706,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
serverSocketFactory = new SslRMIServerSocketFactory(null, null, authMode == PKI_LOGIN) {
@Override
public ServerSocket createServerSocket(int port) throws IOException {
- return new GhidraSSLServerSocket(port, getEnabledCipherSuites(),
+ return new GhidraSSLServerSocket(port, bindAddress, getEnabledCipherSuites(),
getEnabledProtocols(), getNeedClientAuth());
}
};
@@ -671,8 +718,8 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
log.info("Registering Ghidra Server...");
- Registry registry =
- LocateRegistry.createRegistry(ServerPortFactory.getRMIRegistryPort());
+ Registry registry = LocateRegistry.createRegistry(
+ ServerPortFactory.getRMIRegistryPort(), clientSocketFactory, serverSocketFactory);
registry.bind(BIND_NAME, svr);
log.info("Registered Ghidra Server.");
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/ServerHelp.txt b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/ServerHelp.txt
index 562d9fb673..e1a8a603a5 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/ServerHelp.txt
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/remote/ServerHelp.txt
@@ -1,8 +1,11 @@
Ghidra server startup parameters.
-Command line parameters: [-ip###.###.###.###] [-p#] [-a#] [-d] [-e] [-u] [-n]
+Command line parameters: [-ip ] [-i #.#.#.#] [-p#] [-a#] [-d] [-e] [-u] [-n]
- -ip###.###.###.### : ip address to bound to (by default, uses IP address bound to hostname)
-
+ -ip : identifies the remote access IPv4 address or hostname (FQDN) which should be
+ used by remote clients to access the server.
+
+ -i #.#.#.# : server interface IPv4 address to listen on (default will listen on all interfaces).
+
-p# : base TCP port to be used (default: 13100) [see Note 1]
-a# : an optional authentication mode where # is a value 0 or 2
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/BlockStreamServer.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/BlockStreamServer.java
index 781a1c2656..59da41cd0e 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/BlockStreamServer.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/BlockStreamServer.java
@@ -20,9 +20,6 @@ import java.net.*;
import java.util.HashMap;
import java.util.Map;
-import javax.net.ServerSocketFactory;
-import javax.net.ssl.SSLServerSocketFactory;
-
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -30,6 +27,7 @@ import db.buffers.BlockStream;
import db.buffers.InputBlockStream;
import ghidra.server.remote.ServerPortFactory;
import ghidra.server.stream.RemoteBlockStreamHandle.StreamRequest;
+import ghidra.util.StringUtilities;
import ghidra.util.timer.GTimer;
import ghidra.util.timer.GTimerMonitor;
@@ -64,7 +62,7 @@ public class BlockStreamServer extends Thread {
*/
public static synchronized BlockStreamServer getBlockStreamServer() {
if (server == null) {
- server = new BlockStreamServer(ServerPortFactory.getStreamPort());
+ server = new BlockStreamServer();
}
return server;
}
@@ -73,21 +71,17 @@ public class BlockStreamServer extends Thread {
private long nextStreamID = System.currentTimeMillis();
- private int port;
- private String ipAddress;
+ private String hostname;
private volatile boolean running;
private ServerSocket serverSocket;
private GTimerMonitor cleanupTimerMonitor;
/**
- * Construct a block stream server instance. The optional Ghidra Server IP
- * interface binding will be used (i.e., java.rmi.server.hostname property).
- * @param port TCP port to listen on for this server instance
+ * Construct a block stream server instance.
*/
- private BlockStreamServer(int port) {
- super("BlockStreamServer-" + port);
- this.port = port;
+ private BlockStreamServer() {
+ super("BlockStreamServer");
}
/**
@@ -100,18 +94,18 @@ public class BlockStreamServer extends Thread {
/**
* Get the server port
- * @return server port
+ * @return server port, -1 if server not yet started
*/
public int getServerPort() {
- return port;
+ return serverSocket != null ? serverSocket.getLocalPort() : -1;
}
/**
- * Get the server host IP address
- * @return IP address or null if server has not been started
+ * Get the server remote access hostname
+ * @return hostname or IP address to be used for remote access, null if server not yet started
*/
- public String getServerIpAddress() {
- return ipAddress;
+ public String getServerHostname() {
+ return hostname;
}
/**
@@ -191,43 +185,25 @@ public class BlockStreamServer extends Thread {
/**
* Start this server instance. If the server has already been started
* this method will return immediately.
+ * @param s server socket to be used for accepting connections
+ * @param host remote access hostname to be used by clients
* @throws IOException
*/
- public synchronized void startServer() throws IOException {
+ public synchronized void startServer(ServerSocket s, String host) throws IOException {
if (running) {
- return;
+ throw new IOException("server already started");
}
- InetAddress inetAddress = null;
-
- try {
- // use RMI hostname property which corresponds to server -ip command option
- String hostname = System.getProperty("java.rmi.server.hostname");
- if (hostname != null) {
- inetAddress = InetAddress.getByName(hostname);
- }
- else {
- inetAddress = InetAddress.getLocalHost();
- }
+ if (s == null || s.isClosed() || StringUtilities.isAllBlank(host)) {
+ throw new IllegalArgumentException("invalid startServer parameters");
}
- catch (UnknownHostException e) {
- throw new IOException("failed to resolve server hostname: " + e.getMessage(), e);
- }
-
- ipAddress = inetAddress.getHostAddress();
-
- ServerSocketFactory serverSocketFactory = SSLServerSocketFactory.getDefault();
-
- // TODO: make backlog setting a property
- int backlog = 0; // default backlog used
-
- serverSocket = serverSocketFactory.createServerSocket(port, backlog, inetAddress);
-
- log.info("Started Block Stream Server on " + ipAddress + ":" + port);
-
+ serverSocket = s;
+ hostname = host;
running = true;
+ log.info("Starting Block Stream Server...");
+
cleanupTimerMonitor = GTimer.scheduleRepeatingRunnable(CLEANUP_PERIOD, CLEANUP_PERIOD,
() -> cleanupStaleRequests(false));
@@ -260,8 +236,6 @@ public class BlockStreamServer extends Thread {
Socket socket = null;
try {
socket = serverSocket.accept();
- // TODO: add parameters to control socket options
-
BlockStreamHandler handler = new BlockStreamHandler(socket);
handler.start();
}
@@ -305,7 +279,6 @@ public class BlockStreamServer extends Thread {
BlockStreamHandler(Socket socket) {
super("BlockStreamHandler-" + socket.getInetAddress() + "-" + socket.getPort());
this.socket = socket;
- // TODO: socket options?
}
@Override
diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteBlockStreamHandle.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteBlockStreamHandle.java
index 86c4ab9f6c..acf3a080c1 100644
--- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteBlockStreamHandle.java
+++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/stream/RemoteBlockStreamHandle.java
@@ -82,7 +82,7 @@ public abstract class RemoteBlockStreamHandle implements
*/
public RemoteBlockStreamHandle(BlockStreamServer server, int blockCount, int blockSize)
throws IOException {
- streamServerIPAddress = server.getServerIpAddress();
+ streamServerIPAddress = server.getServerHostname();
if (!server.isRunning() || streamServerIPAddress == null) {
throw new IOException("block stream server is not running");
}
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java
index 3bb28c4258..ba9b5d5686 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java
@@ -217,7 +217,7 @@ public class ClientUtil {
excMsg = exc.toString();
}
if (exc instanceof IOException) {
- Msg.showError(ClientUtil.class, parent, title, excMsg);
+ Msg.showError(ClientUtil.class, parent, title, excMsg, exc);
}
else {
// show the stacktrace for non-IOException
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ServerConnectTask.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ServerConnectTask.java
index 079ad0cc6a..e182d986c3 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ServerConnectTask.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ServerConnectTask.java
@@ -16,7 +16,6 @@
package ghidra.framework.client;
import java.io.IOException;
-import java.net.*;
import java.net.UnknownHostException;
import java.rmi.*;
import java.rmi.registry.LocateRegistry;
@@ -124,16 +123,6 @@ class ServerConnectTask extends Task {
return name;
}
- private static void setOutgoingIpAddress(InetAddress destAddr, int serverPort)
- throws IOException {
- InetSocketAddress sockAddr = new InetSocketAddress(destAddr, serverPort);
- Socket s = new Socket();
- s.connect(sockAddr, 5000);
- String ip = s.getLocalAddress().getHostAddress();
- System.setProperty("java.rmi.server.hostname", ip);
- s.close();
- }
-
private static boolean isSSLHandshakeCancelled(SSLHandshakeException e) throws IOException {
if (e.getMessage().indexOf("bad_certificate") > 0) {
if (ApplicationKeyManagerFactory.getPreferredKeyStore() == null) {
@@ -158,10 +147,6 @@ class ServerConnectTask extends Task {
*/
public static GhidraServerHandle getGhidraServerHandle(ServerInfo server) throws IOException {
- setOutgoingIpAddress(InetAddress.getByName(server.getServerName()), server.getPortNumber());
- Registry reg = LocateRegistry.getRegistry(server.getServerName(), server.getPortNumber());
- checkServerBindNames(reg);
-
GhidraServerHandle gsh = null;
try {
// Test SSL Handshake to ensure that user is able to decrypt keystore.
@@ -170,6 +155,18 @@ class ServerConnectTask extends Task {
// for their keystore which should cancel any connection attempt
testServerSSLConnection(server);
+ 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(),
+ new SslRMIClientSocketFactory());
+ checkServerBindNames(reg);
+ }
+
gsh = (GhidraServerHandle) reg.lookup(GhidraServerHandle.BIND_NAME);
gsh.checkCompatibility(GhidraServerHandle.INTERFACE_VERSION);
}
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraServerHandle.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraServerHandle.java
index dc1be2239e..13277e4885 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraServerHandle.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraServerHandle.java
@@ -47,6 +47,9 @@ public interface GhidraServerHandle extends Remote {
* 9: Added support for transient checkouts (7.2)
* 10: Added BlockStreamServer (7.4)
* 11: Revised password hash to SHA-256 (9.0)
+ * - version 9.1 switched to using SSL/TLS for RMI registry connection preventing
+ * older clients the ability to connect to the server. Remote interface remained
+ * unchanged allowing 9.1 clients to connect to 9.0 server.
*/
public static final int INTERFACE_VERSION = 11;
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/PrivateDatabase.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/PrivateDatabase.java
index a19787d2d5..99ed5b1821 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/PrivateDatabase.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/db/PrivateDatabase.java
@@ -229,9 +229,8 @@ public class PrivateDatabase extends Database {
/**
* If a cumulative change files exists, it will be deleted.
* @throws IOException
- * @throws CancelledException
*/
- public void updateCheckoutCopy() throws CancelledException, IOException {
+ public void updateCheckoutCopy() throws IOException {
if (!isCheckOutCopy) {
throw new IOException("Database is not a checkout copy");
}
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDatabaseItem.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDatabaseItem.java
index 19656fe370..d8c5288347 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDatabaseItem.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/store/local/LocalDatabaseItem.java
@@ -469,6 +469,21 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
}
}
+ @Override
+ public void clearCheckout() throws IOException {
+ if (isVersioned) {
+ throw new UnsupportedOperationException(
+ "clearCheckout is not applicable to versioned item");
+ }
+ if (fileSystem.isReadOnly()) {
+ throw new ReadOnlyException();
+ }
+ synchronized (fileSystem) {
+ privateDb.updateCheckoutCopy(); // removes change data
+ super.clearCheckout();
+ }
+ }
+
/*
* @see ghidra.framework.store.local.LocalFolderItem#deleteCurrentVersion()
*/
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java
index 16dd63eb88..6a48dddd13 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java
@@ -1680,20 +1680,31 @@ public class GhidraFileData {
* @throws IOException
*/
void convertToPrivateFile(TaskMonitor monitor) throws IOException, CancelledException {
- if (!isVersioned()) {
- return;
- }
- // TODO: If file is checked-out this will likely fail
- // copy this file to make a private copy
- GhidraFolderData oldParent = getParent();
- GhidraFile df = copyTo(oldParent, monitor);
- versionedFolderItem.delete(-1, ClientUtil.getUserName());
- oldParent.fileChanged(name);
- try {
- df.setName(name);
- }
- catch (InvalidNameException e) {
- throw new AssertException("Unexpected error", e);
+ synchronized (fileSystem) {
+ if (!(versionedFileSystem instanceof LocalFileSystem)) {
+ throw new UnsupportedOperationException("not supported for project");
+ }
+ if (!isVersioned()) {
+ return;
+ }
+ GhidraFolderData oldParent = getParent();
+ if (isCheckedOut()) {
+ // keep local changed file - discard revision information
+ folderItem.clearCheckout();
+ oldParent.fileChanged(name);
+ }
+ else {
+ // copy this file to make a private copy
+ GhidraFile df = copyTo(oldParent, monitor);
+ versionedFolderItem.delete(-1, ClientUtil.getUserName());
+ oldParent.fileChanged(name);
+ try {
+ df.setName(name);
+ }
+ catch (InvalidNameException e) {
+ throw new AssertException("Unexpected error", e);
+ }
+ }
}
}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/ProjectFileManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/ProjectFileManager.java
index e9831fce58..d54fd41062 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/ProjectFileManager.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/ProjectFileManager.java
@@ -15,14 +15,18 @@
*/
package ghidra.framework.data;
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+
import generic.timer.GhidraSwinglessTimer;
-import generic.timer.TimerCallback;
import ghidra.framework.client.*;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.remote.User;
-import ghidra.framework.store.*;
import ghidra.framework.store.FileSystem;
+import ghidra.framework.store.FileSystemListener;
+import ghidra.framework.store.FolderItem;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.framework.store.remote.RemoteFileSystem;
import ghidra.util.*;
@@ -30,11 +34,6 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateFileException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
-
-import java.io.*;
-import java.net.URL;
-import java.util.*;
-
import utilities.util.FileUtilities;
/**
@@ -74,13 +73,13 @@ public class ProjectFileManager implements ProjectData {
private RepositoryAdapter repository;
private DomainFileIndex fileIndex = new DomainFileIndex(this);
- private DomainFolderChangeListenerList listenerList = new DomainFolderChangeListenerList(
- fileIndex);
+ private DomainFolderChangeListenerList listenerList =
+ new DomainFolderChangeListenerList(fileIndex);
private RootGhidraFolderData rootFolderData;
private Map openDomainObjects =
- new HashMap();
+ new HashMap<>();
private TaskMonitorAdapter projectDisposalMonitor = new TaskMonitorAdapter();
@@ -107,11 +106,10 @@ public class ProjectFileManager implements ProjectData {
}
else if (isInWritableProject && !SystemUtilities.getUserName().equals(owner)) {
if (owner == null) {
- throw new NotOwnerException(
- "Older projects may only be opened as a View.\n"
- + "You must first create a new project or open an existing current project, \n"
- + "then use the \"Project->View\" menu action to open the older project as a view.\n"
- + "You can then drag old files into your active project.");
+ throw new NotOwnerException("Older projects may only be opened as a View.\n" +
+ "You must first create a new project or open an existing current project, \n" +
+ "then use the \"Project->View\" menu action to open the older project as a view.\n" +
+ "You can then drag old files into your active project.");
}
throw new NotOwnerException("Project is owned by " + owner);
}
@@ -166,8 +164,8 @@ public class ProjectFileManager implements ProjectData {
properties = new PropertyFile(projectDir, PROPERTY_FILENAME, "/", PROPERTY_FILENAME);
if (create) {
if (projectDir.exists()) {
- throw new DuplicateFileException("Project directory already exists: " +
- projectDir.getCanonicalPath());
+ throw new DuplicateFileException(
+ "Project directory already exists: " + projectDir.getCanonicalPath());
}
projectDir.mkdir();
localStorageLocator.getMarkerFile().createNewFile();
@@ -181,8 +179,8 @@ public class ProjectFileManager implements ProjectData {
}
if (properties.exists()) {
if (isInWritableProject && properties.isReadOnly()) {
- throw new ReadOnlyException("Project " + localStorageLocator.getName() +
- " is read-only");
+ throw new ReadOnlyException(
+ "Project " + localStorageLocator.getName() + " is read-only");
}
properties.readState();
owner = properties.getString(OWNER, SystemUtilities.getUserName());
@@ -236,9 +234,8 @@ public class ProjectFileManager implements ProjectData {
if (!versionedFileSystemDir.exists()) {
versionedFileSystemDir.mkdir();
}
- versionedFileSystem =
- LocalFileSystem.getLocalFileSystem(versionedFileSystemDir.getAbsolutePath(), true,
- true, false, true);
+ versionedFileSystem = LocalFileSystem.getLocalFileSystem(
+ versionedFileSystemDir.getAbsolutePath(), true, true, false, true);
}
}
@@ -259,9 +256,8 @@ public class ProjectFileManager implements ProjectData {
if (TEST_REPOSITORY_PATH != null) {
File versionedFileSystemDir = new File(TEST_REPOSITORY_PATH);
if (versionedFileSystemDir.exists()) {
- versionedFileSystem =
- LocalFileSystem.getLocalFileSystem(versionedFileSystemDir.getAbsolutePath(),
- false, true, false, true);
+ versionedFileSystem = LocalFileSystem.getLocalFileSystem(
+ versionedFileSystemDir.getAbsolutePath(), false, true, false, true);
return;
}
Msg.error(this, "Test repository not found: " + TEST_REPOSITORY_PATH);
@@ -276,9 +272,8 @@ public class ProjectFileManager implements ProjectData {
versionedFileSystemDir.mkdir();
create = true;
}
- versionedFileSystem =
- LocalFileSystem.getLocalFileSystem(versionedFileSystemDir.getAbsolutePath(),
- create, true, !isInWritableProject, true);
+ versionedFileSystem = LocalFileSystem.getLocalFileSystem(
+ versionedFileSystemDir.getAbsolutePath(), create, true, !isInWritableProject, true);
}
else {
int port = properties.getInt(PORT_NUMBER, -1);
@@ -353,17 +348,16 @@ public class ProjectFileManager implements ProjectData {
if (!fileSystemDir.isDirectory()) {
if (create && !fileSystemDir.exists()) {
if (!fileSystemDir.mkdir()) {
- throw new IOException("Failed to create project data directory: " +
- fileSystemDir);
+ throw new IOException(
+ "Failed to create project data directory: " + fileSystemDir);
}
}
else {
throw new IOException("Project data directory not found: " + fileSystemDir);
}
}
- fileSystem =
- LocalFileSystem.getLocalFileSystem(fileSystemDir.getAbsolutePath(), create, false,
- !isInWritableProject, true);
+ fileSystem = LocalFileSystem.getLocalFileSystem(fileSystemDir.getAbsolutePath(), create,
+ false, !isInWritableProject, true);
}
private void getUserFileSystem(boolean isInWritableProject) throws IOException {
@@ -378,9 +372,8 @@ public class ProjectFileManager implements ProjectData {
}
create = true;
}
- userFileSystem =
- LocalFileSystem.getLocalFileSystem(fileSystemDir.getAbsolutePath(), create, false,
- !isInWritableProject, true);
+ userFileSystem = LocalFileSystem.getLocalFileSystem(fileSystemDir.getAbsolutePath(), create,
+ false, !isInWritableProject, true);
}
/**
@@ -408,8 +401,8 @@ public class ProjectFileManager implements ProjectData {
public DomainFolder getFolder(String path) {
int len = path.length();
if (len == 0 || path.charAt(0) != FileSystem.SEPARATOR_CHAR) {
- throw new IllegalArgumentException("Absolute path must begin with '" +
- FileSystem.SEPARATOR_CHAR + "'");
+ throw new IllegalArgumentException(
+ "Absolute path must begin with '" + FileSystem.SEPARATOR_CHAR + "'");
}
try {
return getRootFolder().getFolderPathData(path).getDomainFolder();
@@ -466,8 +459,8 @@ public class ProjectFileManager implements ProjectData {
public DomainFile getFile(String path) {
int len = path.length();
if (len == 0 || path.charAt(0) != FileSystem.SEPARATOR_CHAR) {
- throw new IllegalArgumentException("Absolute path must begin with '" +
- FileSystem.SEPARATOR_CHAR + "'");
+ throw new IllegalArgumentException(
+ "Absolute path must begin with '" + FileSystem.SEPARATOR_CHAR + "'");
}
else if (path.charAt(len - 1) == FileSystem.SEPARATOR_CHAR) {
throw new IllegalArgumentException("Missing file name in path");
@@ -533,6 +526,7 @@ public class ProjectFileManager implements ProjectData {
* them to the specified list.
* @param list the list to receive the changed domain files
*/
+ @Override
public void findOpenFiles(List list) {
for (DomainObjectAdapter domainObj : openDomainObjects.values()) {
list.add(domainObj.getDomainFile());
@@ -613,16 +607,13 @@ public class ProjectFileManager implements ProjectData {
throw new IllegalStateException("Only private project may be converted to shared");
}
- // 1) check for checked out files
- findCheckedOutFiles(getRootFolder(), monitor);
-
- // 2) Convert versioned files to private files
+ // 1) Convert versioned files (inclulding checked-out files) to private files
convertFilesToPrivate(getRootFolder(), monitor);
- // 3) Update the properties with server info
+ // 2) Update the properties with server info
updatePropertiesFile(newRepository);
- // 4) Transition versioned filesystem and remove the old versioned filesystem
+ // 3) Transition versioned filesystem and remove the old versioned filesystem
versionedFileSystem.dispose();
repository = newRepository;
versionedFileSystem = new RemoteFileSystem(newRepository);
@@ -646,8 +637,8 @@ public class ProjectFileManager implements ProjectData {
updatePropertiesFile(newRepository);
}
- private void findCheckedOutFiles(DomainFolder folder, TaskMonitor monitor) throws IOException,
- CancelledException {
+ private void findCheckedOutFiles(DomainFolder folder, TaskMonitor monitor)
+ throws IOException, CancelledException {
DomainFile[] files = folder.getFiles();
for (int i = 0; i < files.length; i++) {
@@ -711,26 +702,17 @@ public class ProjectFileManager implements ProjectData {
return;
}
- userDataReconcileTimer =
- new GhidraSwinglessTimer(USER_DATA_RECONCILE_DELAY_MS, new TimerCallback() {
- @Override
- public void timerFired() {
- synchronized (ProjectFileManager.this) {
- startReconcileUserDataFiles();
- }
- }
- });
+ userDataReconcileTimer = new GhidraSwinglessTimer(USER_DATA_RECONCILE_DELAY_MS, () -> {
+ synchronized (ProjectFileManager.this) {
+ startReconcileUserDataFiles();
+ }
+ });
userDataReconcileTimer.setRepeats(false);
userDataReconcileTimer.start();
}
private void startReconcileUserDataFiles() {
- userDataReconcileThread = new Thread(new Runnable() {
- @Override
- public void run() {
- reconcileUserDataFiles();
- }
- });
+ userDataReconcileThread = new Thread(() -> reconcileUserDataFiles());
userDataReconcileThread.setPriority(Thread.MIN_PRIORITY);
userDataReconcileThread.start();
}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectInfoDialog.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectInfoDialog.java
index a925034f92..eac6e066da 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectInfoDialog.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectInfoDialog.java
@@ -557,6 +557,7 @@ public class ProjectInfoDialog extends DialogComponentProvider {
@Override
public void run(TaskMonitor monitor) {
try {
+ // NOTE: conversion of non-shared project will lose version history
project.getProjectData().updateRepositoryInfo(taskRepository, monitor);
status = true;
}
diff --git a/Ghidra/RuntimeScripts/Common/server/server.conf b/Ghidra/RuntimeScripts/Common/server/server.conf
index 191e2fed91..9c155dfa83 100644
--- a/Ghidra/RuntimeScripts/Common/server/server.conf
+++ b/Ghidra/RuntimeScripts/Common/server/server.conf
@@ -106,9 +106,10 @@ ghidra.repositories.dir=./repositories
# Ghidra server startup parameters.
#
# Command line parameters: (Add command line parameters as needed and renumber each starting from .1)
-# [-ip###.###.###.###] [-p#] [-a#] [-anonymous] [-ssh] [-d] [-e] [-u] [-n]
+# [-ip ] [-i ###.###.###.###] [-p#] [-a#] [-anonymous] [-ssh] [-d] [-e] [-u] [-n]
#
-# -ip###.###.###.### : ip address to bound to (by default, uses IP address bound to hostname)
+# -ip : remote access hostname or IPv4 address to be used by clients
+# -i #.#.#.# : interface IPv4 address to accept connections on (default all interfaces)
# -p# : base TCP port to be used (default: 13100)
# -a# : an optional authentication mode where # is a value 0 or 2
# 0 - Private user password
diff --git a/Ghidra/RuntimeScripts/Common/server/svrREADME.html b/Ghidra/RuntimeScripts/Common/server/svrREADME.html
index 5bd9df8898..59078612b2 100644
--- a/Ghidra/RuntimeScripts/Common/server/svrREADME.html
+++ b/Ghidra/RuntimeScripts/Common/server/svrREADME.html
@@ -58,6 +58,7 @@ typewriter {
Troubleshooting
-It is also highly recommended that the server's local hostname (with and with domain name) be
-mapped to its IP address with the hosts file (/etc/hosts on Linux). If
-this is not desirable for your setup, then the server should be explicitly bound to a specific IP
-address (see the -ip parameter in the Server
-Options section).
+By default the server will attempt to identify an appropriate remote access IPv4 address which will
+be written to the log at startup. In addition, the server will listen for incoming
+connections on all IPv4 interfaces by default. It is important to understand the difference between
+the published remote access address and the listening address (i.e. interface) which are both
+configurable. See the -ip and -i options in
+the Server Options section for more details.
(Back to Top)
@@ -289,8 +291,20 @@ public key files may be made without restarting the Ghidra Server.
login for -a0 authentication mode. Without this option, the users
client-side login ID will be assumed.
- -ip<#.#.#.#>
Forces the server to be bound to a specific
- IP address on the server. This option is required when a server has multiple IP interfaces.
+ -ip <hostname>
Identifies the remote access hostname (FQDN)
+ or IPv4 address which should be used by remote clients to access the server. By default the
+ host name reported by the operating system is resolved to an IPv4 address, if this fails the
+ local loopback address is used. The server log will indicate the remote access hostname
+ at startup. This option may be required when a server has multiple IP interfaces, relies on
+ a dynamic DNS or other network address translation for incoming connections.
+ This option establishes the property value for java.rmi.server.hostname.
+
+
+ -i <#.#.#.#>
Forces the server to be bound to a specific
+ IPv4 interface on the server. If specified and the -ip option is not,
+ the address specified by -i will establish the remote access IP
+ address as well as restrict the listening interface. If this option is not specified connections
+ will be accepted on any interface.
-p#
Allows the base TCP port to be specified (default: 13100). The
server utilizes three (3) TCP ports starting with the specified base port (e.g., 13100,13101 and 13102).
@@ -707,7 +721,7 @@ cacerts keystore file location is Ghidra/cacerts
and is also specified by the ghidra.cacerts property setting within the
server.conf file. Uncomment this specification within the
server.conf file to activate use of the cacerts
-for all incoming SSL connections (i.e., all Ghidra client users must install and employ the
+for all incoming SSL/TLS connections (i.e., all Ghidra client users must install and employ the
use of their personal PKI signing certificate for both headed and headless use - see
PKI Certificates). Clients can also impose server authentication
for all HTTPS and Ghidra Server connections by creating the cacerts file
@@ -858,6 +872,17 @@ If you see continuous failures to create repository folders or failures to check
the disk space on the server or folder permissions. When the server runs out of disk space, it
cannot create folders or check in files.
+
+
+Client/Server connection errors
+
+The Ghidra Server has transitioned to using SSL/TLS connections when accessing the server's RMI
+registry. This change in communication protocol can cause unexpected symptoms when attempting to
+connect incompatible versions of Ghidra. When an older incompatible Ghidra client attempts to access a
+newer SSL/TLS enabled Ghidra Server registry, the following connection error will occur:
+
+ non-JRMP server at remote endpoint
+
MS Windows - ERROR Missing Temp Directory
diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java
index e39f33733d..3343e43c0e 100644
--- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java
+++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/server/remote/ServerTestUtil.java
@@ -26,6 +26,8 @@ import java.util.ArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
+import javax.rmi.ssl.SslRMIClientSocketFactory;
+
import org.apache.commons.lang3.RandomStringUtils;
import generic.test.*;
@@ -550,7 +552,7 @@ public class ServerTestUtil {
}
private static boolean isServerRegistered(int port) throws IOException {
- Registry reg = LocateRegistry.getRegistry(LOCALHOST, port);
+ Registry reg = LocateRegistry.getRegistry(LOCALHOST, port, new SslRMIClientSocketFactory());
try {
reg.lookup(GhidraServerHandle.BIND_NAME);
return true;