GT-3640 added support for specifying user password for server add/reset

commands
This commit is contained in:
ghidra1 2020-07-13 17:31:28 -04:00
parent 796ad69cc0
commit 8f65942fd0
8 changed files with 191 additions and 47 deletions

View file

@ -55,7 +55,7 @@ public class RepositoryManager {
* @param enableLocalPasswords if true user passwords will be maintained * @param enableLocalPasswords if true user passwords will be maintained
* within local 'users' file * within local 'users' file
* @param defaultPasswordExpirationDays password expiration in days when * @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 * @param anonymousAccessAllowed if true server permits anonymous access
* to repositories. * to repositories.
* @throws IOException if IO error occurs * @throws IOException if IO error occurs
@ -271,16 +271,16 @@ public class RepositoryManager {
log.info("Known Repositories:"); log.info("Known Repositories:");
String[] names = getRepositoryNames(rootDirFile); String[] names = getRepositoryNames(rootDirFile);
for (int i = 0; i < names.length; i++) { for (String name : names) {
log.info(" " + names[i]); log.info(" " + name);
} }
if (names.length == 0) { if (names.length == 0) {
log.info(" <none>"); log.info(" <none>");
} }
for (int i = 0; i < names.length; i++) { for (String name2 : names) {
File f = new File(rootDirFile, NamingUtilities.mangle(names[i])); File f = new File(rootDirFile, NamingUtilities.mangle(name2));
if (!f.isDirectory()) { if (!f.isDirectory()) {
log.error("Error while processing repository " + names[i] + log.error("Error while processing repository " + name2 +
", directory not found: " + f); ", directory not found: " + f);
continue; continue;
} }
@ -288,7 +288,7 @@ public class RepositoryManager {
throw new IOException(f.getAbsolutePath() + " can not be written to"); throw new IOException(f.getAbsolutePath() + " can not be written to");
} }
try { try {
Repository rep = new Repository(this, null, f, names[i]); Repository rep = new Repository(this, null, f, name2);
String name = rep.getName(); String name = rep.getName();
repositoryMap.put(name, rep); repositoryMap.put(name, rep);
} }
@ -296,7 +296,7 @@ public class RepositoryManager {
// ignore // ignore
} }
catch (Exception e) { catch (Exception e) {
log.error("Error while processing repository " + names[i] + ", " + e.getMessage()); log.error("Error while processing repository " + name2 + ", " + e.getMessage());
continue; continue;
} }
} }
@ -363,16 +363,16 @@ public class RepositoryManager {
return new String[0]; return new String[0];
} }
ArrayList<String> list = new ArrayList<>(dirList.length); ArrayList<String> list = new ArrayList<>(dirList.length);
for (int i = 0; i < dirList.length; i++) { for (File element : dirList) {
if (!dirList[i].isDirectory() || if (!element.isDirectory() ||
LocalFileSystem.isHiddenDirName(dirList[i].getName())) { LocalFileSystem.isHiddenDirName(element.getName())) {
continue; continue;
} }
if (!NamingUtilities.isValidMangledName(dirList[i].getName())) { if (!NamingUtilities.isValidMangledName(element.getName())) {
log.warn("Ignoring repository directory with bad name: " + dirList[i]); log.warn("Ignoring repository directory with bad name: " + element);
continue; continue;
} }
list.add(NamingUtilities.demangle(dirList[i].getName())); list.add(NamingUtilities.demangle(element.getName()));
} }
Collections.sort(list); Collections.sort(list);
String[] names = new String[list.size()]; String[] names = new String[list.size()];

View file

