mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-24 13:21:22 +08:00
Merge remote-tracking branch 'origin/GT-2685_ghidra1_ServerChanges'
This commit is contained in:
+4
-17
@@ -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<Principal> pricipalContext = new ThreadLocal<Principal>();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@@ -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<port>] [-a<authMode>] [-d<domain>] [-u] [-anonymous] [-ssh] [-ip<ipAddr>] [-e<expireDays>] [-n] <serverPath>";
|
||||
" [-p<port>] [-a<authMode>] [-d<domain>] [-u] [-anonymous] [-ssh] [-ip <hostname>] [-i <ipAddress>] [-e<expireDays>] [-n] <serverPath>";
|
||||
|
||||
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.");
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
Ghidra server startup parameters.
|
||||
Command line parameters: [-ip###.###.###.###] [-p#] [-a#] [-d<ntDomain>] [-e<days>] [-u] [-n] <repository_path>
|
||||
Command line parameters: [-ip <hostname>] [-i #.#.#.#] [-p#] [-a#] [-d<ntDomain>] [-e<days>] [-u] [-n] <repository_path>
|
||||
|
||||
-ip###.###.###.### : ip address to bound to (by default, uses IP address bound to hostname)
|
||||
|
||||
-ip <hostname> : 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
|
||||
|
||||
+22
-49
@@ -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
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ public abstract class RemoteBlockStreamHandle<T extends BlockStream> 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");
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+12
-15
@@ -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);
|
||||
}
|
||||
|
||||
+3
@@ -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;
|
||||
|
||||
|
||||
+1
-2
@@ -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");
|
||||
}
|
||||
|
||||
+15
@@ -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()
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+45
-63
@@ -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<String, DomainObjectAdapter> openDomainObjects =
|
||||
new HashMap<String, DomainObjectAdapter>();
|
||||
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<DomainFile> 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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<ntDomain>] [-e<days>] [-u] [-n] <repositories_path>
|
||||
# [-ip <hostname>] [-i ###.###.###.###] [-p#] [-a#] [-anonymous] [-ssh] [-d<ntDomain>] [-e<days>] [-u] [-n] <repositories_path>
|
||||
#
|
||||
# -ip###.###.###.### : ip address to bound to (by default, uses IP address bound to hostname)
|
||||
# -ip <hostname> : 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
|
||||
|
||||
@@ -58,6 +58,7 @@ typewriter {
|
||||
<LI><a href="#troubleshooting">Troubleshooting</a></LI>
|
||||
<UL>
|
||||
<LI><a href="#checkinFailures">Failures Creating Repository Folders / Checking in Files</a></LI>
|
||||
<LI><a href="#connectErrors">Client/Server connection errors</a></LI>
|
||||
<LI><a href="#windowsMissingTempDirectory">MS Windows - ERROR Missing Temp Directory</a></LI>
|
||||
<LI><a href="#windows7and8_scriptErrors">MS Windows 7 and 8 - ghidraSvr.bat, svrInstall.bat,
|
||||
or svrUninstall.bat Error</a></LI>
|
||||
@@ -203,11 +204,12 @@ name/address queries.
|
||||
</P>
|
||||
|
||||
<P>
|
||||
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 (<typewriter>/etc/hosts</typewriter> on Linux). If
|
||||
this is not desirable for your setup, then the server should be explicitly bound to a specific IP
|
||||
address (see the <typewriter>-ip</typewriter> parameter in the <a href="#serverOptions">Server
|
||||
Options</a> 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 <typewriter>-ip</typewriter> and <typewriter>-i</typewriter> options in
|
||||
the <a href="#serverOptions">Server Options</a> section for more details.
|
||||
</P>
|
||||
|
||||
(<a href="#top">Back to Top</a>)
|
||||
@@ -289,8 +291,20 @@ public key files may be made without restarting the Ghidra Server.
|
||||
login for <typewriter>-a0</typewriter> authentication mode. Without this option, the users
|
||||
client-side login ID will be assumed.</LI>
|
||||
<br>
|
||||
<LI><typewriter>-ip<#.#.#.#></typewriter><br>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.</LI>
|
||||
<LI><typewriter>-ip <hostname></typewriter><br>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 <i>java.rmi.server.hostname</i>.
|
||||
</LI>
|
||||
<br>
|
||||
<LI><typewriter>-i <#.#.#.#></typewriter><br>Forces the server to be bound to a specific
|
||||
IPv4 interface on the server. If specified and the <typewriter>-ip</typewriter> option is not,
|
||||
the address specified by <typewriter>-i</typewriter> 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.</LI>
|
||||
<br>
|
||||
<LI><typewriter>-p#</typewriter><br>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 <typewriter>Ghidra/cacerts</typewriter>
|
||||
and is also specified by the <typewriter>ghidra.cacerts</typewriter> property setting within the
|
||||
<typewriter>server.conf</typewriter> file. Uncomment this specification within the
|
||||
<typewriter>server.conf</typewriter> file to activate use of the <typewriter>cacerts</typewriter>
|
||||
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
|
||||
<a href="#pkiCertificates">PKI Certificates</a>). Clients can also impose server authentication
|
||||
for all HTTPS and Ghidra Server connections by creating the <typewriter>cacerts</typewriter> 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.
|
||||
</P>
|
||||
<br>
|
||||
|
||||
<a name="connectErrors"><h3><u>Client/Server connection errors</u></h3></a>
|
||||
<P>
|
||||
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:
|
||||
<PRE>
|
||||
non-JRMP server at remote endpoint
|
||||
</PRE>
|
||||
|
||||
<br>
|
||||
<a name="windowsMissingTempDirectory"><h3><u>MS Windows - ERROR Missing Temp Directory</u></h3></a>
|
||||
|
||||
+3
-1
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user