/* ### * 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.ArrayList; import java.util.Properties; import javax.security.auth.x500.X500Principal; import generic.jar.ResourceFile; import ghidra.GhidraApplicationLayout; import ghidra.GhidraLaunchable; import ghidra.framework.Application; import ghidra.framework.ApplicationConfiguration; import ghidra.util.Msg; import ghidra.util.NamingUtilities; public class ServerAdmin implements GhidraLaunchable { private static final String CONFIG_FILE_PROPERTY = "UserAdmin.config"; // property name defined within the sever.conf file which specifies // server repositories directory private static final String SERVER_DIR_CONFIG_PROPERTY = "ghidra.repositories.dir"; private static final String INVOCATION_NAME_PROPERTY = "UserAdmin.invocation"; // Immediate commands private static final String LIST_COMMAND = "-list"; private static final String USERS_COMMAND = "-users"; // Delayed commands private static final String MIGRATE_COMMAND = "-migrate"; private static final String MIGRATE_ALL_COMMAND = "-migrate-all"; private boolean propertyUsed = false; /** * Main method for running the UserAdmin Application. * The following properties may be set: *
	 *   UserAdmin.invocation - identifies the name of the application used when displaying usage text.
	 *   UserAdmin.serverDir - identifies the server directory instead of passing on command line.
	 * 
* @param args command line arguments */ @Override public void launch(GhidraApplicationLayout layout, String[] args) { // Perform static initializations if not already initialized // Some tests invoke main method directly which have already initialized Application if (!Application.isInitialized()) { ApplicationConfiguration configuration = new ApplicationConfiguration(); configuration.setInitializeLogging(false); Application.initializeApplication(layout, configuration); } File serverDir = null; int ix = 0; if (args.length != 0 && !args[0].startsWith("-")) { serverDir = new File(args[ix++]); } else { serverDir = getServerDirFromConfig(); } if (serverDir == null || (args.length - ix) == 0) { displayUsage(""); System.exit(-1); return; } try { serverDir = serverDir.getCanonicalFile(); } catch (IOException e1) { System.err.println("Failed to resolve server directory: " + serverDir); System.exit(-1); } if (propertyUsed) { System.out.println("Using server directory: " + serverDir); } File userFile = new File(serverDir, UserManager.USER_PASSWORD_FILE); if (!serverDir.isDirectory() || !userFile.isFile()) { System.err.println("Invalid Ghidra server directory!"); System.exit(-1); } File cmdDir = new File(serverDir, UserAdmin.ADMIN_CMD_DIR); if (!cmdDir.isDirectory() || !cmdDir.canWrite()) { System.err.println("Insufficient privilege or server not started!"); System.exit(-1); } // Process command line boolean listRepositories = false; boolean listUsers = false; boolean migrationConfirmed = false; boolean migrationAbort = false; ArrayList cmdList = new ArrayList<>(); int cmdLen = 1; for (; ix < args.length; ix += cmdLen) { boolean queueCmd = true; if (UserAdmin.ADD_USER_COMMAND.equals(args[ix])) { // add user cmdLen = 2; validateSID(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); } 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]; if (!migrationConfirmed && !confirmMigration()) { migrationAbort = true; } migrationConfirmed = true; if (!migrationAbort) { Repository.markRepositoryForIndexMigration(serverDir, repositoryName, false); } } } else { displayUsage("Invalid usage!"); System.exit(-1); } if (queueCmd) { addCommand(cmdList, args, ix, cmdLen); } } try { UserAdmin.writeCommands(cmdList, cmdDir); } 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); } if (listRepositories) { RepositoryManager.listRepositories(serverDir, listUsers); } System.out.println(); } /** * @param serverDir * @param args * @param i */ private static void addCommand(ArrayList cmdList, String[] args, int argOffset, int argCnt) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < argCnt; i++) { if (i > 0) { buf.append(' '); } buf.append(args[argOffset + i]); } cmdList.add(buf.toString()); } private static boolean confirmMigration() { System.out.print("\nWARNING! Please confirm the requested migration of one or more\n" + "Ghidra Server repositories. Once migrated to indexed storage,\n" + "any attempt to use these server repositories with a Ghidra Server\n" + "older than version 5.5 will corrupt the data storage.\n" + "\nWould you like to continue? [y/n]: "); try { if ('y' == System.in.read()) { System.out.println(); return true; } } catch (IOException e) { e.printStackTrace(); } System.out.println("\nAll repository data migration(s) has been aborted."); return false; } /** * Validate properly formatted Distinguished Name * Example: 'CN=Doe John, OU=X, OU=Y, OU=DoD, O=U.S. Government, C=US' * @param args * @param i argument index */ private void validateDN(String[] args, int i) { if (args.length < (i + 1)) { displayUsage("Invalid usage!"); System.exit(-1); } String dn = args[i]; try { X500Principal x500User = new X500Principal(dn); args[i] = "\"" + x500User.getName() + "\""; } catch (Exception e) { Msg.error(UserAdmin.class, "Invalid DN: " + dn); System.exit(-1); } } /** * Validate username/sid * @param args * @param i argument index */ private void validateSID(String[] args, int i) { if (args.length < (i + 1)) { displayUsage("Invalid usage!"); System.exit(-1); } String sid = args[i]; if (!NamingUtilities.isValidName(sid) || sid.indexOf(' ') >= 0) { Msg.error(UserAdmin.class, "Invalid username/sid: " + sid); System.exit(-1); } } /** * Validate username/sid * @param args * @param i argument index */ private void validateRepName(String[] args, int i, File rootDirFile) { if (args.length < (i + 1)) { displayUsage("Invalid usage!"); 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); System.exit(-1); } } private File getServerDirFromConfig() { String p = System.getProperty(CONFIG_FILE_PROPERTY); if (p == null) { return null; } propertyUsed = true; File configFile = new File(p); if (!configFile.exists()) { System.out.println("Config file not found: " + configFile.getAbsolutePath()); } Properties config = new Properties(); InputStream in = null; try { in = new FileInputStream(configFile); config.load(in); } catch (IOException e) { System.out.println("Failed to read " + configFile.getName() + ": " + e.getMessage()); } finally { if (in != null) { try { in.close(); } catch (IOException e) { // ignore } } } p = config.getProperty(SERVER_DIR_CONFIG_PROPERTY); if (p == null) { return null; } File dir = new File(p); if (!dir.isAbsolute()) { // Make relative repositories dir relative to installation root ResourceFile installRoot = Application.getInstallationDirectory(); if (installRoot == null || installRoot.getFile(false) == null) { System.out.println("Failed to resolve installation root directory!"); return null; } dir = new File(installRoot.getFile(false), p); } return dir; } /** * Display an optional message followed by usage syntax. * @param msg */ private void displayUsage(String msg) { if (msg != null) { System.err.println(msg); } String invocationName = System.getProperty(INVOCATION_NAME_PROPERTY); System.err.println("Usage: " + (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(" -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(" -dn \"\""); System.err.println( " When PKI authentication is used, add the specified X500 Distinguished Name for a user"); System.err.println(" -admin \"\""); System.err.println( " Grant ADMIN privilege to the specified user with the specified repository"); 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(" -users"); System.err.println(" Output list of users to console which have server access"); System.err.println(" -migrate \"\""); System.err.println( " Migrate the specified repository to the latest file system storage schema (see svrREADME.html)"); System.err.println(" -migrate-all"); System.err.println( " Migrate the all repositories to the latest file system storage schema (see svrREADME.html)"); System.err.println(); } }