@ -16,8 +16,7 @@
package ghidra.server; package ghidra.server;
import java.io.*; import java.io.*;
import java.util.ArrayList; import java.util.*;
import java.util.Properties;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
@ -26,8 +25,7 @@ import ghidra.GhidraApplicationLayout;
import ghidra.GhidraLaunchable; import ghidra.GhidraLaunchable;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration; import ghidra.framework.ApplicationConfiguration;
import ghidra.util.Msg; import ghidra.util.*;
import ghidra.util.NamingUtilities;
public class ServerAdmin implements GhidraLaunchable { public class ServerAdmin implements GhidraLaunchable {
@ -132,9 +130,14 @@ public class ServerAdmin implements GhidraLaunchable {
int cmdLen = 1; int cmdLen = 1;
for (; ix < args.length; ix += cmdLen) { for (; ix < args.length; ix += cmdLen) {
boolean queueCmd = true; boolean queueCmd = true;
char[] pwdHash = null;
if (UserAdmin.ADD_USER_COMMAND.equals(args[ix])) { // add user if (UserAdmin.ADD_USER_COMMAND.equals(args[ix])) { // add user
cmdLen = 2; cmdLen = 2;
validateSID(args, ix + 1); 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 else if (UserAdmin.REMOVE_USER_COMMAND.equals(args[ix])) { // remove user
cmdLen = 2; cmdLen = 2;
@ -143,6 +146,10 @@ public class ServerAdmin implements GhidraLaunchable {
else if (UserAdmin.RESET_USER_COMMAND.equals(args[ix])) { // reset user else if (UserAdmin.RESET_USER_COMMAND.equals(args[ix])) { // reset user
cmdLen = 2; cmdLen = 2;
validateSID(args, ix + 1); 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 else if (UserAdmin.SET_USER_DN_COMMAND.equals(args[ix])) { // set/add user with DN for PKI
cmdLen = 3; cmdLen = 3;
@ -198,7 +205,7 @@ public class ServerAdmin implements GhidraLaunchable {
System.exit(-1); System.exit(-1);
} }
if (queueCmd) { 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(); 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 * Determine if option specified as args[argOffset]
* @param args * @param args command line args
* @param i * @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<String> cmdList, String[] args, int argOffset, private static void addCommand(ArrayList<String> cmdList, String[] args, int argOffset,
int argCnt) { int argCnt, char[] pwdHash) {
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();
for (int i = 0; i < argCnt; i++) { for (int i = 0; i < argCnt; i++) {
if (i > 0) { if (i > 0) {
@ -234,6 +334,10 @@ public class ServerAdmin implements GhidraLaunchable {
} }
buf.append(args[argOffset + i]); buf.append(args[argOffset + i]);
} }
if (pwdHash != null) {
buf.append(' ');
buf.append(pwdHash);
}
cmdList.add(buf.toString()); cmdList.add(buf.toString());
} }
@ -376,12 +480,14 @@ public class ServerAdmin implements GhidraLaunchable {
(invocationName != null ? invocationName : "java " + UserAdmin.class.getName()) + (invocationName != null ? invocationName : "java " + UserAdmin.class.getName()) +
(propertyUsed ? "" : " <serverPath>") + " [<command>] [<command>] ..."); (propertyUsed ? "" : " <serverPath>") + " [<command>] [<command>] ...");
System.err.println("\nSupported commands:"); System.err.println("\nSupported commands:");
System.err.println(" -add <sid>"); System.err.println(" -add <sid> [--p]");
System.err.println(" Add a new user to the server identified by their sid identifier"); System.err.println(
" Add a new user to the server identified by their sid identifier [-p prompt for password]");
System.err.println(" -remove <sid>"); System.err.println(" -remove <sid>");
System.err.println(" Remove the specified user from the server's user list"); System.err.println(" Remove the specified user from the server's user list");
System.err.println(" -reset <sid>"); System.err.println(" -reset <sid> [--p]");
System.err.println(" Reset the specified user's server login password"); System.err.println(
" Reset the specified user's server login password [-p prompt for password]");
System.err.println(" -dn <sid> \"<dname>\""); System.err.println(" -dn <sid> \"<dname>\"");
System.err.println( System.err.println(
" When PKI authentication is used, add the specified X500 Distinguished Name for a user"); " When PKI authentication is used, add the specified X500 Distinguished Name for a user");

View file

@ -42,6 +42,8 @@ public class UserAdmin {
static final String SET_USER_DN_COMMAND = "-dn"; static final String SET_USER_DN_COMMAND = "-dn";
static final String SET_ADMIN_COMMAND = "-admin"; 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 ADMIN_CMD_DIR = LocalFileSystem.HIDDEN_DIR_PREFIX + "admin";
static final String COMMAND_FILE_EXT = ".cmd"; static final String COMMAND_FILE_EXT = ".cmd";
@ -116,8 +118,12 @@ public class UserAdmin {
String[] args = splitCommand(cmd); String[] args = splitCommand(cmd);
if (ADD_USER_COMMAND.equals(args[0])) { // add user if (ADD_USER_COMMAND.equals(args[0])) { // add user
String sid = args[1]; String sid = args[1];
char[] pwdHash = null;
if (args.length == 4 && args[2].contentEquals(PASSWORD_OPTION)) {
pwdHash = args[3].toCharArray();
}
try { try {
userMgr.addUser(sid); userMgr.addUser(sid, pwdHash);
log.info("User '" + sid + "' added"); log.info("User '" + sid + "' added");
} }
catch (DuplicateNameException e) { catch (DuplicateNameException e) {
@ -131,9 +137,16 @@ public class UserAdmin {
} }
else if (RESET_USER_COMMAND.equals(args[0])) { // reset user else if (RESET_USER_COMMAND.equals(args[0])) { // reset user
String sid = args[1]; 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 + "'"); log.info("Failed to reset password for user '" + sid + "'");
} }
else if (pwdHash != null) {
log.info("User '" + sid + "' password reset to specified password");
}
else { else {
log.info("User '" + sid + "' password reset to default password"); log.info("User '" + sid + "' password reset to default password");
} }

View file

@ -75,7 +75,7 @@ public class UserManager {
* @param enableLocalPasswords if true user passwords will be maintained * @param enableLocalPasswords if true user passwords will be maintained
* within local 'users' file * within local 'users' file
* @param defaultPasswordExpirationDays password expiration in days when * @param defaultPasswordExpirationDays password expiration in days when
* local passwords are enabled * local passwords are enabled (0 = no expiration)
*/ */
UserManager(RepositoryManager repositoryMgr, boolean enableLocalPasswords, UserManager(RepositoryManager repositoryMgr, boolean enableLocalPasswords,
int defaultPasswordExpirationDays) { int defaultPasswordExpirationDays) {
@ -195,7 +195,22 @@ public class UserManager {
* @throws IOException if IO error occurs * @throws IOException if IO error occurs
*/ */
public void addUser(String username) throws DuplicateNameException, IOException { 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. * Reset the local password to the 'changeme' for the specified user.
* @param username * @param username
* @param saltedPasswordHash optional user password hash (may be null)
* @return true if password updated successfully. * @return true if password updated successfully.
* @throws IOException * @throws IOException
*/ */
public boolean resetPassword(String username) throws IOException { public boolean resetPassword(String username, char[] saltedPasswordHash) throws IOException {
if (!enableLocalPasswords) { if (!enableLocalPasswords) {
return false; return false;
} }
return setPassword(username, getDefaultPasswordHash(), true); return setPassword(username,
saltedPasswordHash != null ? saltedPasswordHash : getDefaultPasswordHash(), true);
} }
private char[] getDefaultPasswordHash() { private char[] getDefaultPasswordHash() {

View file

@ -128,7 +128,7 @@ ghidra.repositories.dir=./repositories
# #
# -d<ad_domain> : the Active Directory domain name. Example: "-dmydomain.com" # -d<ad_domain> : the Active Directory domain name. Example: "-dmydomain.com"
# #
# -e<days> : specifies default password expiration time in days (-a0 mode only, default is 1-day) # -e<days> : specifies initial/reset password expiration time in days (-a0 mode only, default is 1-day, 0 = no expiration)
# #
# -jaas <config_file> : specifies the path to the JAAS config file (when using -a4), relative # -jaas <config_file> : specifies the path to the JAAS config file (when using -a4), relative
# to the ghidra/server directory (if not absolute). # to the ghidra/server directory (if not absolute).

View file

@ -363,7 +363,7 @@ public key files may be made without restarting the Ghidra Server.
Example: "-dmydomain.com"</LI> Example: "-dmydomain.com"</LI>
<br> <br>
<LI><typewriter>-e#</typewriter><br>Allows the reset password expiration to be set to a <LI><typewriter>-e#</typewriter><br>Allows the reset password expiration to be set to a
specified number of days (default is 1-day).</LI> specified number of days (default is 1-day). A value of 0 prevents expiration.</LI>
<br> <br>
<LI><typewriter>-jaas &lt;config_file&gt;</typewriter><br>Specifies the path to the JAAS <LI><typewriter>-jaas &lt;config_file&gt;</typewriter><br>Specifies the path to the JAAS
config file (when using -a4), relative to the ghidra/server directory (if not absolute). config file (when using -a4), relative to the ghidra/server directory (if not absolute).
@ -581,9 +581,9 @@ to run as <i>root</i> and monitor/manage the Java process.
<PRE> <PRE>
svrAdmin [&lt;server-root-path&gt;] svrAdmin [&lt;server-root-path&gt;]
[-add &lt;user_sid&gt;] [-add &lt;user_sid&gt; [--p]]
[-remove &lt;user_sid&gt;] [-remove &lt;user_sid&gt;]
[-reset &lt;user_sid&gt;] [-reset &lt;user_sid&gt; [--p]]
[-dn &lt;user_sid&gt; &quot;&lt;user_dn&gt;&quot;] [-dn &lt;user_sid&gt; &quot;&lt;user_dn&gt;&quot;]
[-admin &lt;user_sid&gt; &quot;&lt;repository_name&gt;&quot;] [-admin &lt;user_sid&gt; &quot;&lt;repository_name&gt;&quot;]
[-list] [-list]
@ -606,10 +606,13 @@ to run as <i>root</i> and monitor/manage the Java process.
initial password is set to <typewriter>changeme</typewriter>. This password must be changed by initial password is set to <typewriter>changeme</typewriter>. This password must be changed by
the user within 24-hours to avoid its expiration (password expiration period can be extended as the user within 24-hours to avoid its expiration (password expiration period can be extended as
a server option, see <typewriter>-e</typewriter> <a href="#serverOptions">server option</a>). a server option, see <typewriter>-e</typewriter> <a href="#serverOptions">server option</a>).
Alternatively, the initial password may be specified by including the optional <typewriter>--p</typewriter>
parameter which will prompt for an initial password.
<br><br> <br><br>
Example: Examples:
<PRE> <PRE>
svrAdmin -add mySID svrAdmin -add mySID
svrAdmin -add mySID --p
</PRE> </PRE>
</LI> </LI>
<LI><typewriter>-remove</typewriter>&nbsp;&nbsp;<b>(Removing a User)</b><br> <LI><typewriter>-remove</typewriter>&nbsp;&nbsp;<b>(Removing a User)</b><br>
@ -627,10 +630,13 @@ to run as <i>root</i> and monitor/manage the Java process.
If a user&apos;s password has expired, or has simply been forgotten, the password may be reset If a user&apos;s password has expired, or has simply been forgotten, the password may be reset
to <typewriter>changeme</typewriter>. After resetting, this password must be changed by the user within to <typewriter>changeme</typewriter>. 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). 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 <typewriter>--p</typewriter>
parameter which will prompt for an initial password.
<br><br> <br><br>
Example: Example:
<PRE> <PRE>
svrAdmin -reset mySID svrAdmin -reset mySID
svrAdmin -reset mySID --p
</PRE> </PRE>
</LI> </LI>
<LI><typewriter>-dn</typewriter>&nbsp;&nbsp;<b>(Assign User&apos;s Distinguished Name)</b><br> <LI><typewriter>-dn</typewriter>&nbsp;&nbsp;<b>(Assign User&apos;s Distinguished Name)</b><br>

View file

@ -1,17 +1,18 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# *********************************************************** # ***********************************************************
# ** Arguments (each -argument option may be repeated): # ** Arguments (each -argument option may be repeated):
# ** [-add <sid>] [-dn <sid> "<x500_distinguished_name>"] # ** [-add <sid> [--p]]
# ** [-dn <sid> "<x500_distinguished_name>"]
# ** [-remove <sid>] # ** [-remove <sid>]
# ** [-reset <sid>] # ** [-reset <sid> [--p]]
# ** [-admin <sid> "<repository-name>"] # ** [-admin <sid> "<repository-name>"]
# ** [-list] [-users] # ** [-list] [-users]
# ** [-migrate "<repository-name>"] [-migrate-all] # ** [-migrate "<repository-name>"] [-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 # ** dn - set a user's distinguished name for PKI authentication
# ** remove - remove an existing user from the server # ** 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 # ** admin - set the specified existing user as an admin of the specified repository
# ** list - list all existing named repositories # ** list - list all existing named repositories
# ** users - list all users or those associated with each listed repository # ** users - list all users or those associated with each listed repository

View file

@ -2,17 +2,18 @@
:: *********************************************************** :: ***********************************************************
:: ** Arguments (each -argument option may be repeated): :: ** Arguments (each -argument option may be repeated):
:: ** [-add <sid>] [-dn <sid> "<x500_distinguished_name>"] :: ** [-add <sid> [--p]]
:: ** [-dn <sid> "<x500_distinguished_name>"]
:: ** [-remove <sid>] :: ** [-remove <sid>]
:: ** [-reset <sid>] :: ** [-reset <sid> [--p]]
:: ** [-admin <sid> "<repository-name>"] :: ** [-admin <sid> "<repository-name>"]
:: ** [-list] [-users] :: ** [-list] [-users]
:: ** [-migrate "<repository-name>"] [-migrate-all] :: ** [-migrate "<repository-name>"] [-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 :: ** dn - set a user's distinguished name for PKI authentication
:: ** remove - remove an existing user from the server :: ** 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 :: ** admin - set the specified existing user as an admin of the specified repository
:: ** list - list all existing named repositories :: ** list - list all existing named repositories
:: ** users - list all users or those associated with each listed repository :: ** users - list all users or those associated with each listed repository