mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-10-03 00:44:52 +00:00
Merge remote-tracking branch 'origin/GP-394_ghidra1_SvrAdmin--SQUASHED'
(Closes #1703, Closes #2467)
This commit is contained in:
commit
65a2cc42dc
|
@ -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
|
||||
* been created.
|
||||
* @param mgr repository manager
|
||||
* @param currentUser user creating the repository, or null if the
|
||||
* repository exists
|
||||
* @param rootFile root file for this repository
|
||||
* @param initialize true means
|
||||
* @throws IOException
|
||||
* @param name repository name
|
||||
* @throws IOException if filesystem error occurs
|
||||
*/
|
||||
public Repository(RepositoryManager mgr, String currentUser, File rootFile, String name)
|
||||
throws IOException, UserAccessException {
|
||||
throws IOException {
|
||||
this.mgr = mgr;
|
||||
this.name = name;
|
||||
|
||||
|
@ -275,14 +276,17 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||
/**
|
||||
* Get the name of this repository.
|
||||
* @return name of the repository.
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getName() {
|
||||
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 {
|
||||
return fileSystem.getItemCount();
|
||||
|
@ -321,7 +325,7 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||
/**
|
||||
* Convenience method for getting list of all "Known" users
|
||||
* defined to the repository user manager.
|
||||
* @param currentUser
|
||||
* @param currentUser user performing request
|
||||
* @return list of user names.
|
||||
* @throws IOException
|
||||
*/
|
||||
|
@ -336,9 +340,9 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||
* Set the user access list.
|
||||
* @param currentUser user that is setting the access list on this
|
||||
* repository; the current user must
|
||||
* @param users
|
||||
* @param allowAnonymousAccess
|
||||
* @throws UserAccessException
|
||||
* @param users user access list
|
||||
* @param allowAnonymousAccess true if anonymous access should be permitted (assume allowed by server config).
|
||||
* @throws UserAccessException if currentUser is not a current repository admin
|
||||
* @throws IOException
|
||||
*/
|
||||
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
|
||||
* @param sid user username
|
||||
* @throws IOException
|
||||
* Privileged method for setting user access for this repository
|
||||
* @param username user username
|
||||
* @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) {
|
||||
userMap.remove(username);
|
||||
userMap.put(username, new User(username, User.ADMIN));
|
||||
writeUserList(userMap, anonymousAccessAllowed);
|
||||
if (permission < User.READ_ONLY || permission > User.ADMIN) {
|
||||
throw new IllegalArgumentException("Invalid permission: " + permission);
|
||||
}
|
||||
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.
|
||||
* @param currentUser user that is requesting the user list.
|
||||
* @throws UserAccessException
|
||||
* @throws UserAccessException if currentUser is not a current repository admin
|
||||
* @throws IOException
|
||||
*/
|
||||
public User[] getUserList(String currentUser) throws UserAccessException, IOException {
|
||||
|
@ -428,29 +465,18 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||
* Get the specified user data.
|
||||
* If the repository's user list if missing or currupt, this user
|
||||
* will become its administrator.
|
||||
* @param currentUser
|
||||
* @param username user name attempting repository access
|
||||
* @return user data
|
||||
*/
|
||||
public User getUser(String currentUser) {
|
||||
public User getUser(String username) {
|
||||
synchronized (fileSystem) {
|
||||
if (anonymousAccessAllowed && UserManager.ANONYMOUS_USERNAME.equals(currentUser)) {
|
||||
if (anonymousAccessAllowed && UserManager.ANONYMOUS_USERNAME.equals(username)) {
|
||||
return ANONYMOUS_USER;
|
||||
}
|
||||
if (userMap.isEmpty()) {
|
||||
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);
|
||||
User user = userMap.get(username);
|
||||
if (user == null && anonymousAccessAllowed) {
|
||||
// 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;
|
||||
}
|
||||
|
@ -460,8 +486,8 @@ public class Repository implements FileSystemListener, RepositoryLogger {
|
|||
* Write user access list to local file.
|
||||
* @param currentUser current user
|
||||
* @param newUserMap user map
|
||||
* @param allowAnonymous
|
||||
* @throws UserAccessException
|
||||
* @param allowAnonymous true if anonymous access is allowed
|
||||
* @throws UserAccessException if currentUser does not have admin priviledge
|
||||
* @throws IOException
|
||||
*/
|
||||
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!");
|
||||
}
|
||||
writeUserList(newUserMap, allowAnonymous);
|
||||
log.info("User access list for repository '" + name + "' updated by: " + currentUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Privileged method for updating user access list.
|
||||
* @param newUserMap
|
||||
* @param allowAnonymous
|
||||
* @throws UserAccessException
|
||||
* @param newUserMap user map
|
||||
* @param allowAnonymous true if anonymous access is allowed
|
||||
* @throws UserAccessException if currentUser does not have admin priviledge
|
||||
* @throws IOException
|
||||
*/
|
||||
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
|
||||
* @param repositoryDir repository directory
|
||||
* @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);
|
||||
try {
|
||||
ArrayList<User> list = new ArrayList<>();
|
||||
List<User> list = new ArrayList<>();
|
||||
boolean anonymousAccessAllowed = readAccessFile(userAccessFile, list);
|
||||
Collections.sort(list);
|
||||
if (anonymousAccessAllowed) {
|
||||
System.out.println(pad + "* Anonymous read-only access permitted *");
|
||||
buf.append(pad + "* Anonymous read-only access permitted *\n");
|
||||
}
|
||||
for (User user : list) {
|
||||
System.out.println(pad + user);
|
||||
buf.append(pad + user + "\n");
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
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.util.NamingUtilities;
|
||||
import ghidra.util.StringUtilities;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.exception.DuplicateFileException;
|
||||
import ghidra.util.exception.UserAccessException;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
/**
|
||||
|
@ -43,6 +44,7 @@ public class RepositoryManager {
|
|||
private static Map<Thread, String> clientNameMap = new WeakHashMap<>();
|
||||
|
||||
private File rootDirFile;
|
||||
private CommandWatcher commandWatcher;
|
||||
private HashMap<String, Repository> repositoryMap; // maps name to Repository
|
||||
private ArrayList<RepositoryServerHandleImpl> handleList = new ArrayList<>();
|
||||
private UserManager userMgr;
|
||||
|
@ -92,6 +94,7 @@ public class RepositoryManager {
|
|||
* Dispose this repository manager and all repository instances
|
||||
*/
|
||||
public synchronized void dispose() {
|
||||
commandWatcher.dispose();
|
||||
Iterator<Repository> iter = repositoryMap.values().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Repository rep = iter.next();
|
||||
|
@ -101,6 +104,7 @@ public class RepositoryManager {
|
|||
|
||||
/**
|
||||
* Return repositories root directory
|
||||
* @return server root directory
|
||||
*/
|
||||
File getRootDir() {
|
||||
return rootDirFile;
|
||||
|
@ -111,14 +115,14 @@ public class RepositoryManager {
|
|||
* @param currentUser user creating the repository
|
||||
* @param name name of the repository
|
||||
* @return a new Repository
|
||||
* @throws DuplicateNameException if another repository exists with the
|
||||
* @throws DuplicateFileException if another repository exists with the
|
||||
* given name
|
||||
* @throws UserAccessException if the user does not exist in
|
||||
* the list of known users for this manager
|
||||
* @throws IOException if there was an error creating the repository
|
||||
*/
|
||||
public synchronized Repository createRepository(String currentUser, String name)
|
||||
throws IOException {
|
||||
throws IOException, DuplicateFileException {
|
||||
|
||||
if (isAnonymousUser(currentUser)) {
|
||||
throw new UserAccessException("Anonymous user not permitted to create repository");
|
||||
|
@ -179,7 +183,7 @@ public class RepositoryManager {
|
|||
* Delete a specified repository.
|
||||
* @param currentUser current user
|
||||
* @param name repository name
|
||||
* @throws IOException
|
||||
* @throws IOException if error occurs while removing repository
|
||||
*/
|
||||
public synchronized void deleteRepository(String currentUser, String name) throws IOException {
|
||||
|
||||
|
@ -229,6 +233,13 @@ public class RepositoryManager {
|
|||
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
|
||||
* 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
|
||||
* we should not reveal to currentUser.
|
||||
*/
|
||||
public synchronized String[] getAllUsers(String currentUser) throws IOException {
|
||||
public synchronized String[] getAllUsers(String currentUser) {
|
||||
if (isAnonymousUser(currentUser)) {
|
||||
return new String[0];
|
||||
}
|
||||
try {
|
||||
return userMgr.getUsers();
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.error("Error while accessing user list: " + e.getMessage());
|
||||
throw new IOException("Failed to read user list");
|
||||
}
|
||||
return userMgr.getUsers();
|
||||
}
|
||||
|
||||
public UserManager getUserManager() {
|
||||
|
@ -256,7 +261,7 @@ public class RepositoryManager {
|
|||
/**
|
||||
* Verify that the specified currentUser is a known user
|
||||
* @param currentUser current user
|
||||
* @throws UserAccessException
|
||||
* @throws UserAccessException specified user is not valid
|
||||
*/
|
||||
private void validateUser(String currentUser) throws UserAccessException {
|
||||
if (!userMgr.isValidUser(currentUser)) {
|
||||
|
@ -266,7 +271,7 @@ public class RepositoryManager {
|
|||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -445,44 +465,84 @@ public class RepositoryManager {
|
|||
* 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
|
||||
* @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) {
|
||||
String[] names = RepositoryManager.getRepositoryNames(repositoriesRootDir);
|
||||
System.out.println("\nRepositories:");
|
||||
if (names.length == 0) {
|
||||
System.out.println(" <No repositories have been created>");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
for (String name : names) {
|
||||
File repoDir = new File(repositoriesRootDir, NamingUtilities.mangle(name));
|
||||
String rootPath = repoDir.getAbsolutePath();
|
||||
boolean isIndexed = IndexedLocalFileSystem.isIndexed(rootPath);
|
||||
String type;
|
||||
if (isIndexed || IndexedLocalFileSystem.hasIndexedStructure(rootPath)) {
|
||||
type = "Indexed Filesystem";
|
||||
try {
|
||||
int indexVersion = IndexedLocalFileSystem.readIndexVersion(rootPath);
|
||||
if (indexVersion == IndexedLocalFileSystem.LATEST_INDEX_VERSION) {
|
||||
type = null;
|
||||
}
|
||||
else {
|
||||
type += " (V" + indexVersion + ")";
|
||||
}
|
||||
|
||||
for (String name : names) {
|
||||
File repoDir = new File(repositoriesRootDir, NamingUtilities.mangle(name));
|
||||
String rootPath = repoDir.getAbsolutePath();
|
||||
boolean isIndexed = IndexedLocalFileSystem.isIndexed(rootPath);
|
||||
String type;
|
||||
if (isIndexed || IndexedLocalFileSystem.hasIndexedStructure(rootPath)) {
|
||||
type = "Indexed Filesystem";
|
||||
try {
|
||||
int indexVersion = IndexedLocalFileSystem.readIndexVersion(rootPath);
|
||||
if (indexVersion == IndexedLocalFileSystem.LATEST_INDEX_VERSION) {
|
||||
type = null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
type += "(unknown)";
|
||||
else {
|
||||
type += " (V" + indexVersion + ")";
|
||||
}
|
||||
}
|
||||
else {
|
||||
type = "Mangled Filesystem";
|
||||
catch (IOException e) {
|
||||
type += "(unknown)";
|
||||
}
|
||||
}
|
||||
else {
|
||||
type = "Mangled Filesystem";
|
||||
}
|
||||
|
||||
System.out.println(" " + name + (type == null ? "" : (" - uses " + type)));
|
||||
System.out.println(" " + name + (type == null ? "" : (" - uses " + type)));
|
||||
|
||||
if (includeUserAccessDetails) {
|
||||
Repository.listUserPermissions(repoDir, " ");
|
||||
if (includeUserAccessDetails) {
|
||||
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_ALL_COMMAND = "-migrate-all";
|
||||
|
||||
private boolean propertyUsed = false;
|
||||
|
||||
/**
|
||||
* Main method for launching the ServerAdmin Application via GhidraLauncher.
|
||||
* 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++]
|
||||
: System.getProperty(CONFIG_FILE_PROPERTY);
|
||||
|
||||
File serverDir = getServerDirFromConfig(configFilePath);
|
||||
if (serverDir == null || (args.length - ix) == 0) {
|
||||
System.out.println("server.conf: " + configFilePath);
|
||||
|
||||
File serverRootDir = getServerDirFromConfig(configFilePath);
|
||||
if (serverRootDir == null || (args.length - ix) == 0) {
|
||||
displayUsage("");
|
||||
System.exit(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
serverDir = serverDir.getCanonicalFile();
|
||||
serverRootDir = serverRootDir.getCanonicalFile();
|
||||
}
|
||||
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.out.println("Using server directory: " + serverDir);
|
||||
System.out.println("Using server directory: " + serverRootDir);
|
||||
|
||||
File userFile = new File(serverDir, UserManager.USER_PASSWORD_FILE);
|
||||
if (!serverDir.isDirectory() || !userFile.isFile()) {
|
||||
File userFile = new File(serverRootDir, UserManager.USER_PASSWORD_FILE);
|
||||
if (!serverRootDir.isDirectory() || !userFile.isFile()) {
|
||||
System.err.println("Invalid Ghidra server directory!");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
File cmdDir = new File(serverDir, UserAdmin.ADMIN_CMD_DIR);
|
||||
File cmdDir = CommandProcessor.getCommandDir(serverRootDir);
|
||||
if (!cmdDir.isDirectory() || !cmdDir.canWrite()) {
|
||||
System.err.println("Insufficient privilege or server not started!");
|
||||
System.exit(-1);
|
||||
|
@ -117,6 +117,8 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||
|
||||
// Process command line
|
||||
boolean listRepositories = false;
|
||||
boolean listAllUserPermissions = false;
|
||||
Set<String> listUsernameSet = new HashSet<>();
|
||||
boolean listUsers = false;
|
||||
boolean migrationConfirmed = false;
|
||||
boolean migrationAbort = false;
|
||||
|
@ -125,98 +127,122 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||
for (; ix < args.length; ix += cmdLen) {
|
||||
boolean queueCmd = true;
|
||||
String pwdHash = null;
|
||||
if (UserAdmin.ADD_USER_COMMAND.equals(args[ix])) { // add user
|
||||
cmdLen = 2;
|
||||
validateSID(args, ix + 1);
|
||||
if (hasOptionalArg(args, ix + 2, UserAdmin.PASSWORD_OPTION)) {
|
||||
++cmdLen;
|
||||
pwdHash = promptForPasswordAndGetSaltedHash(args[ix + 1]);
|
||||
}
|
||||
}
|
||||
else if (UserAdmin.REMOVE_USER_COMMAND.equals(args[ix])) { // remove user
|
||||
cmdLen = 2;
|
||||
validateSID(args, ix + 1);
|
||||
}
|
||||
else if (UserAdmin.RESET_USER_COMMAND.equals(args[ix])) { // reset user
|
||||
cmdLen = 2;
|
||||
validateSID(args, ix + 1);
|
||||
if (hasOptionalArg(args, ix + 2, UserAdmin.PASSWORD_OPTION)) {
|
||||
++cmdLen;
|
||||
pwdHash = promptForPasswordAndGetSaltedHash(args[ix + 1]);
|
||||
}
|
||||
}
|
||||
else if (UserAdmin.SET_USER_DN_COMMAND.equals(args[ix])) { // set/add user with DN for PKI
|
||||
cmdLen = 3;
|
||||
validateSID(args, ix + 1);
|
||||
validateDN(args, ix + 2);
|
||||
}
|
||||
else if (UserAdmin.SET_ADMIN_COMMAND.equals(args[ix])) { // set/add repository admin
|
||||
cmdLen = 3;
|
||||
validateSID(args, ix + 1);
|
||||
validateRepName(args, ix + 2, serverDir);
|
||||
}
|
||||
else if (LIST_COMMAND.equals(args[ix])) { // list repositories
|
||||
cmdLen = 1;
|
||||
queueCmd = false;
|
||||
listRepositories = true;
|
||||
}
|
||||
else if (USERS_COMMAND.equals(args[ix])) { // list users (also affects listRepositories)
|
||||
cmdLen = 1;
|
||||
queueCmd = false;
|
||||
listUsers = true;
|
||||
}
|
||||
else if (MIGRATE_ALL_COMMAND.equals(args[ix])) { // list repositories
|
||||
cmdLen = 1;
|
||||
queueCmd = false;
|
||||
if (!migrationConfirmed && !confirmMigration()) {
|
||||
migrationAbort = true;
|
||||
}
|
||||
migrationConfirmed = true;
|
||||
if (!migrationAbort) {
|
||||
RepositoryManager.markAllRepositoriesForIndexMigration(serverDir);
|
||||
}
|
||||
}
|
||||
else if (MIGRATE_COMMAND.equals(args[ix])) { // list repositories
|
||||
cmdLen = 2;
|
||||
queueCmd = false;
|
||||
if (ix == (args.length - 1)) {
|
||||
System.err.println("Missing " + MIGRATE_COMMAND + " repository name argument");
|
||||
}
|
||||
else {
|
||||
String repositoryName = args[ix + 1];
|
||||
switch (args[ix]) {
|
||||
case CommandProcessor.ADD_USER_COMMAND: // add user
|
||||
cmdLen = 2;
|
||||
validateSID(args, ix + 1);
|
||||
if (hasOptionalArg(args, ix + 2, CommandProcessor.PASSWORD_OPTION)) {
|
||||
++cmdLen;
|
||||
pwdHash = promptForPasswordAndGetSaltedHash(args[ix + 1]);
|
||||
}
|
||||
break;
|
||||
case CommandProcessor.REMOVE_USER_COMMAND: // remove user
|
||||
cmdLen = 2;
|
||||
validateSID(args, ix + 1);
|
||||
break;
|
||||
case CommandProcessor.RESET_USER_COMMAND: // reset user
|
||||
cmdLen = 2;
|
||||
validateSID(args, ix + 1);
|
||||
if (hasOptionalArg(args, ix + 2, CommandProcessor.PASSWORD_OPTION)) {
|
||||
++cmdLen;
|
||||
pwdHash = promptForPasswordAndGetSaltedHash(args[ix + 1]);
|
||||
}
|
||||
break;
|
||||
case CommandProcessor.SET_USER_DN_COMMAND: // set/add user with DN for PKI
|
||||
cmdLen = 3;
|
||||
validateSID(args, ix + 1);
|
||||
validateDN(args, ix + 2);
|
||||
break;
|
||||
case CommandProcessor.GRANT_USER_COMMAND: // grant repository permission
|
||||
cmdLen = 4;
|
||||
validateSID(args, ix + 1);
|
||||
validatePermission(args, ix + 2);
|
||||
validateRepositoryName(args, ix + 3, serverRootDir);
|
||||
break;
|
||||
case CommandProcessor.REVOKE_USER_COMMAND: // revoke repository access
|
||||
cmdLen = 3;
|
||||
validateSID(args, ix + 1);
|
||||
validateRepositoryName(args, ix + 2, serverRootDir);
|
||||
break;
|
||||
case LIST_COMMAND: // list repositories;
|
||||
queueCmd = false;
|
||||
listRepositories = true;
|
||||
boolean hasUsernames = false;
|
||||
while ((ix + 1) < args.length && !args[ix + 1].startsWith("-")) {
|
||||
String sid = args[++ix]; // consume next arg as user sid
|
||||
validateSID(sid);
|
||||
listUsernameSet.add(sid);
|
||||
hasUsernames = true;
|
||||
}
|
||||
if (!hasUsernames && (ix + 1) < args.length && "--users".equals(args[ix + 1])) {
|
||||
listAllUserPermissions = true;
|
||||
++ix;
|
||||
}
|
||||
break;
|
||||
case USERS_COMMAND: // list users (also affects listRepositories)
|
||||
queueCmd = false;
|
||||
listUsers = true;
|
||||
listUsernameSet.clear();
|
||||
break;
|
||||
case MIGRATE_ALL_COMMAND: // list repositories;
|
||||
queueCmd = false;
|
||||
if (!migrationConfirmed && !confirmMigration()) {
|
||||
migrationAbort = true;
|
||||
}
|
||||
migrationConfirmed = true;
|
||||
if (!migrationAbort) {
|
||||
Repository.markRepositoryForIndexMigration(serverDir, repositoryName,
|
||||
false);
|
||||
RepositoryManager.markAllRepositoriesForIndexMigration(serverRootDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
displayUsage("Invalid usage!");
|
||||
System.exit(-1);
|
||||
break;
|
||||
case MIGRATE_COMMAND: // list repositories
|
||||
queueCmd = false;
|
||||
if (ix == (args.length - 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) {
|
||||
addCommand(cmdList, args, ix, cmdLen, pwdHash);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
UserAdmin.writeCommands(cmdList, cmdDir);
|
||||
if (cmdList.size() != 0) {
|
||||
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) {
|
||||
UserManager.listUsers(serverDir);
|
||||
UserManager.listUsers(serverRootDir);
|
||||
}
|
||||
if (listRepositories) {
|
||||
RepositoryManager.listRepositories(serverDir, listUsers);
|
||||
if (listUsernameSet.isEmpty()) {
|
||||
RepositoryManager.listRepositories(serverRootDir, listAllUserPermissions);
|
||||
}
|
||||
else {
|
||||
RepositoryManager.listRepositories(serverRootDir, listUsernameSet);
|
||||
}
|
||||
}
|
||||
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'
|
||||
* @param args
|
||||
* @param args command args
|
||||
* @param i argument index
|
||||
*/
|
||||
private void validateDN(String[] args, int i) {
|
||||
|
@ -376,43 +402,58 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||
args[i] = "\"" + x500User.getName() + "\"";
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(UserAdmin.class, "Invalid DN: " + dn);
|
||||
Msg.error(CommandProcessor.class, "Invalid DN: " + dn);
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate username/sid
|
||||
* @param args
|
||||
* @param args command args
|
||||
* @param i argument index
|
||||
*/
|
||||
private void validateSID(String[] args, int i) {
|
||||
if (args.length < (i + 1)) {
|
||||
displayUsage("Invalid usage!");
|
||||
displayUsage("Invalid usage, expected username/sid");
|
||||
System.exit(-1);
|
||||
}
|
||||
String sid = args[i];
|
||||
validateSID(args[i]);
|
||||
}
|
||||
|
||||
private void validateSID(String sid) {
|
||||
if (!UserManager.isValidUserName(sid)) {
|
||||
Msg.error(UserAdmin.class, "Invalid username/sid: " + sid);
|
||||
displayUsage("Invalid username/sid: " + sid);
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate repository name
|
||||
* @param args
|
||||
* Validate repository permission arg (repository name to follow)
|
||||
* @param args command args
|
||||
* @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)) {
|
||||
displayUsage("Invalid usage!");
|
||||
displayUsage("Invalid usage, expected repository name");
|
||||
System.exit(-1);
|
||||
}
|
||||
String repName = args[i];
|
||||
File f = new File(rootDirFile, NamingUtilities.mangle(repName));
|
||||
if (!f.isDirectory()) {
|
||||
Msg.error(UserAdmin.class, "Repository not found: " + repName);
|
||||
Msg.error(CommandProcessor.class, "Repository not found: " + repName);
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
|
@ -483,7 +524,7 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||
|
||||
/**
|
||||
* Display an optional message followed by usage syntax.
|
||||
* @param msg
|
||||
* @param msg optional error message to proceed usage display
|
||||
*/
|
||||
private void displayUsage(String msg) {
|
||||
if (msg != null) {
|
||||
|
@ -496,21 +537,27 @@ public class ServerAdmin implements GhidraLaunchable {
|
|||
System.err.println("\nSupported commands:");
|
||||
System.err.println(" -add <sid> [--p]");
|
||||
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 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 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(
|
||||
" When PKI authentication is used, add the specified X500 Distinguished Name for a user");
|
||||
System.err.println(" -admin <sid> \"<repository-name>\"");
|
||||
System.err.println(
|
||||
" Grant ADMIN privilege to the specified user with the specified repository");
|
||||
System.err.println(" -list [-users]");
|
||||
System.err.println(" -list [--users]");
|
||||
System.err.println(
|
||||
" 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(" Output list of users to console which have server access");
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -67,11 +67,10 @@ public class UserManager {
|
|||
private LinkedHashMap<String, UserEntry> userList = new LinkedHashMap<>();
|
||||
private HashMap<X500Principal, UserEntry> dnLookupMap = new HashMap<>();
|
||||
private long lastUserListChange;
|
||||
private boolean userListUpdateInProgress = false;
|
||||
|
||||
/**
|
||||
* Construct server user manager
|
||||
* @param repositoryMgr repository manager (used for queued command processing)
|
||||
* @param repositoryMgr repository manager
|
||||
* @param enableLocalPasswords if true user passwords will be maintained
|
||||
* within local 'users' file
|
||||
* @param defaultPasswordExpirationDays password expiration in days when
|
||||
|
@ -90,8 +89,8 @@ public class UserManager {
|
|||
|
||||
userFile = new File(repositoryMgr.getRootDir(), USER_PASSWORD_FILE);
|
||||
try {
|
||||
// everything must be constructed before processing commands
|
||||
updateUserList(false);
|
||||
readUserListIfNeeded();
|
||||
clearExpiredPasswords();
|
||||
log.info("User file contains " + userList.size() + " entries");
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
|
@ -145,14 +144,14 @@ public class UserManager {
|
|||
/**
|
||||
* Get the SSH public key file for the specified user
|
||||
* if it exists.
|
||||
* @param user
|
||||
* @param username user name/SID
|
||||
* @return SSH public key file or null if key unavailable
|
||||
*/
|
||||
public File getSSHPubKeyFile(String user) {
|
||||
if (!userList.containsKey(user)) {
|
||||
public File getSSHPubKeyFile(String username) {
|
||||
if (!userList.containsKey(username)) {
|
||||
return null;
|
||||
}
|
||||
File f = new File(sshDir, user + SSH_PUBKEY_EXT);
|
||||
File f = new File(sshDir, username + SSH_PUBKEY_EXT);
|
||||
if (f.isFile()) {
|
||||
return f;
|
||||
}
|
||||
|
@ -163,29 +162,31 @@ public class UserManager {
|
|||
* Add a user.
|
||||
* @param username user name/SID
|
||||
* @param passwordHash MD5 hash of initial password or null if explicit password reset required
|
||||
* @param dn X500 distinguished name for user (may be null)
|
||||
* @param x500User X500 user name (may be null)
|
||||
* @throws DuplicateNameException if username already exists
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
private synchronized void addUser(String username, char[] passwordHash, X500Principal x500User)
|
||||
private void addUser(String username, char[] passwordHash, X500Principal x500User)
|
||||
throws DuplicateNameException, IOException {
|
||||
if (username == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
updateUserList(true);
|
||||
if (userList.containsKey(username)) {
|
||||
throw new DuplicateNameException("User " + username + " already exists");
|
||||
synchronized (repositoryMgr) {
|
||||
if (userList.containsKey(username)) {
|
||||
throw new DuplicateNameException("User " + username + " already exists");
|
||||
}
|
||||
UserEntry entry = new UserEntry();
|
||||
entry.username = username;
|
||||
entry.passwordHash = passwordHash;
|
||||
entry.passwordTime = (new Date()).getTime();
|
||||
entry.x500User = x500User;
|
||||
userList.put(username, entry);
|
||||
if (x500User != null) {
|
||||
dnLookupMap.put(x500User, entry);
|
||||
}
|
||||
writeUserList();
|
||||
log.info("User '" + username + "' added");
|
||||
}
|
||||
UserEntry entry = new UserEntry();
|
||||
entry.username = username;
|
||||
entry.passwordHash = passwordHash;
|
||||
entry.passwordTime = (new Date()).getTime();
|
||||
entry.x500User = x500User;
|
||||
userList.put(username, entry);
|
||||
if (x500User != null) {
|
||||
dnLookupMap.put(x500User, entry);
|
||||
}
|
||||
writeUserList();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,15 +231,15 @@ public class UserManager {
|
|||
* Returns the X500 distinguished name for the specified user.
|
||||
* @param username user name/SID
|
||||
* @return X500 distinguished name
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized X500Principal getDistinguishedName(String username) throws IOException {
|
||||
updateUserList(true);
|
||||
UserEntry entry = userList.get(username);
|
||||
if (entry != null) {
|
||||
return entry.x500User;
|
||||
public X500Principal getDistinguishedName(String username) {
|
||||
synchronized (repositoryMgr) {
|
||||
UserEntry entry = userList.get(username);
|
||||
if (entry != null) {
|
||||
return entry.x500User;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -246,11 +247,11 @@ public class UserManager {
|
|||
* @param x500User a user's X500 distinguished name
|
||||
* @return username or null if not found
|
||||
*/
|
||||
public synchronized String getUserByDistinguishedName(X500Principal x500User)
|
||||
throws IOException {
|
||||
updateUserList(true);
|
||||
UserEntry entry = dnLookupMap.get(x500User);
|
||||
return entry != null ? entry.username : null;
|
||||
public String getUserByDistinguishedName(X500Principal x500User) {
|
||||
synchronized (repositoryMgr) {
|
||||
UserEntry entry = dnLookupMap.get(x500User);
|
||||
return entry != null ? entry.username : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -258,28 +259,29 @@ public class UserManager {
|
|||
* @param username user name/SID
|
||||
* @param x500User X500 distinguished name
|
||||
* @return true if successful, false if user not found
|
||||
* @throws IOException
|
||||
* @throws IOException if error occurs while updating user file
|
||||
*/
|
||||
public synchronized boolean setDistinguishedName(String username, X500Principal x500User)
|
||||
public boolean setDistinguishedName(String username, X500Principal x500User)
|
||||
throws IOException {
|
||||
updateUserList(true);
|
||||
UserEntry oldEntry = userList.remove(username);
|
||||
if (oldEntry != null) {
|
||||
if (oldEntry.x500User != null) {
|
||||
dnLookupMap.remove(oldEntry.x500User);
|
||||
synchronized (repositoryMgr) {
|
||||
UserEntry oldEntry = userList.remove(username);
|
||||
if (oldEntry != null) {
|
||||
if (oldEntry.x500User != null) {
|
||||
dnLookupMap.remove(oldEntry.x500User);
|
||||
}
|
||||
UserEntry entry = new UserEntry();
|
||||
entry.username = username;
|
||||
entry.passwordHash = oldEntry.passwordHash;
|
||||
entry.x500User = x500User;
|
||||
userList.put(username, entry);
|
||||
if (x500User != null) {
|
||||
dnLookupMap.put(x500User, entry);
|
||||
}
|
||||
writeUserList();
|
||||
return true;
|
||||
}
|
||||
UserEntry entry = new UserEntry();
|
||||
entry.username = username;
|
||||
entry.passwordHash = oldEntry.passwordHash;
|
||||
entry.x500User = x500User;
|
||||
userList.put(username, entry);
|
||||
if (x500User != null) {
|
||||
dnLookupMap.put(x500User, entry);
|
||||
}
|
||||
writeUserList();
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkValidPasswordHash(char[] saltedPasswordHash) throws IOException {
|
||||
|
@ -332,9 +334,9 @@ public class UserManager {
|
|||
* @param saltedSHA256PasswordHash 4-character salt followed by 64-hex digit SHA256 password hash for new password
|
||||
* @param isTemporary if true password will be set to expire
|
||||
* @return true if successful, false if user not found
|
||||
* @throws IOException
|
||||
* @throws IOException if error occurs while updating user file
|
||||
*/
|
||||
public synchronized boolean setPassword(String username, char[] saltedSHA256PasswordHash,
|
||||
public boolean setPassword(String username, char[] saltedSHA256PasswordHash,
|
||||
boolean isTemporary) throws IOException {
|
||||
if (!enableLocalPasswords) {
|
||||
throw new IOException("Local passwords are not used");
|
||||
|
@ -342,31 +344,36 @@ public class UserManager {
|
|||
|
||||
checkValidPasswordHash(saltedSHA256PasswordHash);
|
||||
|
||||
updateUserList(true);
|
||||
UserEntry oldEntry = userList.remove(username);
|
||||
if (oldEntry != null) {
|
||||
UserEntry entry = new UserEntry();
|
||||
entry.username = username;
|
||||
entry.passwordHash = saltedSHA256PasswordHash;
|
||||
entry.passwordTime = isTemporary ? (new Date()).getTime() : NO_EXPIRATION;
|
||||
entry.x500User = oldEntry.x500User;
|
||||
userList.put(username, entry);
|
||||
if (entry.x500User != null) {
|
||||
dnLookupMap.put(entry.x500User, entry);
|
||||
synchronized (repositoryMgr) {
|
||||
UserEntry oldEntry = userList.remove(username);
|
||||
if (oldEntry != null) {
|
||||
UserEntry entry = new UserEntry();
|
||||
entry.username = username;
|
||||
entry.passwordHash = saltedSHA256PasswordHash;
|
||||
entry.passwordTime = isTemporary ? (new Date()).getTime() : NO_EXPIRATION;
|
||||
entry.x500User = oldEntry.x500User;
|
||||
userList.put(username, entry);
|
||||
if (entry.x500User != null) {
|
||||
dnLookupMap.put(entry.x500User, entry);
|
||||
}
|
||||
writeUserList();
|
||||
return true;
|
||||
}
|
||||
writeUserList();
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if local passwords are in use and can be changed by the user.
|
||||
* @see #setPassword(String, char[])
|
||||
* See {@link #setPassword(String, char[], boolean)}.
|
||||
* @param username user name/SID
|
||||
* @return true if password change permitted, else false
|
||||
*/
|
||||
public boolean canSetPassword(String username) {
|
||||
UserEntry userEntry = userList.get(username);
|
||||
return (enableLocalPasswords && userEntry != null && userEntry.passwordHash != null);
|
||||
synchronized (repositoryMgr) {
|
||||
UserEntry userEntry = userList.get(username);
|
||||
return (enableLocalPasswords && userEntry != null && userEntry.passwordHash != null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -375,18 +382,17 @@ public class UserManager {
|
|||
* @param username user name
|
||||
* @return time until expiration or -1 if it will not expire
|
||||
*/
|
||||
public long getPasswordExpiration(String username) throws IOException {
|
||||
updateUserList(true);
|
||||
public long getPasswordExpiration(String username) {
|
||||
synchronized (repositoryMgr) {
|
||||
UserEntry userEntry = userList.get(username);
|
||||
|
||||
UserEntry userEntry = userList.get(username);
|
||||
|
||||
// indicate immediate expiration for users with short hash (non salted SHA-256)
|
||||
if (userEntry != null && userEntry.passwordHash != null &&
|
||||
userEntry.passwordHash.length != HashUtilities.SHA256_SALTED_HASH_LENGTH) {
|
||||
return 0;
|
||||
// indicate immediate expiration for users with short hash (non salted SHA-256)
|
||||
if (userEntry != null && userEntry.passwordHash != null &&
|
||||
userEntry.passwordHash.length != HashUtilities.SHA256_SALTED_HASH_LENGTH) {
|
||||
return 0;
|
||||
}
|
||||
return getPasswordExpiration(userEntry);
|
||||
}
|
||||
|
||||
return getPasswordExpiration(userEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -415,10 +421,10 @@ public class UserManager {
|
|||
|
||||
/**
|
||||
* Reset the local password to the 'changeme' for the specified user.
|
||||
* @param username
|
||||
* @param username user name/SID
|
||||
* @param saltedPasswordHash optional user password hash (may be null)
|
||||
* @return true if password updated successfully.
|
||||
* @throws IOException
|
||||
* @return true if password updated successfully, else false if local passwords are not used.
|
||||
* @throws IOException if error occurs while updating user file
|
||||
*/
|
||||
public boolean resetPassword(String username, char[] saltedPasswordHash) throws IOException {
|
||||
if (!enableLocalPasswords) {
|
||||
|
@ -435,62 +441,46 @@ public class UserManager {
|
|||
/**
|
||||
* Remove the specified user from the server access list
|
||||
* @param username user name/SID
|
||||
* @throws IOException
|
||||
* @return true if existing user removed, else false if not found
|
||||
* @throws IOException if error occurs while updating user file
|
||||
*/
|
||||
public synchronized void removeUser(String username) throws IOException {
|
||||
updateUserList(true);
|
||||
UserEntry oldEntry = userList.remove(username);
|
||||
if (oldEntry != null) {
|
||||
if (oldEntry.x500User != null) {
|
||||
dnLookupMap.remove(oldEntry.x500User);
|
||||
public boolean removeUser(String username) throws IOException {
|
||||
synchronized (repositoryMgr) {
|
||||
UserEntry oldEntry = userList.remove(username);
|
||||
if (oldEntry != null) {
|
||||
if (oldEntry.x500User != null) {
|
||||
dnLookupMap.remove(oldEntry.x500User);
|
||||
}
|
||||
writeUserList();
|
||||
repositoryMgr.userRemoved(username);
|
||||
log.info("User removed from server: " + username);
|
||||
return true;
|
||||
}
|
||||
writeUserList();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of all users known to server.
|
||||
* @return list of known users
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized String[] getUsers() throws IOException {
|
||||
updateUserList(true);
|
||||
String[] names = new String[userList.size()];
|
||||
Iterator<String> iter = userList.keySet().iterator();
|
||||
int i = 0;
|
||||
while (iter.hasNext()) {
|
||||
names[i++] = iter.next();
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the server's user list and process any pending UserAdmin commands.
|
||||
* @param processCmds TODO
|
||||
* @throws IOException
|
||||
*/
|
||||
synchronized void updateUserList(boolean processCmds) throws IOException {
|
||||
if (userListUpdateInProgress) {
|
||||
return;
|
||||
}
|
||||
userListUpdateInProgress = true;
|
||||
try {
|
||||
readUserListIfNeeded();
|
||||
clearExpiredPasswords();
|
||||
if (processCmds) {
|
||||
UserAdmin.processCommands(repositoryMgr);
|
||||
public String[] getUsers() {
|
||||
synchronized (repositoryMgr) {
|
||||
String[] names = new String[userList.size()];
|
||||
Iterator<String> iter = userList.keySet().iterator();
|
||||
int i = 0;
|
||||
while (iter.hasNext()) {
|
||||
names[i++] = iter.next();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
userListUpdateInProgress = false;
|
||||
return names;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all local user passwords which have expired.
|
||||
* @throws IOException
|
||||
* @throws IOException if error occurs while updating user file
|
||||
*/
|
||||
private void clearExpiredPasswords() throws IOException {
|
||||
void clearExpiredPasswords() throws IOException {
|
||||
if (defaultPasswordExpirationMS == 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -498,7 +488,8 @@ public class UserManager {
|
|||
Iterator<UserEntry> it = userList.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
UserEntry entry = it.next();
|
||||
if (enableLocalPasswords && getPasswordExpiration(entry) == 0) {
|
||||
if (entry.passwordHash != null && enableLocalPasswords &&
|
||||
getPasswordExpiration(entry) == 0) {
|
||||
entry.passwordHash = null;
|
||||
entry.passwordTime = 0;
|
||||
dataChanged = true;
|
||||
|
@ -513,9 +504,9 @@ public class UserManager {
|
|||
/**
|
||||
* Read user data from file if the timestamp on the file has changed.
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws IOException if error occurs while updating user file
|
||||
*/
|
||||
private void readUserListIfNeeded() throws IOException {
|
||||
void readUserListIfNeeded() throws IOException {
|
||||
|
||||
long lastMod = userFile.lastModified();
|
||||
if (lastUserListChange == lastMod) {
|
||||
|
@ -627,7 +618,7 @@ public class UserManager {
|
|||
|
||||
/**
|
||||
* Write user data to file.
|
||||
* @throws IOException
|
||||
* @throws IOException if error occurs while updating user file
|
||||
*/
|
||||
private void writeUserList() throws IOException {
|
||||
try (BufferedWriter bw = new BufferedWriter(new FileWriter(userFile))) {
|
||||
|
@ -660,16 +651,12 @@ public class UserManager {
|
|||
/**
|
||||
* Returns true if the specified user is known to server.
|
||||
* @param username user name/SID
|
||||
* @return
|
||||
* @return true if user is known to server
|
||||
*/
|
||||
public synchronized boolean isValidUser(String username) {
|
||||
try {
|
||||
updateUserList(true);
|
||||
public boolean isValidUser(String username) {
|
||||
synchronized (repositoryMgr) {
|
||||
return userList.containsKey(username);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
return userList.containsKey(username);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -677,50 +664,54 @@ public class UserManager {
|
|||
* password set for the specified user.
|
||||
* @param username user name/SID
|
||||
* @param password password data
|
||||
* @throws IOException
|
||||
* @throws IOException if error occurs while updating user file
|
||||
* @throws FailedLoginException if authentication fails
|
||||
*/
|
||||
public synchronized void authenticateUser(String username, char[] password)
|
||||
public void authenticateUser(String username, char[] password)
|
||||
throws IOException, FailedLoginException {
|
||||
if (username == null || password == null) {
|
||||
throw new FailedLoginException("Invalid authentication data");
|
||||
}
|
||||
updateUserList(true);
|
||||
UserEntry entry = userList.get(username);
|
||||
if (entry == null) {
|
||||
throw new FailedLoginException("Unknown user: " + username);
|
||||
}
|
||||
|
||||
if (entry.passwordHash == null ||
|
||||
entry.passwordHash.length < HashUtilities.MD5_UNSALTED_HASH_LENGTH) {
|
||||
throw new FailedLoginException("User password not set, must be reset");
|
||||
}
|
||||
|
||||
// Support deprecated unsalted hash
|
||||
if (entry.passwordHash.length == HashUtilities.MD5_UNSALTED_HASH_LENGTH && Arrays.equals(
|
||||
HashUtilities.getHash(HashUtilities.MD5_ALGORITHM, password), entry.passwordHash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
char[] salt = new char[HashUtilities.SALT_LENGTH];
|
||||
System.arraycopy(entry.passwordHash, 0, salt, 0, HashUtilities.SALT_LENGTH);
|
||||
|
||||
if (entry.passwordHash.length == HashUtilities.MD5_SALTED_HASH_LENGTH) {
|
||||
if (!Arrays.equals(
|
||||
HashUtilities.getSaltedHash(HashUtilities.MD5_ALGORITHM, salt, password),
|
||||
entry.passwordHash)) {
|
||||
throw new FailedLoginException("Incorrect password");
|
||||
synchronized (repositoryMgr) {
|
||||
clearExpiredPasswords();
|
||||
UserEntry entry = userList.get(username);
|
||||
if (entry == null) {
|
||||
throw new FailedLoginException("Unknown user: " + username);
|
||||
}
|
||||
}
|
||||
else if (entry.passwordHash.length == HashUtilities.SHA256_SALTED_HASH_LENGTH) {
|
||||
if (!Arrays.equals(
|
||||
HashUtilities.getSaltedHash(HashUtilities.SHA256_ALGORITHM, salt, password),
|
||||
entry.passwordHash)) {
|
||||
throw new FailedLoginException("Incorrect password");
|
||||
|
||||
if (entry.passwordHash == null ||
|
||||
entry.passwordHash.length < HashUtilities.MD5_UNSALTED_HASH_LENGTH) {
|
||||
throw new FailedLoginException("User password not set, must be reset");
|
||||
}
|
||||
|
||||
// Support deprecated unsalted hash
|
||||
if (entry.passwordHash.length == HashUtilities.MD5_UNSALTED_HASH_LENGTH &&
|
||||
Arrays.equals(
|
||||
HashUtilities.getHash(HashUtilities.MD5_ALGORITHM, password),
|
||||
entry.passwordHash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
char[] salt = new char[HashUtilities.SALT_LENGTH];
|
||||
System.arraycopy(entry.passwordHash, 0, salt, 0, HashUtilities.SALT_LENGTH);
|
||||
|
||||
if (entry.passwordHash.length == HashUtilities.MD5_SALTED_HASH_LENGTH) {
|
||||
if (!Arrays.equals(
|
||||
HashUtilities.getSaltedHash(HashUtilities.MD5_ALGORITHM, salt, password),
|
||||
entry.passwordHash)) {
|
||||
throw new FailedLoginException("Incorrect password");
|
||||
}
|
||||
}
|
||||
else if (entry.passwordHash.length == HashUtilities.SHA256_SALTED_HASH_LENGTH) {
|
||||
if (!Arrays.equals(
|
||||
HashUtilities.getSaltedHash(HashUtilities.SHA256_ALGORITHM, salt, password),
|
||||
entry.passwordHash)) {
|
||||
throw new FailedLoginException("Incorrect password");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new FailedLoginException("User password not set, must be reset");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new FailedLoginException("User password not set, must be reset");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -760,7 +751,8 @@ public class UserManager {
|
|||
/*
|
||||
* Regex: matches if the entire string is alpha, digit, ".", "-", "_", fwd or back slash.
|
||||
*/
|
||||
private static final Pattern VALID_USERNAME_REGEX = Pattern.compile("[a-zA-Z0-9.\\-_/\\\\]+");
|
||||
private static final Pattern VALID_USERNAME_REGEX =
|
||||
Pattern.compile("[a-zA-Z][a-zA-Z0-9.\\-_/\\\\]*");
|
||||
|
||||
/**
|
||||
* Ensures a name only contains valid characters and meets length limitations.
|
||||
|
|
|
@ -30,6 +30,7 @@ typewriter {
|
|||
<LI><a href="#introduction">Introduction</a></LI>
|
||||
<LI><a href="#javaRuntime">Java Runtime Environment</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="#dnsNote">Note regarding use of DNS (name lookup service)</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_uninstall">Uninstall Service</a></LI>
|
||||
</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>
|
||||
<LI><a href="#linux_mac_scripts">Server Scripts</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="#pkiCertificateAuthorities">Managing PKI Certificate Authorities</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>
|
||||
<LI><a href="#checkinFailures">Failures Creating Repository Folders / Checking in Files</a></LI>
|
||||
<LI><a href="#connectErrors">Client/Server connection errors</a></LI>
|
||||
|
@ -64,6 +65,7 @@ typewriter {
|
|||
or svrUninstall.bat Error</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="#macDiskAccess">Mac OS - Service fails to start (macOS 10.14 Mojave and later)</a></LI>
|
||||
</UL>
|
||||
</UL>
|
||||
|
||||
|
@ -149,6 +151,16 @@ new installation. Using a non-default repositories directory outside your Ghidr
|
|||
will simplify the migration process.
|
||||
</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>)
|
||||
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
||||
<h2><a name="serverMemory">Server Memory Considerations</a></h2>
|
||||
|
@ -490,7 +502,10 @@ are not currently supported.
|
|||
|
||||
(<a href="#top">Back to Top</a>)
|
||||
<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>
|
||||
|
||||
|
@ -584,7 +599,15 @@ to run as <i>root</i> and monitor/manage the Java process.
|
|||
<P>
|
||||
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
|
||||
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>
|
||||
|
@ -592,12 +615,14 @@ to run as <i>root</i> and monitor/manage the Java process.
|
|||
|
||||
<PRE>
|
||||
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>]
|
||||
[-reset <user_sid> [--p]]
|
||||
[-dn <user_sid> "<user_dn>"]
|
||||
[-admin <user_sid> "<repository_name>"]
|
||||
[-list]
|
||||
[-list <user_sid> [<user_sid>...]]
|
||||
[-list [--users]]
|
||||
[-users]
|
||||
[-migrate-all]
|
||||
[-migrate "<repository_name>"]
|
||||
|
@ -626,11 +651,29 @@ to run as <i>root</i> and monitor/manage the Java process.
|
|||
svrAdmin -add mySID --p
|
||||
</PRE>
|
||||
</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>
|
||||
A user may be removed from the server 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
|
||||
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 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
|
||||
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>
|
||||
Example:
|
||||
<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
|
||||
certificate.
|
||||
</LI>
|
||||
<br>
|
||||
<LI><typewriter>-admin</typewriter> <b>(Adding a Repository Administrator)</b><br>
|
||||
If an existing repository administrator is unable to add another user as administrator, the
|
||||
server administrator may use this command to specify a new repository administrator.
|
||||
<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.
|
||||
<LI><typewriter>-list</typewriter> <b>(List All Repositories and/or User Permissions)</b><br>
|
||||
If the <i>--users</i> option is also present, the complete user access
|
||||
list will be included for each repository. Otherwise, command may be followed by one or user SIDs (separated by a space)
|
||||
which will limit the displayed repository list and access permissions to those users specified.
|
||||
<br><br>
|
||||
Example:
|
||||
<PRE>
|
||||
svrAdmin -list
|
||||
svrAdmin -list --users
|
||||
svrAdmin -list mySID
|
||||
</PRE>
|
||||
<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>
|
||||
Example:
|
||||
<PRE>
|
||||
|
@ -894,7 +930,7 @@ Please note that the Ghidra Server does not currently support Certificate Revoca
|
|||
<br>
|
||||
<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>
|
||||
or <a href="#linux_mac_uninstall">Linux/Mac-OSX</a>).</LI>
|
||||
or <a href="#linux_mac_uninstall">Linux/Mac OS</a>).</LI>
|
||||
<br>
|
||||
<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>
|
||||
|
@ -953,7 +989,7 @@ backup of your project or server repositories directory is highly recommended be
|
|||
|
||||
(<a href="#top">Back to Top</a>)
|
||||
<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>
|
||||
<P>
|
||||
|
@ -1028,7 +1064,7 @@ Expansion Daemon) which will satisfy the entropy demand needed by /dev/random.
|
|||
</P>
|
||||
|
||||
<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>
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue