mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-27 23:17:03 +08:00
Merge remote-tracking branch 'origin/GP-394_ghidra1_SvrAdmin--SQUASHED'
(Closes #1703, Closes #2467)
This commit is contained in:
@@ -0,0 +1,303 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.server;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.security.auth.x500.X500Principal;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import ghidra.framework.remote.User;
|
||||||
|
import ghidra.framework.store.local.LocalFileSystem;
|
||||||
|
import ghidra.util.exception.DuplicateNameException;
|
||||||
|
import utilities.util.FileUtilities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>CommandProcessor</code> provides server processing of commands
|
||||||
|
* queued by the {@link ServerAdmin} class which corresponds to the <code>svrAdmin</code>
|
||||||
|
* shell command.
|
||||||
|
*/
|
||||||
|
public class CommandProcessor {
|
||||||
|
static final Logger log = LogManager.getLogger(CommandProcessor.class);
|
||||||
|
|
||||||
|
// Queued commands
|
||||||
|
static final String ADD_USER_COMMAND = "-add";
|
||||||
|
static final String REMOVE_USER_COMMAND = "-remove";
|
||||||
|
static final String RESET_USER_COMMAND = "-reset";
|
||||||
|
static final String SET_USER_DN_COMMAND = "-dn";
|
||||||
|
static final String GRANT_USER_COMMAND = "-grant";
|
||||||
|
static final String REVOKE_USER_COMMAND = "-revoke";
|
||||||
|
|
||||||
|
static final String PASSWORD_OPTION = "--p"; // applies to add and reset commands
|
||||||
|
|
||||||
|
private static final String ADMIN_CMD_DIR = LocalFileSystem.HIDDEN_DIR_PREFIX + "admin";
|
||||||
|
private static final String COMMAND_FILE_EXT = ".cmd";
|
||||||
|
|
||||||
|
// private static final int LOCK_TIMEOUT = 30000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command file filter
|
||||||
|
*/
|
||||||
|
static final FileFilter CMD_FILE_FILTER =
|
||||||
|
f -> f.isFile() && f.getName().endsWith(COMMAND_FILE_EXT);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File date comparator
|
||||||
|
*/
|
||||||
|
static final Comparator<File> FILE_DATE_COMPARATOR = (f1, f2) -> {
|
||||||
|
long t1 = f1.lastModified();
|
||||||
|
long t2 = f2.lastModified();
|
||||||
|
long diff = t1 - t2;
|
||||||
|
if (diff == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return diff < 0 ? -1 : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
private CommandProcessor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split a command string into individual arguments.
|
||||||
|
* @param cmd command string
|
||||||
|
* @return array of command arguments
|
||||||
|
*/
|
||||||
|
private static String[] splitCommand(String cmd) {
|
||||||
|
ArrayList<String> argList = new ArrayList<>();
|
||||||
|
int startIx = 0;
|
||||||
|
int endIx = 0;
|
||||||
|
int len = cmd.length();
|
||||||
|
boolean insideQuote = false;
|
||||||
|
while (endIx < len) {
|
||||||
|
char c = cmd.charAt(endIx);
|
||||||
|
if (!insideQuote && startIx == endIx) {
|
||||||
|
if (c == ' ' || c == '\"') {
|
||||||
|
insideQuote = (c == '\"');
|
||||||
|
startIx = ++endIx;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c == (insideQuote ? '\"' : ' ')) {
|
||||||
|
argList.add(cmd.substring(startIx, endIx));
|
||||||
|
startIx = ++endIx;
|
||||||
|
insideQuote = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
++endIx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (startIx != endIx) {
|
||||||
|
argList.add(cmd.substring(startIx, endIx));
|
||||||
|
}
|
||||||
|
String[] args = new String[argList.size()];
|
||||||
|
argList.toArray(args);
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the specified command.
|
||||||
|
* @param repositoryMgr server's repository manager
|
||||||
|
* @param cmd command string
|
||||||
|
* @throws IOException if IO error occurs while processing command
|
||||||
|
*/
|
||||||
|
private static void processCommand(RepositoryManager repositoryMgr, String cmd)
|
||||||
|
throws IOException {
|
||||||
|
UserManager userMgr = repositoryMgr.getUserManager();
|
||||||
|
String[] args = splitCommand(cmd);
|
||||||
|
switch (args[0]) {
|
||||||
|
case ADD_USER_COMMAND: // add user
|
||||||
|
String sid = args[1];
|
||||||
|
char[] pwdHash = null;
|
||||||
|
if (args.length == 4 && args[2].contentEquals(PASSWORD_OPTION)) {
|
||||||
|
pwdHash = args[3].toCharArray();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
userMgr.addUser(sid, pwdHash);
|
||||||
|
}
|
||||||
|
catch (DuplicateNameException e) {
|
||||||
|
log.error("Add User Failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case REMOVE_USER_COMMAND: // remove user
|
||||||
|
sid = args[1];
|
||||||
|
if (!userMgr.removeUser(sid)) {
|
||||||
|
log.info("User not found: '" + sid + "'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RESET_USER_COMMAND: // reset user
|
||||||
|
sid = args[1];
|
||||||
|
pwdHash = null;
|
||||||
|
if (args.length == 4 && args[2].contentEquals(PASSWORD_OPTION)) {
|
||||||
|
pwdHash = args[3].toCharArray();
|
||||||
|
}
|
||||||
|
if (!userMgr.resetPassword(sid, pwdHash)) {
|
||||||
|
log.info("Failed to reset password for user '" + sid + "'");
|
||||||
|
}
|
||||||
|
else if (pwdHash != null) {
|
||||||
|
log.info("User '" + sid + "' password reset to specified password");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.info("User '" + sid + "' password reset to default password");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SET_USER_DN_COMMAND: // set/add user with DN for PKI
|
||||||
|
sid = args[1];
|
||||||
|
X500Principal x500User = new X500Principal(args[2]);
|
||||||
|
if (userMgr.isValidUser(sid)) {
|
||||||
|
userMgr.setDistinguishedName(sid, x500User);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
userMgr.addUser(sid, x500User);
|
||||||
|
}
|
||||||
|
catch (DuplicateNameException e) {
|
||||||
|
// should never occur
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("User '" + sid + "' DN set (" + x500User.getName() + ")");
|
||||||
|
break;
|
||||||
|
case GRANT_USER_COMMAND: // grant repository access
|
||||||
|
sid = args[1];
|
||||||
|
int permission = parsePermission(args[2]);
|
||||||
|
String repName = args[3];
|
||||||
|
if (!userMgr.isValidUser(sid)) {
|
||||||
|
log.error(
|
||||||
|
"Failed to grant access for '" + sid +
|
||||||
|
"', user has not been added to server.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (permission < 0) {
|
||||||
|
log.error("Failed to process grant command. Invalid permission: " + args[2]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Repository rep = repositoryMgr.getRepository(repName);
|
||||||
|
if (rep == null) {
|
||||||
|
log.error("Failed to grant access for '" + sid + "', repository '" + repName +
|
||||||
|
"' not found.");
|
||||||
|
}
|
||||||
|
rep.setUserPermission(sid, permission);
|
||||||
|
break;
|
||||||
|
case REVOKE_USER_COMMAND: // grant repository access
|
||||||
|
sid = args[1];
|
||||||
|
repName = args[2];
|
||||||
|
rep = repositoryMgr.getRepository(repName);
|
||||||
|
if (rep == null) {
|
||||||
|
log.error("Failed to revoke access for '" + sid + "', repository '" + repName +
|
||||||
|
"' not found.");
|
||||||
|
}
|
||||||
|
rep.removeUser(sid);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.error("Failed to process unrecognized command: " + args[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parsePermission(String permissionStr) {
|
||||||
|
if ("+r".equals(permissionStr)) {
|
||||||
|
return User.READ_ONLY;
|
||||||
|
}
|
||||||
|
if ("+w".equals(permissionStr)) {
|
||||||
|
return User.WRITE;
|
||||||
|
}
|
||||||
|
if ("+a".equals(permissionStr)) {
|
||||||
|
return User.ADMIN;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static File getCommandDir(File serverRootDir) {
|
||||||
|
return new File(serverRootDir, ADMIN_CMD_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static File getOrCreateCommandDir(RepositoryManager repositoryMgr) {
|
||||||
|
File cmdDir = getCommandDir(repositoryMgr.getRootDir());
|
||||||
|
if (!cmdDir.exists()) {
|
||||||
|
// ensure process owner creates queued command directory
|
||||||
|
cmdDir.mkdir();
|
||||||
|
}
|
||||||
|
return cmdDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process all queued commands for the specified server.
|
||||||
|
* @param repositoryMgr server's repository manager
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static void processCommands(RepositoryManager repositoryMgr) throws IOException {
|
||||||
|
File cmdDir = getOrCreateCommandDir(repositoryMgr);
|
||||||
|
File[] files = cmdDir.listFiles(CMD_FILE_FILTER);
|
||||||
|
if (files == null) {
|
||||||
|
log.error("Failed to access command queue " + cmdDir.getAbsolutePath() +
|
||||||
|
": possible permission problem");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (files.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Processing queued commands");
|
||||||
|
Arrays.sort(files, FILE_DATE_COMPARATOR);
|
||||||
|
for (File file : files) {
|
||||||
|
List<String> cmdList = FileUtilities.getLines(file);
|
||||||
|
for (String cmdStr : cmdList) {
|
||||||
|
if (cmdStr.isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
processCommand(repositoryMgr, cmdStr.trim());
|
||||||
|
}
|
||||||
|
catch (ArrayIndexOutOfBoundsException e) {
|
||||||
|
log.error("Error occured processing command: " + cmdStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a list of command strings to a new command file.
|
||||||
|
* @param cmdList list of command strings
|
||||||
|
* @param cmdDir command file directory (must exist)
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
static void writeCommands(List<String> cmdList, File cmdDir) throws IOException {
|
||||||
|
File cmdTempFile = null;
|
||||||
|
try {
|
||||||
|
// Write command to temp file
|
||||||
|
cmdTempFile = File.createTempFile("adm", ".tmp", cmdDir);
|
||||||
|
FileUtils.writeLines(cmdTempFile, cmdList);
|
||||||
|
|
||||||
|
// Rename temp file to *.cmd file
|
||||||
|
String cmdFilename = cmdTempFile.getName();
|
||||||
|
cmdFilename = cmdFilename.substring(0, cmdFilename.length() - 4) + COMMAND_FILE_EXT;
|
||||||
|
File cmdFile = new File(cmdTempFile.getParentFile(), cmdFilename);
|
||||||
|
if (!cmdTempFile.renameTo(cmdFile)) {
|
||||||
|
throw new IOException("file error");
|
||||||
|
}
|
||||||
|
cmdTempFile = null;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (cmdTempFile != null) {
|
||||||
|
cmdTempFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.server;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>CommandWatcher</code> watches the command queue directory (~admin) for new
|
||||||
|
* command files and initiates their processing in the order they were issued.
|
||||||
|
* The use of the {@link WatchService} is limited to detection of command file creation
|
||||||
|
* and invokes {@link RepositoryManager#processCommandQueue()} when one or more
|
||||||
|
* command files have been queued or an {@link StandardWatchEventKinds#OVERFLOW}
|
||||||
|
* event occurs.
|
||||||
|
*/
|
||||||
|
public class CommandWatcher implements Runnable {
|
||||||
|
|
||||||
|
private RepositoryManager repositoryMgr;
|
||||||
|
private Path cmdDirPath;
|
||||||
|
private WatchService watcher;
|
||||||
|
|
||||||
|
CommandWatcher(RepositoryManager repositoryMgr) throws IOException {
|
||||||
|
this.repositoryMgr = repositoryMgr;
|
||||||
|
|
||||||
|
watcher = FileSystems.getDefault().newWatchService();
|
||||||
|
cmdDirPath = CommandProcessor.getOrCreateCommandDir(repositoryMgr).toPath();
|
||||||
|
cmdDirPath.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
try {
|
||||||
|
watcher.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
RepositoryManager.log.info("Command watcher started");
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
// wait for key to be signaled
|
||||||
|
WatchKey key;
|
||||||
|
try {
|
||||||
|
key = watcher.take();
|
||||||
|
}
|
||||||
|
catch (InterruptedException | ClosedWatchServiceException e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean processCommands = false;
|
||||||
|
for (WatchEvent<?> event : key.pollEvents()) {
|
||||||
|
WatchEvent.Kind<?> kind = event.kind();
|
||||||
|
|
||||||
|
// An OVERFLOW event can occur if events are lost or discarded.
|
||||||
|
if (kind == StandardWatchEventKinds.OVERFLOW) {
|
||||||
|
processCommands = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The filename is the
|
||||||
|
// context of the event.
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
WatchEvent<Path> ev = (WatchEvent<Path>) event;
|
||||||
|
Path filename = ev.context();
|
||||||
|
|
||||||
|
// Verify that the new file is a command file - ignore all others
|
||||||
|
Path child = cmdDirPath.resolve(filename);
|
||||||
|
File file = child.toFile();
|
||||||
|
|
||||||
|
// Only care about command files which still exist since
|
||||||
|
// they may have already been consumed
|
||||||
|
if (CommandProcessor.CMD_FILE_FILTER.accept(child.toFile()) && file.exists()) {
|
||||||
|
processCommands = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processCommands) {
|
||||||
|
try {
|
||||||
|
repositoryMgr.processCommandQueue();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
RepositoryManager.log.error("Command processing failure: " + e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the key to receive further watch events.
|
||||||
|
// Key will become invalid when closed/disposed
|
||||||
|
boolean valid = key.reset();
|
||||||
|
if (!valid) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RepositoryManager.log.info("Command watcher terminated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -71,14 +71,15 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||||||
/**
|
/**
|
||||||
* Create a new Repository at the given path; the directory has already
|
* Create a new Repository at the given path; the directory has already
|
||||||
* been created.
|
* been created.
|
||||||
|
* @param mgr repository manager
|
||||||
* @param currentUser user creating the repository, or null if the
|
* @param currentUser user creating the repository, or null if the
|
||||||
* repository exists
|
* repository exists
|
||||||
* @param rootFile root file for this repository
|
* @param rootFile root file for this repository
|
||||||
* @param initialize true means
|
* @param name repository name
|
||||||
* @throws IOException
|
* @throws IOException if filesystem error occurs
|
||||||
*/
|
*/
|
||||||
public Repository(RepositoryManager mgr, String currentUser, File rootFile, String name)
|
public Repository(RepositoryManager mgr, String currentUser, File rootFile, String name)
|
||||||
throws IOException, UserAccessException {
|
throws IOException {
|
||||||
this.mgr = mgr;
|
this.mgr = mgr;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
@@ -275,14 +276,17 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||||||
/**
|
/**
|
||||||
* Get the name of this repository.
|
* Get the name of this repository.
|
||||||
* @return name of the repository.
|
* @return name of the repository.
|
||||||
* @throws IOException
|
|
||||||
*/
|
*/
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see FileSystem#getItemCount()
|
* Get the total number of items contained within this repository.
|
||||||
|
* See {@link FileSystem#getItemCount()}.
|
||||||
|
* @return total number of repository items
|
||||||
|
* @throws IOException if filesystem IO error occurs
|
||||||
|
* @throws UnsupportedOperationException if file-system does not support this operation
|
||||||
*/
|
*/
|
||||||
public int getItemCount() throws IOException, UnsupportedOperationException {
|
public int getItemCount() throws IOException, UnsupportedOperationException {
|
||||||
return fileSystem.getItemCount();
|
return fileSystem.getItemCount();
|
||||||
@@ -321,7 +325,7 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||||||
/**
|
/**
|
||||||
* Convenience method for getting list of all "Known" users
|
* Convenience method for getting list of all "Known" users
|
||||||
* defined to the repository user manager.
|
* defined to the repository user manager.
|
||||||
* @param currentUser
|
* @param currentUser user performing request
|
||||||
* @return list of user names.
|
* @return list of user names.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
@@ -336,9 +340,9 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||||||
* Set the user access list.
|
* Set the user access list.
|
||||||
* @param currentUser user that is setting the access list on this
|
* @param currentUser user that is setting the access list on this
|
||||||
* repository; the current user must
|
* repository; the current user must
|
||||||
* @param users
|
* @param users user access list
|
||||||
* @param allowAnonymousAccess
|
* @param allowAnonymousAccess true if anonymous access should be permitted (assume allowed by server config).
|
||||||
* @throws UserAccessException
|
* @throws UserAccessException if currentUser is not a current repository admin
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void setUserList(String currentUser, User[] users, boolean allowAnonymousAccess)
|
public void setUserList(String currentUser, User[] users, boolean allowAnonymousAccess)
|
||||||
@@ -382,22 +386,55 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Privileged method for adding a new repository admin
|
* Privileged method for setting user access for this repository
|
||||||
* @param sid user username
|
* @param username user username
|
||||||
* @throws IOException
|
* @param permission access permission ({@link User#READ_ONLY},
|
||||||
|
* {@link User#WRITE}, or {@link User#ADMIN}).
|
||||||
|
* @return true if successful, false if user has not been added to server
|
||||||
|
* @throws IOException failed to update repository access list
|
||||||
*/
|
*/
|
||||||
void addAdmin(String username) throws IOException {
|
boolean setUserPermission(String username, int permission) throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
userMap.remove(username);
|
if (permission < User.READ_ONLY || permission > User.ADMIN) {
|
||||||
userMap.put(username, new User(username, User.ADMIN));
|
throw new IllegalArgumentException("Invalid permission: " + permission);
|
||||||
writeUserList(userMap, anonymousAccessAllowed);
|
}
|
||||||
|
if (mgr.getUserManager().isValidUser(username)) {
|
||||||
|
User newUser = new User(username, permission);
|
||||||
|
User oldUser = userMap.put(username, newUser);
|
||||||
|
writeUserList(userMap, anonymousAccessAllowed);
|
||||||
|
if (oldUser != null) {
|
||||||
|
log.info("User access to repository '" + name + "' changed: " + newUser);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.info("User access granted to repository '" + name + "': " + newUser);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Privileged method for removing user access from this repository
|
||||||
|
* @param username user username
|
||||||
|
* @return true if user had access and has been successfully removed, else false
|
||||||
|
* @throws IOException failed to update repository access list
|
||||||
|
*/
|
||||||
|
boolean removeUser(String username) throws IOException {
|
||||||
|
synchronized (fileSystem) {
|
||||||
|
if (userMap.remove(username) != null) {
|
||||||
|
writeUserList(userMap, anonymousAccessAllowed);
|
||||||
|
log.info("User access d from repository '" + name + "': " + username);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of known users for this repository.
|
* Get the list of known users for this repository.
|
||||||
* @param currentUser user that is requesting the user list.
|
* @param currentUser user that is requesting the user list.
|
||||||
* @throws UserAccessException
|
* @throws UserAccessException if currentUser is not a current repository admin
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public User[] getUserList(String currentUser) throws UserAccessException, IOException {
|
public User[] getUserList(String currentUser) throws UserAccessException, IOException {
|
||||||
@@ -428,29 +465,18 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||||||
* Get the specified user data.
|
* Get the specified user data.
|
||||||
* If the repository's user list if missing or currupt, this user
|
* If the repository's user list if missing or currupt, this user
|
||||||
* will become its administrator.
|
* will become its administrator.
|
||||||
* @param currentUser
|
* @param username user name attempting repository access
|
||||||
* @return user data
|
* @return user data
|
||||||
*/
|
*/
|
||||||
public User getUser(String currentUser) {
|
public User getUser(String username) {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
if (anonymousAccessAllowed && UserManager.ANONYMOUS_USERNAME.equals(currentUser)) {
|
if (anonymousAccessAllowed && UserManager.ANONYMOUS_USERNAME.equals(username)) {
|
||||||
return ANONYMOUS_USER;
|
return ANONYMOUS_USER;
|
||||||
}
|
}
|
||||||
if (userMap.isEmpty()) {
|
User user = userMap.get(username);
|
||||||
log.error("Empty repository access list, will attempt repair (" + name + ")");
|
|
||||||
log.warn("Adding user " + currentUser + " as Admin to repository (" + name + ")");
|
|
||||||
userMap.put(currentUser, new User(currentUser, User.ADMIN));
|
|
||||||
try {
|
|
||||||
writeUserList(currentUser, userMap, anonymousAccessAllowed);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
log.error("Failed to repair repository access list: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
User user = userMap.get(currentUser);
|
|
||||||
if (user == null && anonymousAccessAllowed) {
|
if (user == null && anonymousAccessAllowed) {
|
||||||
// allow authenticated user to access repository in read-only mode
|
// allow authenticated user to access repository in read-only mode
|
||||||
return new User(currentUser, User.READ_ONLY);
|
return new User(username, User.READ_ONLY);
|
||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@@ -460,8 +486,8 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||||||
* Write user access list to local file.
|
* Write user access list to local file.
|
||||||
* @param currentUser current user
|
* @param currentUser current user
|
||||||
* @param newUserMap user map
|
* @param newUserMap user map
|
||||||
* @param allowAnonymous
|
* @param allowAnonymous true if anonymous access is allowed
|
||||||
* @throws UserAccessException
|
* @throws UserAccessException if currentUser does not have admin priviledge
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private void writeUserList(String currentUser, LinkedHashMap<String, User> newUserMap,
|
private void writeUserList(String currentUser, LinkedHashMap<String, User> newUserMap,
|
||||||
@@ -472,13 +498,14 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||||||
throw new UserAccessException(currentUser + " must have ADMIN privilege!");
|
throw new UserAccessException(currentUser + " must have ADMIN privilege!");
|
||||||
}
|
}
|
||||||
writeUserList(newUserMap, allowAnonymous);
|
writeUserList(newUserMap, allowAnonymous);
|
||||||
|
log.info("User access list for repository '" + name + "' updated by: " + currentUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Privileged method for updating user access list.
|
* Privileged method for updating user access list.
|
||||||
* @param newUserMap
|
* @param newUserMap user map
|
||||||
* @param allowAnonymous
|
* @param allowAnonymous true if anonymous access is allowed
|
||||||
* @throws UserAccessException
|
* @throws UserAccessException if currentUser does not have admin priviledge
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private void writeUserList(LinkedHashMap<String, User> newUserMap, boolean allowAnonymous)
|
private void writeUserList(LinkedHashMap<String, User> newUserMap, boolean allowAnonymous)
|
||||||
@@ -533,27 +560,63 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print to stdout the user access permissions to the specified repository.
|
* Generate formatted list of user access permissions to the specified repository.
|
||||||
* This is intended to be used with the svrAdmin console command
|
* This is intended to be used with the svrAdmin console command
|
||||||
* @param repositoryDir repository directory
|
* @param repositoryDir repository directory
|
||||||
* @param pad padding string to be prefixed to each output line
|
* @param pad padding string to be prefixed to each output line
|
||||||
|
* @return formatted list of user access permissions
|
||||||
*/
|
*/
|
||||||
static void listUserPermissions(File repositoryDir, String pad) {
|
static String getFormattedUserPermissions(File repositoryDir, String pad) {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
File userAccessFile = new File(repositoryDir, ACCESS_CONTROL_FILENAME);
|
File userAccessFile = new File(repositoryDir, ACCESS_CONTROL_FILENAME);
|
||||||
try {
|
try {
|
||||||
ArrayList<User> list = new ArrayList<>();
|
List<User> list = new ArrayList<>();
|
||||||
boolean anonymousAccessAllowed = readAccessFile(userAccessFile, list);
|
boolean anonymousAccessAllowed = readAccessFile(userAccessFile, list);
|
||||||
Collections.sort(list);
|
Collections.sort(list);
|
||||||
if (anonymousAccessAllowed) {
|
if (anonymousAccessAllowed) {
|
||||||
System.out.println(pad + "* Anonymous read-only access permitted *");
|
buf.append(pad + "* Anonymous read-only access permitted *\n");
|
||||||
}
|
}
|
||||||
for (User user : list) {
|
for (User user : list) {
|
||||||
System.out.println(pad + user);
|
buf.append(pad + user + "\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
System.out.println(pad + "Failed to read repository access file: " + e.getMessage());
|
System.out.println(pad + "Failed to read repository access file: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate formatted list of user access permissions to the specified repository
|
||||||
|
* restricted to user names contained within listUserAccess.
|
||||||
|
* This is intended to be used with the svrAdmin console command
|
||||||
|
* @param repositoryDir repository directory
|
||||||
|
* @param pad padding string to be prefixed to each output line
|
||||||
|
* @param listUserAccess set of user names of interest
|
||||||
|
* @return formatted list of user access permissions or null if no users of interest found
|
||||||
|
*/
|
||||||
|
static String getFormattedUserPermissions(File repositoryDir, String pad,
|
||||||
|
Set<String> listUserAccess) {
|
||||||
|
StringBuilder buf = null;
|
||||||
|
File userAccessFile = new File(repositoryDir, ACCESS_CONTROL_FILENAME);
|
||||||
|
try {
|
||||||
|
ArrayList<User> list = new ArrayList<>();
|
||||||
|
readAccessFile(userAccessFile, list);
|
||||||
|
Collections.sort(list);
|
||||||
|
for (User user : list) {
|
||||||
|
if (!listUserAccess.contains(user.getName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (buf == null) {
|
||||||
|
buf = new StringBuilder();
|
||||||
|
}
|
||||||
|
buf.append(pad + user + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
System.out.println(pad + "Failed to read repository access file: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return buf != null ? buf.toString() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ import ghidra.framework.store.local.LocalFileSystem;
|
|||||||
import ghidra.server.remote.RepositoryServerHandleImpl;
|
import ghidra.server.remote.RepositoryServerHandleImpl;
|
||||||
import ghidra.util.NamingUtilities;
|
import ghidra.util.NamingUtilities;
|
||||||
import ghidra.util.StringUtilities;
|
import ghidra.util.StringUtilities;
|
||||||
import ghidra.util.exception.*;
|
import ghidra.util.exception.DuplicateFileException;
|
||||||
|
import ghidra.util.exception.UserAccessException;
|
||||||
import utilities.util.FileUtilities;
|
import utilities.util.FileUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +44,7 @@ public class RepositoryManager {
|
|||||||
private static Map<Thread, String> clientNameMap = new WeakHashMap<>();
|
private static Map<Thread, String> clientNameMap = new WeakHashMap<>();
|
||||||
|
|
||||||
private File rootDirFile;
|
private File rootDirFile;
|
||||||
|
private CommandWatcher commandWatcher;
|
||||||
private HashMap<String, Repository> repositoryMap; // maps name to Repository
|
private HashMap<String, Repository> repositoryMap; // maps name to Repository
|
||||||
private ArrayList<RepositoryServerHandleImpl> handleList = new ArrayList<>();
|
private ArrayList<RepositoryServerHandleImpl> handleList = new ArrayList<>();
|
||||||
private UserManager userMgr;
|
private UserManager userMgr;
|
||||||
@@ -92,6 +94,7 @@ public class RepositoryManager {
|
|||||||
* Dispose this repository manager and all repository instances
|
* Dispose this repository manager and all repository instances
|
||||||
*/
|
*/
|
||||||
public synchronized void dispose() {
|
public synchronized void dispose() {
|
||||||
|
commandWatcher.dispose();
|
||||||
Iterator<Repository> iter = repositoryMap.values().iterator();
|
Iterator<Repository> iter = repositoryMap.values().iterator();
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
Repository rep = iter.next();
|
Repository rep = iter.next();
|
||||||
@@ -101,6 +104,7 @@ public class RepositoryManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return repositories root directory
|
* Return repositories root directory
|
||||||
|
* @return server root directory
|
||||||
*/
|
*/
|
||||||
File getRootDir() {
|
File getRootDir() {
|
||||||
return rootDirFile;
|
return rootDirFile;
|
||||||
@@ -111,14 +115,14 @@ public class RepositoryManager {
|
|||||||
* @param currentUser user creating the repository
|
* @param currentUser user creating the repository
|
||||||
* @param name name of the repository
|
* @param name name of the repository
|
||||||
* @return a new Repository
|
* @return a new Repository
|
||||||
* @throws DuplicateNameException if another repository exists with the
|
* @throws DuplicateFileException if another repository exists with the
|
||||||
* given name
|
* given name
|
||||||
* @throws UserAccessException if the user does not exist in
|
* @throws UserAccessException if the user does not exist in
|
||||||
* the list of known users for this manager
|
* the list of known users for this manager
|
||||||
* @throws IOException if there was an error creating the repository
|
* @throws IOException if there was an error creating the repository
|
||||||
*/
|
*/
|
||||||
public synchronized Repository createRepository(String currentUser, String name)
|
public synchronized Repository createRepository(String currentUser, String name)
|
||||||
throws IOException {
|
throws IOException, DuplicateFileException {
|
||||||
|
|
||||||
if (isAnonymousUser(currentUser)) {
|
if (isAnonymousUser(currentUser)) {
|
||||||
throw new UserAccessException("Anonymous user not permitted to create repository");
|
throw new UserAccessException("Anonymous user not permitted to create repository");
|
||||||
@@ -179,7 +183,7 @@ public class RepositoryManager {
|
|||||||
* Delete a specified repository.
|
* Delete a specified repository.
|
||||||
* @param currentUser current user
|
* @param currentUser current user
|
||||||
* @param name repository name
|
* @param name repository name
|
||||||
* @throws IOException
|
* @throws IOException if error occurs while removing repository
|
||||||
*/
|
*/
|
||||||
public synchronized void deleteRepository(String currentUser, String name) throws IOException {
|
public synchronized void deleteRepository(String currentUser, String name) throws IOException {
|
||||||
|
|
||||||
@@ -229,6 +233,13 @@ public class RepositoryManager {
|
|||||||
return list.toArray(names);
|
return list.toArray(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized String[] getRepositoryNames() {
|
||||||
|
Set<String> nameSet = repositoryMap.keySet();
|
||||||
|
String[] names = nameSet.toArray(new String[nameSet.size()]);
|
||||||
|
Arrays.sort(names);
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all defined users. If currentUser is an
|
* Get all defined users. If currentUser is an
|
||||||
* Anonymous user an empty array will be returned.
|
* Anonymous user an empty array will be returned.
|
||||||
@@ -236,17 +247,11 @@ public class RepositoryManager {
|
|||||||
* @return array of users known to this manager or empty array if
|
* @return array of users known to this manager or empty array if
|
||||||
* we should not reveal to currentUser.
|
* we should not reveal to currentUser.
|
||||||
*/
|
*/
|
||||||
public synchronized String[] getAllUsers(String currentUser) throws IOException {
|
public synchronized String[] getAllUsers(String currentUser) {
|
||||||
if (isAnonymousUser(currentUser)) {
|
if (isAnonymousUser(currentUser)) {
|
||||||
return new String[0];
|
return new String[0];
|
||||||
}
|
}
|
||||||
try {
|
return userMgr.getUsers();
|
||||||
return userMgr.getUsers();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
log.error("Error while accessing user list: " + e.getMessage());
|
|
||||||
throw new IOException("Failed to read user list");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserManager getUserManager() {
|
public UserManager getUserManager() {
|
||||||
@@ -256,7 +261,7 @@ public class RepositoryManager {
|
|||||||
/**
|
/**
|
||||||
* Verify that the specified currentUser is a known user
|
* Verify that the specified currentUser is a known user
|
||||||
* @param currentUser current user
|
* @param currentUser current user
|
||||||
* @throws UserAccessException
|
* @throws UserAccessException specified user is not valid
|
||||||
*/
|
*/
|
||||||
private void validateUser(String currentUser) throws UserAccessException {
|
private void validateUser(String currentUser) throws UserAccessException {
|
||||||
if (!userMgr.isValidUser(currentUser)) {
|
if (!userMgr.isValidUser(currentUser)) {
|
||||||
@@ -266,7 +271,7 @@ public class RepositoryManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan for existing repositories and build repositoryMap.
|
* Scan for existing repositories and build repositoryMap.
|
||||||
* @throws IOException
|
* @throws IOException if error occurs accessing or writing to server storage directory
|
||||||
*/
|
*/
|
||||||
private void initialize() throws IOException {
|
private void initialize() throws IOException {
|
||||||
|
|
||||||
@@ -301,7 +306,22 @@ public class RepositoryManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userMgr.updateUserList(true);
|
// Start command queue watcher
|
||||||
|
commandWatcher = new CommandWatcher(this);
|
||||||
|
Thread t = new Thread(commandWatcher, "Server Command Watcher");
|
||||||
|
t.start();
|
||||||
|
|
||||||
|
processCommandQueue(); // process any old commands
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the server's user list and process any pending UserAdmin commands.
|
||||||
|
* @throws IOException if error occurs processing command files
|
||||||
|
*/
|
||||||
|
synchronized void processCommandQueue() throws IOException {
|
||||||
|
userMgr.readUserListIfNeeded();
|
||||||
|
userMgr.clearExpiredPasswords();
|
||||||
|
CommandProcessor.processCommands(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getElapsedTimeSince(long t) {
|
static String getElapsedTimeSince(long t) {
|
||||||
@@ -445,44 +465,84 @@ public class RepositoryManager {
|
|||||||
* Print to stdout the set of repository names defined within the specified repositories root.
|
* Print to stdout the set of repository names defined within the specified repositories root.
|
||||||
* This is intended to be used with the svrAdmin console command
|
* This is intended to be used with the svrAdmin console command
|
||||||
* @param repositoriesRootDir repositories root directory
|
* @param repositoriesRootDir repositories root directory
|
||||||
* @param includeUserAccessDetails
|
* @param includeUserAccessDetails if true additional user access details will displayed
|
||||||
|
* for each repository
|
||||||
*/
|
*/
|
||||||
static void listRepositories(File repositoriesRootDir, boolean includeUserAccessDetails) {
|
static void listRepositories(File repositoriesRootDir, boolean includeUserAccessDetails) {
|
||||||
String[] names = RepositoryManager.getRepositoryNames(repositoriesRootDir);
|
String[] names = RepositoryManager.getRepositoryNames(repositoriesRootDir);
|
||||||
System.out.println("\nRepositories:");
|
System.out.println("\nRepositories:");
|
||||||
if (names.length == 0) {
|
if (names.length == 0) {
|
||||||
System.out.println(" <No repositories have been created>");
|
System.out.println(" <No repositories have been created>");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
for (String name : names) {
|
for (String name : names) {
|
||||||
File repoDir = new File(repositoriesRootDir, NamingUtilities.mangle(name));
|
File repoDir = new File(repositoriesRootDir, NamingUtilities.mangle(name));
|
||||||
String rootPath = repoDir.getAbsolutePath();
|
String rootPath = repoDir.getAbsolutePath();
|
||||||
boolean isIndexed = IndexedLocalFileSystem.isIndexed(rootPath);
|
boolean isIndexed = IndexedLocalFileSystem.isIndexed(rootPath);
|
||||||
String type;
|
String type;
|
||||||
if (isIndexed || IndexedLocalFileSystem.hasIndexedStructure(rootPath)) {
|
if (isIndexed || IndexedLocalFileSystem.hasIndexedStructure(rootPath)) {
|
||||||
type = "Indexed Filesystem";
|
type = "Indexed Filesystem";
|
||||||
try {
|
try {
|
||||||
int indexVersion = IndexedLocalFileSystem.readIndexVersion(rootPath);
|
int indexVersion = IndexedLocalFileSystem.readIndexVersion(rootPath);
|
||||||
if (indexVersion == IndexedLocalFileSystem.LATEST_INDEX_VERSION) {
|
if (indexVersion == IndexedLocalFileSystem.LATEST_INDEX_VERSION) {
|
||||||
type = null;
|
type = null;
|
||||||
}
|
|
||||||
else {
|
|
||||||
type += " (V" + indexVersion + ")";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
else {
|
||||||
type += "(unknown)";
|
type += " (V" + indexVersion + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
catch (IOException e) {
|
||||||
type = "Mangled Filesystem";
|
type += "(unknown)";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
type = "Mangled Filesystem";
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println(" " + name + (type == null ? "" : (" - uses " + type)));
|
System.out.println(" " + name + (type == null ? "" : (" - uses " + type)));
|
||||||
|
|
||||||
if (includeUserAccessDetails) {
|
if (includeUserAccessDetails) {
|
||||||
Repository.listUserPermissions(repoDir, " ");
|
System.out.print(Repository.getFormattedUserPermissions(repoDir, " "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print to stdout the repository access permissions for the specified set of users.
|
||||||
|
* This is intended to be used with the svrAdmin console command
|
||||||
|
* @param repositoriesRootDir repositories root directory
|
||||||
|
* @param usernameSet set of users whose details should be displayed
|
||||||
|
*/
|
||||||
|
static void listRepositories(File repositoriesRootDir, Set<String> usernameSet) {
|
||||||
|
String[] names = RepositoryManager.getRepositoryNames(repositoriesRootDir);
|
||||||
|
if (names.length == 0) {
|
||||||
|
System.out.println(" <No repositories have been created>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean outputHeader = true;
|
||||||
|
for (String name : names) {
|
||||||
|
File repoDir = new File(repositoriesRootDir, NamingUtilities.mangle(name));
|
||||||
|
|
||||||
|
String formattedAccessList =
|
||||||
|
Repository.getFormattedUserPermissions(repoDir, " ", usernameSet);
|
||||||
|
if (formattedAccessList != null) {
|
||||||
|
if (outputHeader) {
|
||||||
|
System.out.println("\nRepositories:");
|
||||||
|
outputHeader = false;
|
||||||
}
|
}
|
||||||
|
System.out.println(" " + name);
|
||||||
|
System.out.print(formattedAccessList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputHeader) {
|
||||||
|
System.out.println("No repository access found for user(s):");
|
||||||
|
String[] userNames = usernameSet.toArray(new String[usernameSet.size()]);
|
||||||
|
Arrays.sort(userNames);
|
||||||
|
for (String n : userNames) {
|
||||||
|
System.out.println(" " + n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -504,4 +564,15 @@ public class RepositoryManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when user removed from server. Remove user from all repository access lists.
|
||||||
|
* @param username user name
|
||||||
|
* @throws IOException if error occured while updating repository access lists.
|
||||||
|
*/
|
||||||
|
void userRemoved(String username) throws IOException {
|
||||||
|
for (String repName : getRepositoryNames()) {
|
||||||
|
getRepository(repName).removeUser(username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,6 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||||||
private static final String MIGRATE_COMMAND = "-migrate";
|
private static final String MIGRATE_COMMAND = "-migrate";
|
||||||
private static final String MIGRATE_ALL_COMMAND = "-migrate-all";
|
private static final String MIGRATE_ALL_COMMAND = "-migrate-all";
|
||||||
|
|
||||||
private boolean propertyUsed = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main method for launching the ServerAdmin Application via GhidraLauncher.
|
* Main method for launching the ServerAdmin Application via GhidraLauncher.
|
||||||
* The following properties may be set:
|
* The following properties may be set:
|
||||||
@@ -86,30 +84,32 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||||||
String configFilePath = args.length != 0 && !args[0].startsWith("-") ? args[ix++]
|
String configFilePath = args.length != 0 && !args[0].startsWith("-") ? args[ix++]
|
||||||
: System.getProperty(CONFIG_FILE_PROPERTY);
|
: System.getProperty(CONFIG_FILE_PROPERTY);
|
||||||
|
|
||||||
File serverDir = getServerDirFromConfig(configFilePath);
|
System.out.println("server.conf: " + configFilePath);
|
||||||
if (serverDir == null || (args.length - ix) == 0) {
|
|
||||||
|
File serverRootDir = getServerDirFromConfig(configFilePath);
|
||||||
|
if (serverRootDir == null || (args.length - ix) == 0) {
|
||||||
displayUsage("");
|
displayUsage("");
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
serverDir = serverDir.getCanonicalFile();
|
serverRootDir = serverRootDir.getCanonicalFile();
|
||||||
}
|
}
|
||||||
catch (IOException e1) {
|
catch (IOException e1) {
|
||||||
System.err.println("Failed to resolve server directory: " + serverDir);
|
System.err.println("Failed to resolve server directory: " + serverRootDir);
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("Using server directory: " + serverDir);
|
System.out.println("Using server directory: " + serverRootDir);
|
||||||
|
|
||||||
File userFile = new File(serverDir, UserManager.USER_PASSWORD_FILE);
|
File userFile = new File(serverRootDir, UserManager.USER_PASSWORD_FILE);
|
||||||
if (!serverDir.isDirectory() || !userFile.isFile()) {
|
if (!serverRootDir.isDirectory() || !userFile.isFile()) {
|
||||||
System.err.println("Invalid Ghidra server directory!");
|
System.err.println("Invalid Ghidra server directory!");
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
File cmdDir = new File(serverDir, UserAdmin.ADMIN_CMD_DIR);
|
File cmdDir = CommandProcessor.getCommandDir(serverRootDir);
|
||||||
if (!cmdDir.isDirectory() || !cmdDir.canWrite()) {
|
if (!cmdDir.isDirectory() || !cmdDir.canWrite()) {
|
||||||
System.err.println("Insufficient privilege or server not started!");
|
System.err.println("Insufficient privilege or server not started!");
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
@@ -117,6 +117,8 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||||||
|
|
||||||
// Process command line
|
// Process command line
|
||||||
boolean listRepositories = false;
|
boolean listRepositories = false;
|
||||||
|
boolean listAllUserPermissions = false;
|
||||||
|
Set<String> listUsernameSet = new HashSet<>();
|
||||||
boolean listUsers = false;
|
boolean listUsers = false;
|
||||||
boolean migrationConfirmed = false;
|
boolean migrationConfirmed = false;
|
||||||
boolean migrationAbort = false;
|
boolean migrationAbort = false;
|
||||||
@@ -125,98 +127,122 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||||||
for (; ix < args.length; ix += cmdLen) {
|
for (; ix < args.length; ix += cmdLen) {
|
||||||
boolean queueCmd = true;
|
boolean queueCmd = true;
|
||||||
String pwdHash = null;
|
String pwdHash = null;
|
||||||
if (UserAdmin.ADD_USER_COMMAND.equals(args[ix])) { // add user
|
switch (args[ix]) {
|
||||||
cmdLen = 2;
|
case CommandProcessor.ADD_USER_COMMAND: // add user
|
||||||
validateSID(args, ix + 1);
|
cmdLen = 2;
|
||||||
if (hasOptionalArg(args, ix + 2, UserAdmin.PASSWORD_OPTION)) {
|
validateSID(args, ix + 1);
|
||||||
++cmdLen;
|
if (hasOptionalArg(args, ix + 2, CommandProcessor.PASSWORD_OPTION)) {
|
||||||
pwdHash = promptForPasswordAndGetSaltedHash(args[ix + 1]);
|
++cmdLen;
|
||||||
}
|
pwdHash = promptForPasswordAndGetSaltedHash(args[ix + 1]);
|
||||||
}
|
}
|
||||||
else if (UserAdmin.REMOVE_USER_COMMAND.equals(args[ix])) { // remove user
|
break;
|
||||||
cmdLen = 2;
|
case CommandProcessor.REMOVE_USER_COMMAND: // remove user
|
||||||
validateSID(args, ix + 1);
|
cmdLen = 2;
|
||||||
}
|
validateSID(args, ix + 1);
|
||||||
else if (UserAdmin.RESET_USER_COMMAND.equals(args[ix])) { // reset user
|
break;
|
||||||
cmdLen = 2;
|
case CommandProcessor.RESET_USER_COMMAND: // reset user
|
||||||
validateSID(args, ix + 1);
|
cmdLen = 2;
|
||||||
if (hasOptionalArg(args, ix + 2, UserAdmin.PASSWORD_OPTION)) {
|
validateSID(args, ix + 1);
|
||||||
++cmdLen;
|
if (hasOptionalArg(args, ix + 2, CommandProcessor.PASSWORD_OPTION)) {
|
||||||
pwdHash = promptForPasswordAndGetSaltedHash(args[ix + 1]);
|
++cmdLen;
|
||||||
}
|
pwdHash = promptForPasswordAndGetSaltedHash(args[ix + 1]);
|
||||||
}
|
}
|
||||||
else if (UserAdmin.SET_USER_DN_COMMAND.equals(args[ix])) { // set/add user with DN for PKI
|
break;
|
||||||
cmdLen = 3;
|
case CommandProcessor.SET_USER_DN_COMMAND: // set/add user with DN for PKI
|
||||||
validateSID(args, ix + 1);
|
cmdLen = 3;
|
||||||
validateDN(args, ix + 2);
|
validateSID(args, ix + 1);
|
||||||
}
|
validateDN(args, ix + 2);
|
||||||
else if (UserAdmin.SET_ADMIN_COMMAND.equals(args[ix])) { // set/add repository admin
|
break;
|
||||||
cmdLen = 3;
|
case CommandProcessor.GRANT_USER_COMMAND: // grant repository permission
|
||||||
validateSID(args, ix + 1);
|
cmdLen = 4;
|
||||||
validateRepName(args, ix + 2, serverDir);
|
validateSID(args, ix + 1);
|
||||||
}
|
validatePermission(args, ix + 2);
|
||||||
else if (LIST_COMMAND.equals(args[ix])) { // list repositories
|
validateRepositoryName(args, ix + 3, serverRootDir);
|
||||||
cmdLen = 1;
|
break;
|
||||||
queueCmd = false;
|
case CommandProcessor.REVOKE_USER_COMMAND: // revoke repository access
|
||||||
listRepositories = true;
|
cmdLen = 3;
|
||||||
}
|
validateSID(args, ix + 1);
|
||||||
else if (USERS_COMMAND.equals(args[ix])) { // list users (also affects listRepositories)
|
validateRepositoryName(args, ix + 2, serverRootDir);
|
||||||
cmdLen = 1;
|
break;
|
||||||
queueCmd = false;
|
case LIST_COMMAND: // list repositories;
|
||||||
listUsers = true;
|
queueCmd = false;
|
||||||
}
|
listRepositories = true;
|
||||||
else if (MIGRATE_ALL_COMMAND.equals(args[ix])) { // list repositories
|
boolean hasUsernames = false;
|
||||||
cmdLen = 1;
|
while ((ix + 1) < args.length && !args[ix + 1].startsWith("-")) {
|
||||||
queueCmd = false;
|
String sid = args[++ix]; // consume next arg as user sid
|
||||||
if (!migrationConfirmed && !confirmMigration()) {
|
validateSID(sid);
|
||||||
migrationAbort = true;
|
listUsernameSet.add(sid);
|
||||||
}
|
hasUsernames = true;
|
||||||
migrationConfirmed = true;
|
}
|
||||||
if (!migrationAbort) {
|
if (!hasUsernames && (ix + 1) < args.length && "--users".equals(args[ix + 1])) {
|
||||||
RepositoryManager.markAllRepositoriesForIndexMigration(serverDir);
|
listAllUserPermissions = true;
|
||||||
}
|
++ix;
|
||||||
}
|
}
|
||||||
else if (MIGRATE_COMMAND.equals(args[ix])) { // list repositories
|
break;
|
||||||
cmdLen = 2;
|
case USERS_COMMAND: // list users (also affects listRepositories)
|
||||||
queueCmd = false;
|
queueCmd = false;
|
||||||
if (ix == (args.length - 1)) {
|
listUsers = true;
|
||||||
System.err.println("Missing " + MIGRATE_COMMAND + " repository name argument");
|
listUsernameSet.clear();
|
||||||
}
|
break;
|
||||||
else {
|
case MIGRATE_ALL_COMMAND: // list repositories;
|
||||||
String repositoryName = args[ix + 1];
|
queueCmd = false;
|
||||||
if (!migrationConfirmed && !confirmMigration()) {
|
if (!migrationConfirmed && !confirmMigration()) {
|
||||||
migrationAbort = true;
|
migrationAbort = true;
|
||||||
}
|
}
|
||||||
migrationConfirmed = true;
|
migrationConfirmed = true;
|
||||||
if (!migrationAbort) {
|
if (!migrationAbort) {
|
||||||
Repository.markRepositoryForIndexMigration(serverDir, repositoryName,
|
RepositoryManager.markAllRepositoriesForIndexMigration(serverRootDir);
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
}
|
case MIGRATE_COMMAND: // list repositories
|
||||||
else {
|
queueCmd = false;
|
||||||
displayUsage("Invalid usage!");
|
if (ix == (args.length - 1)) {
|
||||||
System.exit(-1);
|
System.err.println(
|
||||||
|
"Missing " + MIGRATE_COMMAND + " repository name argument");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String repositoryName = args[ix + 1];
|
||||||
|
if (!migrationConfirmed && !confirmMigration()) {
|
||||||
|
migrationAbort = true;
|
||||||
|
}
|
||||||
|
migrationConfirmed = true;
|
||||||
|
if (!migrationAbort) {
|
||||||
|
Repository.markRepositoryForIndexMigration(serverRootDir,
|
||||||
|
repositoryName,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
displayUsage("Invalid usage!");
|
||||||
|
System.exit(-1);
|
||||||
}
|
}
|
||||||
if (queueCmd) {
|
if (queueCmd) {
|
||||||
addCommand(cmdList, args, ix, cmdLen, pwdHash);
|
addCommand(cmdList, args, ix, cmdLen, pwdHash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (cmdList.size() != 0) {
|
||||||
UserAdmin.writeCommands(cmdList, cmdDir);
|
try {
|
||||||
|
CommandProcessor.writeCommands(cmdList, cmdDir);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
System.err.println("Failed to queue commands: " + e.toString());
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
System.out.println("Command queued.");
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
|
||||||
System.err.println("Failed to queue commands: " + e.toString());
|
|
||||||
System.exit(-1);
|
|
||||||
}
|
|
||||||
System.out.println(cmdList.size() + " command(s) queued.");
|
|
||||||
|
|
||||||
if (listUsers) {
|
if (listUsers) {
|
||||||
UserManager.listUsers(serverDir);
|
UserManager.listUsers(serverRootDir);
|
||||||
}
|
}
|
||||||
if (listRepositories) {
|
if (listRepositories) {
|
||||||
RepositoryManager.listRepositories(serverDir, listUsers);
|
if (listUsernameSet.isEmpty()) {
|
||||||
|
RepositoryManager.listRepositories(serverRootDir, listAllUserPermissions);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
RepositoryManager.listRepositories(serverRootDir, listUsernameSet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
System.out.println();
|
System.out.println();
|
||||||
}
|
}
|
||||||
@@ -360,9 +386,9 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate properly formatted Distinguished Name
|
* Validate properly formatted Distinguished Name as command arg
|
||||||
* Example: 'CN=Doe John, OU=X, OU=Y, OU=DoD, O=U.S. Government, C=US'
|
* Example: 'CN=Doe John, OU=X, OU=Y, OU=DoD, O=U.S. Government, C=US'
|
||||||
* @param args
|
* @param args command args
|
||||||
* @param i argument index
|
* @param i argument index
|
||||||
*/
|
*/
|
||||||
private void validateDN(String[] args, int i) {
|
private void validateDN(String[] args, int i) {
|
||||||
@@ -376,43 +402,58 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||||||
args[i] = "\"" + x500User.getName() + "\"";
|
args[i] = "\"" + x500User.getName() + "\"";
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Msg.error(UserAdmin.class, "Invalid DN: " + dn);
|
Msg.error(CommandProcessor.class, "Invalid DN: " + dn);
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate username/sid
|
* Validate username/sid
|
||||||
* @param args
|
* @param args command args
|
||||||
* @param i argument index
|
* @param i argument index
|
||||||
*/
|
*/
|
||||||
private void validateSID(String[] args, int i) {
|
private void validateSID(String[] args, int i) {
|
||||||
if (args.length < (i + 1)) {
|
if (args.length < (i + 1)) {
|
||||||
displayUsage("Invalid usage!");
|
displayUsage("Invalid usage, expected username/sid");
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
}
|
}
|
||||||
String sid = args[i];
|
validateSID(args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateSID(String sid) {
|
||||||
if (!UserManager.isValidUserName(sid)) {
|
if (!UserManager.isValidUserName(sid)) {
|
||||||
Msg.error(UserAdmin.class, "Invalid username/sid: " + sid);
|
displayUsage("Invalid username/sid: " + sid);
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate repository name
|
* Validate repository permission arg (repository name to follow)
|
||||||
* @param args
|
* @param args command args
|
||||||
* @param i argument index
|
* @param i argument index
|
||||||
* @param rootDirFile base repository directory
|
|
||||||
*/
|
*/
|
||||||
private void validateRepName(String[] args, int i, File rootDirFile) {
|
private void validatePermission(String[] args, int i) {
|
||||||
|
if (args.length < (i + 1) || CommandProcessor.parsePermission(args[i]) < 0) {
|
||||||
|
displayUsage("Invalid usage, expected grant permission +r, +w or +a");
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate existing repository name as command arg
|
||||||
|
* @param args command args
|
||||||
|
* @param i argument index
|
||||||
|
* @param rootDirFile base repositories directory
|
||||||
|
*/
|
||||||
|
private void validateRepositoryName(String[] args, int i, File rootDirFile) {
|
||||||
if (args.length < (i + 1)) {
|
if (args.length < (i + 1)) {
|
||||||
displayUsage("Invalid usage!");
|
displayUsage("Invalid usage, expected repository name");
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
}
|
}
|
||||||
String repName = args[i];
|
String repName = args[i];
|
||||||
File f = new File(rootDirFile, NamingUtilities.mangle(repName));
|
File f = new File(rootDirFile, NamingUtilities.mangle(repName));
|
||||||
if (!f.isDirectory()) {
|
if (!f.isDirectory()) {
|
||||||
Msg.error(UserAdmin.class, "Repository not found: " + repName);
|
Msg.error(CommandProcessor.class, "Repository not found: " + repName);
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -483,7 +524,7 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an optional message followed by usage syntax.
|
* Display an optional message followed by usage syntax.
|
||||||
* @param msg
|
* @param msg optional error message to proceed usage display
|
||||||
*/
|
*/
|
||||||
private void displayUsage(String msg) {
|
private void displayUsage(String msg) {
|
||||||
if (msg != null) {
|
if (msg != null) {
|
||||||
@@ -496,21 +537,27 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||||||
System.err.println("\nSupported commands:");
|
System.err.println("\nSupported commands:");
|
||||||
System.err.println(" -add <sid> [--p]");
|
System.err.println(" -add <sid> [--p]");
|
||||||
System.err.println(
|
System.err.println(
|
||||||
" Add a new user to the server identified by their sid identifier [--p prompt for password]");
|
" Add a new user to the server identified by their sid identifier [optional --p prompts for password]");
|
||||||
|
System.err.println(" -grant <sid> [+r|+w|+a] <repository-name>");
|
||||||
|
System.err.println(
|
||||||
|
" Grant access permission for a user, identified by sid, to the named repository");
|
||||||
|
System.err.println(" -revoke <sid> <repository-name>");
|
||||||
|
System.err.println(
|
||||||
|
" Revoke access for a user, identified by sid, to a named repository");
|
||||||
System.err.println(" -remove <sid>");
|
System.err.println(" -remove <sid>");
|
||||||
System.err.println(" Remove the specified user from the server's user list");
|
System.err.println(
|
||||||
|
" Remove the specified user from the server's user list and revoke all repository access");
|
||||||
System.err.println(" -reset <sid> [--p]");
|
System.err.println(" -reset <sid> [--p]");
|
||||||
System.err.println(
|
System.err.println(
|
||||||
" Reset the specified user's server login password [--p prompt for password]");
|
" Reset the specified user's server login password [optional --p prompts for password]");
|
||||||
System.err.println(" -dn <sid> \"<dname>\"");
|
System.err.println(" -dn <sid> \"<dname>\"");
|
||||||
System.err.println(
|
System.err.println(
|
||||||
" When PKI authentication is used, add the specified X500 Distinguished Name for a user");
|
" When PKI authentication is used, add the specified X500 Distinguished Name for a user");
|
||||||
System.err.println(" -admin <sid> \"<repository-name>\"");
|
System.err.println(" -list [--users]");
|
||||||
System.err.println(
|
|
||||||
" Grant ADMIN privilege to the specified user with the specified repository");
|
|
||||||
System.err.println(" -list [-users]");
|
|
||||||
System.err.println(
|
System.err.println(
|
||||||
" Output list of repositories to the console (user access list will be included with -users)");
|
" Output list of repositories to the console (user access list will be included with -users)");
|
||||||
|
System.err.println(" -list <sid> [<sid>...]");
|
||||||
|
System.err.println(" Output list of repository permissions for each user specified");
|
||||||
System.err.println(" -users");
|
System.err.println(" -users");
|
||||||
System.err.println(" Output list of users to console which have server access");
|
System.err.println(" Output list of users to console which have server access");
|
||||||
System.err.println(" -migrate \"<repository-name>\"");
|
System.err.println(" -migrate \"<repository-name>\"");
|
||||||
|
|||||||
@@ -1,264 +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.server;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import javax.security.auth.x500.X500Principal;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import ghidra.framework.store.local.LocalFileSystem;
|
|
||||||
import ghidra.util.exception.DuplicateNameException;
|
|
||||||
import utilities.util.FileUtilities;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <code>UserAdmin</code> is an Application for generating administrative
|
|
||||||
* commands to be processed by the UserManager. Static methods are also
|
|
||||||
* provided which enable the UserManager to process such commands.
|
|
||||||
*/
|
|
||||||
public class UserAdmin {
|
|
||||||
static final Logger log = LogManager.getLogger(UserAdmin.class);
|
|
||||||
|
|
||||||
// Queued commands
|
|
||||||
static final String ADD_USER_COMMAND = "-add";
|
|
||||||
static final String REMOVE_USER_COMMAND = "-remove";
|
|
||||||
static final String RESET_USER_COMMAND = "-reset";
|
|
||||||
static final String SET_USER_DN_COMMAND = "-dn";
|
|
||||||
static final String SET_ADMIN_COMMAND = "-admin";
|
|
||||||
|
|
||||||
static final String PASSWORD_OPTION = "--p"; // applies to add and reset commands
|
|
||||||
|
|
||||||
static final String ADMIN_CMD_DIR = LocalFileSystem.HIDDEN_DIR_PREFIX + "admin";
|
|
||||||
static final String COMMAND_FILE_EXT = ".cmd";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Command file filter
|
|
||||||
*/
|
|
||||||
static final FileFilter CMD_FILE_FILTER =
|
|
||||||
f -> f.isFile() && f.getName().endsWith(COMMAND_FILE_EXT);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File date comparator
|
|
||||||
*/
|
|
||||||
static final Comparator<File> FILE_DATE_COMPARATOR = (f1, f2) -> {
|
|
||||||
long t1 = f1.lastModified();
|
|
||||||
long t2 = f2.lastModified();
|
|
||||||
long diff = t1 - t2;
|
|
||||||
if (diff == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return diff < 0 ? -1 : 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
private UserAdmin() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split a command string into individual arguments.
|
|
||||||
* @param cmd command string
|
|
||||||
* @return array of command arguments
|
|
||||||
*/
|
|
||||||
private static String[] splitCommand(String cmd) {
|
|
||||||
ArrayList<String> argList = new ArrayList<>();
|
|
||||||
int startIx = 0;
|
|
||||||
int endIx = 0;
|
|
||||||
int len = cmd.length();
|
|
||||||
boolean insideQuote = false;
|
|
||||||
while (endIx < len) {
|
|
||||||
char c = cmd.charAt(endIx);
|
|
||||||
if (!insideQuote && startIx == endIx) {
|
|
||||||
if (c == ' ' || c == '\"') {
|
|
||||||
insideQuote = (c == '\"');
|
|
||||||
startIx = ++endIx;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (c == (insideQuote ? '\"' : ' ')) {
|
|
||||||
argList.add(cmd.substring(startIx, endIx));
|
|
||||||
startIx = ++endIx;
|
|
||||||
insideQuote = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
++endIx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (startIx != endIx) {
|
|
||||||
argList.add(cmd.substring(startIx, endIx));
|
|
||||||
}
|
|
||||||
String[] args = new String[argList.size()];
|
|
||||||
argList.toArray(args);
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the specified command.
|
|
||||||
* @param repositoryMgr server's repository manager
|
|
||||||
* @param cmd command string
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
private static void processCommand(RepositoryManager repositoryMgr, String cmd)
|
|
||||||
throws IOException {
|
|
||||||
UserManager userMgr = repositoryMgr.getUserManager();
|
|
||||||
String[] args = splitCommand(cmd);
|
|
||||||
if (ADD_USER_COMMAND.equals(args[0])) { // add user
|
|
||||||
String sid = args[1];
|
|
||||||
char[] pwdHash = null;
|
|
||||||
if (args.length == 4 && args[2].contentEquals(PASSWORD_OPTION)) {
|
|
||||||
pwdHash = args[3].toCharArray();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
userMgr.addUser(sid, pwdHash);
|
|
||||||
log.info("User '" + sid + "' added");
|
|
||||||
}
|
|
||||||
catch (DuplicateNameException e) {
|
|
||||||
log.error("Add User Failed: user '" + sid + "' already exists");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (REMOVE_USER_COMMAND.equals(args[0])) { // remove user
|
|
||||||
String sid = args[1];
|
|
||||||
userMgr.removeUser(sid);
|
|
||||||
log.info("User '" + sid + "' removed");
|
|
||||||
}
|
|
||||||
else if (RESET_USER_COMMAND.equals(args[0])) { // reset user
|
|
||||||
String sid = args[1];
|
|
||||||
char[] pwdHash = null;
|
|
||||||
if (args.length == 4 && args[2].contentEquals(PASSWORD_OPTION)) {
|
|
||||||
pwdHash = args[3].toCharArray();
|
|
||||||
}
|
|
||||||
if (!userMgr.resetPassword(sid, pwdHash)) {
|
|
||||||
log.info("Failed to reset password for user '" + sid + "'");
|
|
||||||
}
|
|
||||||
else if (pwdHash != null) {
|
|
||||||
log.info("User '" + sid + "' password reset to specified password");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.info("User '" + sid + "' password reset to default password");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (SET_USER_DN_COMMAND.equals(args[0])) { // set/add user with DN for PKI
|
|
||||||
String sid = args[1];
|
|
||||||
X500Principal x500User = new X500Principal(args[2]);
|
|
||||||
if (userMgr.isValidUser(sid)) {
|
|
||||||
userMgr.setDistinguishedName(sid, x500User);
|
|
||||||
log.info("User '" + sid + "' DN set (" + x500User.getName() + ")");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
try {
|
|
||||||
userMgr.addUser(sid, x500User);
|
|
||||||
log.info("User '" + sid + "' added with DN (" + x500User.getName() +
|
|
||||||
") and default password");
|
|
||||||
}
|
|
||||||
catch (DuplicateNameException e) {
|
|
||||||
// should never occur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (SET_ADMIN_COMMAND.equals(args[0])) { // set/add repository admin
|
|
||||||
String sid = args[1];
|
|
||||||
String repName = args[2];
|
|
||||||
if (!userMgr.isValidUser(sid)) {
|
|
||||||
try {
|
|
||||||
userMgr.addUser(sid);
|
|
||||||
log.info("User '" + sid + "' added");
|
|
||||||
}
|
|
||||||
catch (DuplicateNameException e) {
|
|
||||||
return; // should never occur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Repository rep = repositoryMgr.getRepository(repName);
|
|
||||||
if (rep == null) {
|
|
||||||
log.error("Failed to add '" + sid + "' as admin, repository '" + repName +
|
|
||||||
"' not found.");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rep.addAdmin(sid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process all queued commands for the specified server.
|
|
||||||
* @param repositoryMgr server's repository manager
|
|
||||||
* @param serverDir Ghidra server directory
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
static void processCommands(RepositoryManager repositoryMgr) throws IOException {
|
|
||||||
File cmdDir = new File(repositoryMgr.getRootDir(), ADMIN_CMD_DIR);
|
|
||||||
if (!cmdDir.exists()) {
|
|
||||||
// ensure process owner creates queued command directory
|
|
||||||
cmdDir.mkdir();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File[] files = cmdDir.listFiles(CMD_FILE_FILTER);
|
|
||||||
if (files == null) {
|
|
||||||
log.error("Failed to access command queue " + cmdDir.getAbsolutePath() +
|
|
||||||
": possible permission problem");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Arrays.sort(files, FILE_DATE_COMPARATOR);
|
|
||||||
|
|
||||||
if (files.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.info("Processing " + files.length + " queued commands");
|
|
||||||
|
|
||||||
for (File file : files) {
|
|
||||||
List<String> cmdList = FileUtilities.getLines(file);
|
|
||||||
for (String cmdStr : cmdList) {
|
|
||||||
if (cmdStr.isBlank()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
processCommand(repositoryMgr, cmdStr.trim());
|
|
||||||
}
|
|
||||||
file.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a list of command strings to a new command file.
|
|
||||||
* @param cmdList list of command strings
|
|
||||||
* @param cmdDir command file directory
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
static void writeCommands(ArrayList<String> cmdList, File cmdDir) throws IOException {
|
|
||||||
File cmdFile = File.createTempFile("adm", ".tmp", cmdDir);
|
|
||||||
String cmdFilename = cmdFile.getName();
|
|
||||||
cmdFilename = cmdFilename.substring(0, cmdFilename.length() - 4) + COMMAND_FILE_EXT;
|
|
||||||
PrintWriter pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream(cmdFile)));
|
|
||||||
boolean success = false;
|
|
||||||
try {
|
|
||||||
Iterator<String> it = cmdList.iterator();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
String cmd = it.next();
|
|
||||||
pw.println(cmd);
|
|
||||||
}
|
|
||||||
pw.close();
|
|
||||||
if (!cmdFile.renameTo(new File(cmdFile.getParentFile(), cmdFilename))) {
|
|
||||||
throw new IOException("file error");
|
|
||||||
}
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (!success) {
|
|
||||||
cmdFile.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,7 @@ typewriter {
|
|||||||
<LI><a href="#introduction">Introduction</a></LI>
|
<LI><a href="#introduction">Introduction</a></LI>
|
||||||
<LI><a href="#javaRuntime">Java Runtime Environment</a></LI>
|
<LI><a href="#javaRuntime">Java Runtime Environment</a></LI>
|
||||||
<LI><a href="#serverConfig">Server Configuration</a></LI>
|
<LI><a href="#serverConfig">Server Configuration</a></LI>
|
||||||
|
<LI><a href="#serverLogs">Server Logs</a></LI>
|
||||||
<LI><a href="#serverMemory">Server Memory Considerations</a></LI>
|
<LI><a href="#serverMemory">Server Memory Considerations</a></LI>
|
||||||
<LI><a href="#dnsNote">Note regarding use of DNS (name lookup service)</a></LI>
|
<LI><a href="#dnsNote">Note regarding use of DNS (name lookup service)</a></LI>
|
||||||
<LI><a href="#userAuthentication">User Authentication</a></LI>
|
<LI><a href="#userAuthentication">User Authentication</a></LI>
|
||||||
@@ -42,7 +43,7 @@ typewriter {
|
|||||||
<LI><a href="#windows_install">Install as Automatic Service</a></LI>
|
<LI><a href="#windows_install">Install as Automatic Service</a></LI>
|
||||||
<LI><a href="#windows_uninstall">Uninstall Service</a></LI>
|
<LI><a href="#windows_uninstall">Uninstall Service</a></LI>
|
||||||
</UL>
|
</UL>
|
||||||
<LI><a href="#running_linux_mac">Running Ghidra Server on Linux or Mac-OSX</a></LI>
|
<LI><a href="#running_linux_mac">Running Ghidra Server on Linux or Mac OS</a></LI>
|
||||||
<UL>
|
<UL>
|
||||||
<LI><a href="#linux_mac_scripts">Server Scripts</a></LI>
|
<LI><a href="#linux_mac_scripts">Server Scripts</a></LI>
|
||||||
<LI><a href="#linux_mac_console">Running Server in Console Window</a></LI>
|
<LI><a href="#linux_mac_console">Running Server in Console Window</a></LI>
|
||||||
@@ -55,7 +56,7 @@ typewriter {
|
|||||||
<LI><a href="#pkiCertificates">PKI Certificates</a></LI>
|
<LI><a href="#pkiCertificates">PKI Certificates</a></LI>
|
||||||
<LI><a href="#pkiCertificateAuthorities">Managing PKI Certificate Authorities</a></LI>
|
<LI><a href="#pkiCertificateAuthorities">Managing PKI Certificate Authorities</a></LI>
|
||||||
<LI><a href="#upgradeServer">Upgrading the Ghidra Server Installation</a></LI>
|
<LI><a href="#upgradeServer">Upgrading the Ghidra Server Installation</a></LI>
|
||||||
<LI><a href="#troubleshooting">Troubleshooting</a></LI>
|
<LI><a href="#troubleshooting">Troubleshooting / Known Issues</a></LI>
|
||||||
<UL>
|
<UL>
|
||||||
<LI><a href="#checkinFailures">Failures Creating Repository Folders / Checking in Files</a></LI>
|
<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="#connectErrors">Client/Server connection errors</a></LI>
|
||||||
@@ -64,6 +65,7 @@ typewriter {
|
|||||||
or svrUninstall.bat Error</a></LI>
|
or svrUninstall.bat Error</a></LI>
|
||||||
<LI><a href="#selinuxDisabled">Linux - SELinux must be disabled</a></LI>
|
<LI><a href="#selinuxDisabled">Linux - SELinux must be disabled</a></LI>
|
||||||
<LI><a href="#randomHang">Linux - Potential hang from /dev/random depletion</a></LI>
|
<LI><a href="#randomHang">Linux - Potential hang from /dev/random depletion</a></LI>
|
||||||
|
<LI><a href="#macDiskAccess">Mac OS - Service fails to start (macOS 10.14 Mojave and later)</a></LI>
|
||||||
</UL>
|
</UL>
|
||||||
</UL>
|
</UL>
|
||||||
|
|
||||||
@@ -149,6 +151,16 @@ new installation. Using a non-default repositories directory outside your Ghidr
|
|||||||
will simplify the migration process.
|
will simplify the migration process.
|
||||||
</P>
|
</P>
|
||||||
|
|
||||||
|
(<a href="#top">Back to Top</a>)
|
||||||
|
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
||||||
|
<h2><a name="serverLog">Server Logs</a></h2>
|
||||||
|
|
||||||
|
<P>The Ghidra Server produces two log files, which for the most part have the same content.
|
||||||
|
The service <i>wrapper.log</i> file generally resides within the Ghidra installation root
|
||||||
|
directory, while the <i>server.log</i> file resides within the configured <i>repositories</i>
|
||||||
|
directory. When running the server in console mode all <i>wrapper.log</i> output is directed
|
||||||
|
to the console.
|
||||||
|
|
||||||
(<a href="#top">Back to Top</a>)
|
(<a href="#top">Back to Top</a>)
|
||||||
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
||||||
<h2><a name="serverMemory">Server Memory Considerations</a></h2>
|
<h2><a name="serverMemory">Server Memory Considerations</a></h2>
|
||||||
@@ -490,7 +502,10 @@ are not currently supported.
|
|||||||
|
|
||||||
(<a href="#top">Back to Top</a>)
|
(<a href="#top">Back to Top</a>)
|
||||||
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
||||||
<h2><a name="running_linux_mac">Running Ghidra Server on Linux or Mac-OSX</a></h2>
|
<h2><a name="running_linux_mac">Running Ghidra Server on Linux or Mac OS</a></h2>
|
||||||
|
|
||||||
|
<B>NOTE:</B> Mac OS has limited support. The latest supported version is macOS 10.13.x High Sierra
|
||||||
|
(see <a href="#macDiskAccess">Mac OS - Service fails to start</a>).</u>
|
||||||
|
|
||||||
<a name="linux_mac_scripts"><h3><u>Server Scripts (located within the server subdirectory)</u></h3></a>
|
<a name="linux_mac_scripts"><h3><u>Server Scripts (located within the server subdirectory)</u></h3></a>
|
||||||
|
|
||||||
@@ -584,7 +599,15 @@ to run as <i>root</i> and monitor/manage the Java process.
|
|||||||
<P>
|
<P>
|
||||||
The script <typewriter>svrAdmin</typewriter>, or <typewriter>svrAdmin.bat</typewriter>, provides
|
The script <typewriter>svrAdmin</typewriter>, or <typewriter>svrAdmin.bat</typewriter>, provides
|
||||||
the ability to manage Ghidra Server users and repositories. This script must be run from a
|
the ability to manage Ghidra Server users and repositories. This script must be run from a
|
||||||
command shell so that the proper command line arguments may be specified.
|
command shell so that the proper command line arguments may be specified. This command
|
||||||
|
should only be used after the corresponding Ghidra installation has been properly
|
||||||
|
configured via modification of the <typewriter>server/server.conf</typewriter> file
|
||||||
|
(see <a href="#serverConfig">Server Configuration</a>) and installed and/or started.
|
||||||
|
</P><P>
|
||||||
|
Many of the commands are queued for subsequent execution by the Ghidra Server process.
|
||||||
|
Due to this queing, there may be a delay between the invocation of a <typewriter>svrAdmin</typewriter>
|
||||||
|
command and its desired affect. The Ghidra log file(s) may be examined for feedback on
|
||||||
|
queued command execution (see <a href="#serverLogs">Server Logs</a>).
|
||||||
</P>
|
</P>
|
||||||
|
|
||||||
<P>
|
<P>
|
||||||
@@ -592,12 +615,14 @@ to run as <i>root</i> and monitor/manage the Java process.
|
|||||||
|
|
||||||
<PRE>
|
<PRE>
|
||||||
svrAdmin [<server-root-path>]
|
svrAdmin [<server-root-path>]
|
||||||
[-add <user_sid> [--p]]
|
[-add <user_sid> [--p]]
|
||||||
|
[-grant <user_sid> <"+r"|"+w"|"+a"> <repository_name>]
|
||||||
|
[-revoke <user_sid> <repository_name>]
|
||||||
[-remove <user_sid>]
|
[-remove <user_sid>]
|
||||||
[-reset <user_sid> [--p]]
|
[-reset <user_sid> [--p]]
|
||||||
[-dn <user_sid> "<user_dn>"]
|
[-dn <user_sid> "<user_dn>"]
|
||||||
[-admin <user_sid> "<repository_name>"]
|
[-list <user_sid> [<user_sid>...]]
|
||||||
[-list]
|
[-list [--users]]
|
||||||
[-users]
|
[-users]
|
||||||
[-migrate-all]
|
[-migrate-all]
|
||||||
[-migrate "<repository_name>"]
|
[-migrate "<repository_name>"]
|
||||||
@@ -626,11 +651,29 @@ to run as <i>root</i> and monitor/manage the Java process.
|
|||||||
svrAdmin -add mySID --p
|
svrAdmin -add mySID --p
|
||||||
</PRE>
|
</PRE>
|
||||||
</LI>
|
</LI>
|
||||||
|
<LI><typewriter>-grant</typewriter> <b>(Grant Repository Access for User)</b><br>
|
||||||
|
Grant access for a specified user and repository where both must be known to the server.
|
||||||
|
Repository access permission must be specified as +r for READ_ONLY, +w for WRITE or +a for ADMIN.
|
||||||
|
Examples:
|
||||||
|
<PRE>
|
||||||
|
svrAdmin -grant mySID +a myRepo
|
||||||
|
svrAdmin -grant mySID +w myRepo
|
||||||
|
</PRE>
|
||||||
|
</LI>
|
||||||
|
<LI><typewriter>-revoke</typewriter> <b>(Revoke Repository Access for User)</b><br>
|
||||||
|
Revoke the access for a specified user and named repository. Currently, revoking access for a
|
||||||
|
user does not disconnect them if currently connected.
|
||||||
|
Examples:
|
||||||
|
<PRE>
|
||||||
|
svrAdmin -revoke mySID myRepo
|
||||||
|
</PRE>
|
||||||
|
</LI>
|
||||||
<LI><typewriter>-remove</typewriter> <b>(Removing a User)</b><br>
|
<LI><typewriter>-remove</typewriter> <b>(Removing a User)</b><br>
|
||||||
A user may be removed from the server with this command form. This will only prevent the
|
A user may be removed from the Ghidra Server and all repositories with this command form. This will only prevent the
|
||||||
specified user from connecting to the server and will have no effect on the state or history
|
specified user from connecting to the server in the future and will have no effect on the state or history
|
||||||
of repository files. If a repository admin wishes to clear a user's checkouts, this is
|
of repository files. If a repository admin wishes to clear a user's checkouts, this is
|
||||||
a separate task which may be performed from an admin's Ghidra client.
|
a separate task which may be performed from an admin's Ghidra client. Currently, removing a
|
||||||
|
user does not disconnect them if currently connected.
|
||||||
<br><br>
|
<br><br>
|
||||||
Example:
|
Example:
|
||||||
<PRE>
|
<PRE>
|
||||||
@@ -661,26 +704,19 @@ to run as <i>root</i> and monitor/manage the Java process.
|
|||||||
<typewriter>UnknownDN.log</typewriter> file following an attempted connection with their PKCS
|
<typewriter>UnknownDN.log</typewriter> file following an attempted connection with their PKCS
|
||||||
certificate.
|
certificate.
|
||||||
</LI>
|
</LI>
|
||||||
<br>
|
<LI><typewriter>-list</typewriter> <b>(List All Repositories and/or User Permissions)</b><br>
|
||||||
<LI><typewriter>-admin</typewriter> <b>(Adding a Repository Administrator)</b><br>
|
If the <i>--users</i> option is also present, the complete user access
|
||||||
If an existing repository administrator is unable to add another user as administrator, the
|
list will be included for each repository. Otherwise, command may be followed by one or user SIDs (separated by a space)
|
||||||
server administrator may use this command to specify a new repository administrator.
|
which will limit the displayed repository list and access permissions to those users specified.
|
||||||
<br><br>
|
|
||||||
Example:
|
|
||||||
<PRE>
|
|
||||||
svrAdmin -admin mySID "myProject"
|
|
||||||
</PRE>
|
|
||||||
</LI>
|
|
||||||
<LI><typewriter>-list</typewriter> <b>(List All Repositories)</b><br>
|
|
||||||
Lists all repositories. If the <i>-users</i> option is also present, the user access
|
|
||||||
list will be included for each repository.
|
|
||||||
<br><br>
|
<br><br>
|
||||||
Example:
|
Example:
|
||||||
<PRE>
|
<PRE>
|
||||||
svrAdmin -list
|
svrAdmin -list
|
||||||
|
svrAdmin -list --users
|
||||||
|
svrAdmin -list mySID
|
||||||
</PRE>
|
</PRE>
|
||||||
<LI><typewriter>-users</typewriter> <b>(List All Users)</b><br>
|
<LI><typewriter>-users</typewriter> <b>(List All Users)</b><br>
|
||||||
Lists all users with server access. May also be coupled with the <i>-list</i> option.
|
Lists all users with server access.
|
||||||
<br><br>
|
<br><br>
|
||||||
Example:
|
Example:
|
||||||
<PRE>
|
<PRE>
|
||||||
@@ -894,7 +930,7 @@ Please note that the Ghidra Server does not currently support Certificate Revoca
|
|||||||
<br>
|
<br>
|
||||||
<LI>Uninstall an installed Ghidra Server Service by following the <typewriter>Uninstall Service</typewriter>
|
<LI>Uninstall an installed Ghidra Server Service by following the <typewriter>Uninstall Service</typewriter>
|
||||||
instructions corresponding to your operating system (<a href="#windows_uninstall">Windows</a>
|
instructions corresponding to your operating system (<a href="#windows_uninstall">Windows</a>
|
||||||
or <a href="#linux_mac_uninstall">Linux/Mac-OSX</a>).</LI>
|
or <a href="#linux_mac_uninstall">Linux/Mac OS</a>).</LI>
|
||||||
<br>
|
<br>
|
||||||
<LI>Unzip the new Ghidra distribution to a new installation directory (general unpacking and installation
|
<LI>Unzip the new Ghidra distribution to a new installation directory (general unpacking and installation
|
||||||
guidelines may be found in <typewriter>ghidra_<I>x.x</I>/docs/InstallationGuide.html</typewriter>).</LI>
|
guidelines may be found in <typewriter>ghidra_<I>x.x</I>/docs/InstallationGuide.html</typewriter>).</LI>
|
||||||
@@ -953,7 +989,7 @@ backup of your project or server repositories directory is highly recommended be
|
|||||||
|
|
||||||
(<a href="#top">Back to Top</a>)
|
(<a href="#top">Back to Top</a>)
|
||||||
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
||||||
<h2><a name="troubleshooting">Troubleshooting</a></h2>
|
<h2><a name="troubleshooting">Troubleshooting / Known Issues</a></h2>
|
||||||
|
|
||||||
<a name="checkinFailures"><h3><u>Failures Creating Repository Folders / Checking in Files</u></h3></a>
|
<a name="checkinFailures"><h3><u>Failures Creating Repository Folders / Checking in Files</u></h3></a>
|
||||||
<P>
|
<P>
|
||||||
@@ -1028,7 +1064,7 @@ Expansion Daemon) which will satisfy the entropy demand needed by /dev/random.
|
|||||||
</P>
|
</P>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<a name="macDiskAccess"><h3><u>Mac OS - Service fails to start</u></h3></a>
|
<a name="macDiskAccess"><h3><u>Mac OS - Service fails to start (macOS 10.14 Mojave and later)</u></h3></a>
|
||||||
<P>
|
<P>
|
||||||
The installed service may fail to start with Mac OS Majave (10.14) and later due
|
The installed service may fail to start with Mac OS Majave (10.14) and later due
|
||||||
to changes in the Mac OS system protection feature. When the service fails to start it does not
|
to changes in the Mac OS system protection feature. When the service fails to start it does not
|
||||||
|
|||||||
Reference in New Issue
Block a user