diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/RepositoryManager.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/RepositoryManager.java index 2ac8c60f55..6d42577928 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/RepositoryManager.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/RepositoryManager.java @@ -55,7 +55,7 @@ public class RepositoryManager { * @param enableLocalPasswords if true user passwords will be maintained * within local 'users' file * @param defaultPasswordExpirationDays password expiration in days when - * local passwords are enabled + * local passwords are enabled (0 = no expiration) * @param anonymousAccessAllowed if true server permits anonymous access * to repositories. * @throws IOException if IO error occurs @@ -271,16 +271,16 @@ public class RepositoryManager { log.info("Known Repositories:"); String[] names = getRepositoryNames(rootDirFile); - for (int i = 0; i < names.length; i++) { - log.info(" " + names[i]); + for (String name : names) { + log.info(" " + name); } if (names.length == 0) { log.info(" "); } - for (int i = 0; i < names.length; i++) { - File f = new File(rootDirFile, NamingUtilities.mangle(names[i])); + for (String name2 : names) { + File f = new File(rootDirFile, NamingUtilities.mangle(name2)); if (!f.isDirectory()) { - log.error("Error while processing repository " + names[i] + + log.error("Error while processing repository " + name2 + ", directory not found: " + f); continue; } @@ -288,7 +288,7 @@ public class RepositoryManager { throw new IOException(f.getAbsolutePath() + " can not be written to"); } try { - Repository rep = new Repository(this, null, f, names[i]); + Repository rep = new Repository(this, null, f, name2); String name = rep.getName(); repositoryMap.put(name, rep); } @@ -296,7 +296,7 @@ public class RepositoryManager { // ignore } catch (Exception e) { - log.error("Error while processing repository " + names[i] + ", " + e.getMessage()); + log.error("Error while processing repository " + name2 + ", " + e.getMessage()); continue; } } @@ -363,16 +363,16 @@ public class RepositoryManager { return new String[0]; } ArrayList list = new ArrayList<>(dirList.length); - for (int i = 0; i < dirList.length; i++) { - if (!dirList[i].isDirectory() || - LocalFileSystem.isHiddenDirName(dirList[i].getName())) { + for (File element : dirList) { + if (!element.isDirectory() || + LocalFileSystem.isHiddenDirName(element.getName())) { continue; } - if (!NamingUtilities.isValidMangledName(dirList[i].getName())) { - log.warn("Ignoring repository directory with bad name: " + dirList[i]); + if (!NamingUtilities.isValidMangledName(element.getName())) { + log.warn("Ignoring repository directory with bad name: " + element); continue; } - list.add(NamingUtilities.demangle(dirList[i].getName())); + list.add(NamingUtilities.demangle(element.getName())); } Collections.sort(list); String[] names = new String[list.size()]; diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java index 66928c7c96..af8fadbec9 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/ServerAdmin.java @@ -16,8 +16,7 @@ package ghidra.server; import java.io.*; -import java.util.ArrayList; -import java.util.Properties; +import java.util.*; import javax.security.auth.x500.X500Principal; @@ -26,8 +25,7 @@ import ghidra.GhidraApplicationLayout; import ghidra.GhidraLaunchable; import ghidra.framework.Application; import ghidra.framework.ApplicationConfiguration; -import ghidra.util.Msg; -import ghidra.util.NamingUtilities; +import ghidra.util.*; public class ServerAdmin implements GhidraLaunchable { @@ -132,9 +130,14 @@ public class ServerAdmin implements GhidraLaunchable { int cmdLen = 1; for (; ix < args.length; ix += cmdLen) { boolean queueCmd = true; + char[] 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 = promptForPassword(args[ix + 1]); + } } else if (UserAdmin.REMOVE_USER_COMMAND.equals(args[ix])) { // remove user cmdLen = 2; @@ -143,6 +146,10 @@ public class ServerAdmin implements GhidraLaunchable { 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 = promptForPassword(args[ix + 1]); + } } else if (UserAdmin.SET_USER_DN_COMMAND.equals(args[ix])) { // set/add user with DN for PKI cmdLen = 3; @@ -198,7 +205,7 @@ public class ServerAdmin implements GhidraLaunchable { System.exit(-1); } if (queueCmd) { - addCommand(cmdList, args, ix, cmdLen); + addCommand(cmdList, args, ix, cmdLen, pwdHash); } } @@ -220,13 +227,106 @@ public class ServerAdmin implements GhidraLaunchable { System.out.println(); } + private char[] promptForPassword(String userSID) { + char[] pwd1 = null; + char[] pwd2 = null; + try { + while (true) { + System.out.println("Enter password for user '" + userSID + "'"); + pwd1 = getPassword("New password: ", true); + pwd2 = getPassword("Retype new password: ", false); + if (Arrays.equals(pwd1, pwd2)) { + break; + } + System.out.println("Password entries do not match! Please try again..."); + Arrays.fill(pwd1, (char) 0); + Arrays.fill(pwd2, (char) 0); + } + return HashUtilities.getSaltedHash(HashUtilities.SHA256_ALGORITHM, pwd1); + } + catch (IOException e) { + System.err.println("Password entry error: " + e.getMessage()); + System.exit(-1); + return null; + } + finally { + if (pwd1 != null) { + Arrays.fill(pwd1, (char) 0); + } + if (pwd2 != null) { + Arrays.fill(pwd2, (char) 0); + } + } + } + + private char[] getPassword(String prompt, boolean echoWarn) throws IOException { + + boolean success = false; + char[] password = null; + int c; + try { + Console cons = System.console(); + if (cons != null) { + password = cons.readPassword(prompt); + } + else { + if (echoWarn) { + System.out.println("*** WARNING! Password entry will NOT be masked ***"); + } + + System.out.print(prompt); + + while (true) { + c = System.in.read(); + if (c <= 0 || (Character.isWhitespace((char) c) && c != ' ')) { + break; + } + if (password == null) { + password = new char[1]; + } + else { + char[] newPass = new char[password.length + 1]; + for (int i = 0; i < password.length; i++) { + newPass[i] = password[i]; + password[i] = 0; + } + password = newPass; + } + password[password.length - 1] = (char) c; + } + } + success = true; + return password; + + } + finally { + if (!success && password != null) { + Arrays.fill(password, (char) 0); + } + } + } + /** - * @param serverDir - * @param args - * @param i + * Determine if option specified as args[argOffset] + * @param args command line args + * @param argOffset index within args array of option + * @param option option string + * @return true if option specified + */ + private boolean hasOptionalArg(String[] args, int argOffset, String option) { + return (argOffset < args.length && args[argOffset].contentEquals(option)); + } + + /** + * Add command args as next command in cmdList. + * @param cmdList command list + * @param args command line args + * @param argOffset args index of command start + * @param argCnt number of args to consume + * @param pwdHash optional password has to append to end of command */ private static void addCommand(ArrayList cmdList, String[] args, int argOffset, - int argCnt) { + int argCnt, char[] pwdHash) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < argCnt; i++) { if (i > 0) { @@ -234,6 +334,10 @@ public class ServerAdmin implements GhidraLaunchable { } buf.append(args[argOffset + i]); } + if (pwdHash != null) { + buf.append(' '); + buf.append(pwdHash); + } cmdList.add(buf.toString()); } @@ -376,12 +480,14 @@ public class ServerAdmin implements GhidraLaunchable { (invocationName != null ? invocationName : "java " + UserAdmin.class.getName()) + (propertyUsed ? "" : " ") + " [] [] ..."); System.err.println("\nSupported commands:"); - System.err.println(" -add "); - System.err.println(" Add a new user to the server identified by their sid identifier"); + System.err.println(" -add [--p]"); + System.err.println( + " Add a new user to the server identified by their sid identifier [-p prompt for password]"); System.err.println(" -remove "); System.err.println(" Remove the specified user from the server's user list"); - System.err.println(" -reset "); - System.err.println(" Reset the specified user's server login password"); + System.err.println(" -reset [--p]"); + System.err.println( + " Reset the specified user's server login password [-p prompt for password]"); System.err.println(" -dn \"\""); System.err.println( " When PKI authentication is used, add the specified X500 Distinguished Name for a user"); diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserAdmin.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserAdmin.java index a8c6fadc16..695819bd80 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserAdmin.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserAdmin.java @@ -42,6 +42,8 @@ public class UserAdmin { 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"; @@ -116,8 +118,12 @@ public class UserAdmin { 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); + userMgr.addUser(sid, pwdHash); log.info("User '" + sid + "' added"); } catch (DuplicateNameException e) { @@ -131,9 +137,16 @@ public class UserAdmin { } else if (RESET_USER_COMMAND.equals(args[0])) { // reset user String sid = args[1]; - if (!userMgr.resetPassword(sid)) { + 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"); } diff --git a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java index 937b6b6fd2..eab1135db9 100644 --- a/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java +++ b/Ghidra/Features/GhidraServer/src/main/java/ghidra/server/UserManager.java @@ -75,7 +75,7 @@ public class UserManager { * @param enableLocalPasswords if true user passwords will be maintained * within local 'users' file * @param defaultPasswordExpirationDays password expiration in days when - * local passwords are enabled + * local passwords are enabled (0 = no expiration) */ UserManager(RepositoryManager repositoryMgr, boolean enableLocalPasswords, int defaultPasswordExpirationDays) { @@ -195,7 +195,22 @@ public class UserManager { * @throws IOException if IO error occurs */ public void addUser(String username) throws DuplicateNameException, IOException { - addUser(username, null); + addUser(username, (char[]) null); + } + + /** + * Add a user with optional salted password hash. + * @param username user name/SID + * @param saltedPasswordHash optional user password hash (may be null) + * @throws DuplicateNameException if username already exists + * @throws IOException if IO error occurs + */ + void addUser(String username, char[] saltedPasswordHash) + throws DuplicateNameException, IOException { + if (saltedPasswordHash == null && enableLocalPasswords) { + saltedPasswordHash = getDefaultPasswordHash(); + } + addUser(username, saltedPasswordHash, null); } /** @@ -401,14 +416,16 @@ public class UserManager { /** * Reset the local password to the 'changeme' for the specified user. * @param username + * @param saltedPasswordHash optional user password hash (may be null) * @return true if password updated successfully. * @throws IOException */ - public boolean resetPassword(String username) throws IOException { + public boolean resetPassword(String username, char[] saltedPasswordHash) throws IOException { if (!enableLocalPasswords) { return false; } - return setPassword(username, getDefaultPasswordHash(), true); + return setPassword(username, + saltedPasswordHash != null ? saltedPasswordHash : getDefaultPasswordHash(), true); } private char[] getDefaultPasswordHash() { diff --git a/Ghidra/RuntimeScripts/Common/server/server.conf b/Ghidra/RuntimeScripts/Common/server/server.conf index 3dd4b14f79..780da9a0c8 100644 --- a/Ghidra/RuntimeScripts/Common/server/server.conf +++ b/Ghidra/RuntimeScripts/Common/server/server.conf @@ -128,7 +128,7 @@ ghidra.repositories.dir=./repositories # # -d : the Active Directory domain name. Example: "-dmydomain.com" # -# -e : specifies default password expiration time in days (-a0 mode only, default is 1-day) +# -e : specifies initial/reset password expiration time in days (-a0 mode only, default is 1-day, 0 = no expiration) # # -jaas : specifies the path to the JAAS config file (when using -a4), relative # to the ghidra/server directory (if not absolute). diff --git a/Ghidra/RuntimeScripts/Common/server/svrREADME.html b/Ghidra/RuntimeScripts/Common/server/svrREADME.html index b714a53cb6..80c2dc0630 100644 --- a/Ghidra/RuntimeScripts/Common/server/svrREADME.html +++ b/Ghidra/RuntimeScripts/Common/server/svrREADME.html @@ -363,7 +363,7 @@ public key files may be made without restarting the Ghidra Server. Example: "-dmydomain.com"
  • -e#
    Allows the reset password expiration to be set to a - specified number of days (default is 1-day).
  • + specified number of days (default is 1-day). A value of 0 prevents expiration.
  • -jaas <config_file>
    Specifies the path to the JAAS config file (when using -a4), relative to the ghidra/server directory (if not absolute). @@ -581,9 +581,9 @@ to run as root and monitor/manage the Java process.
         svrAdmin [<server-root-path>]
    -             [-add <user_sid>] 
    +             [-add <user_sid> [--p]] 
                  [-remove <user_sid>] 
    -             [-reset <user_sid>] 
    +             [-reset <user_sid> [--p]] 
                  [-dn <user_sid> "<user_dn>"]
                  [-admin <user_sid> "<repository_name>"]
                  [-list]
    @@ -605,11 +605,14 @@ to run as root and monitor/manage the Java process.
         be permitted.  If Ghidra password authentication is used [-a0], the 
         initial password is set to changeme. This password must be changed by 
         the user within 24-hours to avoid its expiration (password expiration period can be extended as 
    -    a server option, see -e server option).
    +    a server option, see -e server option).  
    +    Alternatively, the initial password may be specified by including the optional --p
    +    parameter which will prompt for an initial password.
         

    - Example: + Examples:
             svrAdmin -add mySID
    +        svrAdmin -add mySID --p
         
  • -remove  (Removing a User)
    @@ -627,10 +630,13 @@ to run as root and monitor/manage the Java process. If a user's password has expired, or has simply been forgotten, the password may be reset to changeme. After resetting, this password must be changed by the user within 24-hours to avoid its expiration (password expiration period can be extended as a server option). + Alternatively, the new password may be specified by including the optional --p + parameter which will prompt for an initial password.

    Example:
             svrAdmin -reset mySID
    +        svrAdmin -reset mySID --p
         
  • -dn  (Assign User's Distinguished Name)
    diff --git a/Ghidra/RuntimeScripts/Linux/server/svrAdmin b/Ghidra/RuntimeScripts/Linux/server/svrAdmin index 0ad3278940..702ca83c49 100755 --- a/Ghidra/RuntimeScripts/Linux/server/svrAdmin +++ b/Ghidra/RuntimeScripts/Linux/server/svrAdmin @@ -1,17 +1,18 @@ #!/usr/bin/env bash # *********************************************************** # ** Arguments (each -argument option may be repeated): -# ** [-add ] [-dn ""] +# ** [-add [--p]] +# ** [-dn ""] # ** [-remove ] -# ** [-reset ] +# ** [-reset [--p]] # ** [-admin ""] # ** [-list] [-users] # ** [-migrate ""] [-migrate-all] # ** -# ** add - add a new user to the server with the default password 'changeme' +# ** add - add a new user to the server with the default password 'changeme' [--p prompt for password] # ** dn - set a user's distinguished name for PKI authentication # ** remove - remove an existing user from the server -# ** reset - reset an existing user's password to 'changeme' +# ** reset - reset an existing user's password to 'changeme' [--p prompt for password] # ** admin - set the specified existing user as an admin of the specified repository # ** list - list all existing named repositories # ** users - list all users or those associated with each listed repository diff --git a/Ghidra/RuntimeScripts/Windows/server/svrAdmin.bat b/Ghidra/RuntimeScripts/Windows/server/svrAdmin.bat index d323aad366..ea7993ca45 100644 --- a/Ghidra/RuntimeScripts/Windows/server/svrAdmin.bat +++ b/Ghidra/RuntimeScripts/Windows/server/svrAdmin.bat @@ -2,17 +2,18 @@ :: *********************************************************** :: ** Arguments (each -argument option may be repeated): -:: ** [-add ] [-dn ""] +:: ** [-add [--p]] +:: ** [-dn ""] :: ** [-remove ] -:: ** [-reset ] +:: ** [-reset [--p]] :: ** [-admin ""] :: ** [-list] [-users] :: ** [-migrate ""] [-migrate-all] :: ** -:: ** add - add a new user to the server with the default password 'changeme' +:: ** add - add a new user to the server with the default password 'changeme' [--p prompt for password] :: ** dn - set a user's distinguished name for PKI authentication :: ** remove - remove an existing user from the server -:: ** reset - reset an existing user's password to 'changeme' +:: ** reset - reset an existing user's password to 'changeme' [--p prompt for password] :: ** admin - set the specified existing user as an admin of the specified repository :: ** list - list all existing named repositories :: ** users - list all users or those associated with each listed repository