/*
* Password Management Servlets (PWM)
* http://www.pwm-project.org
*
* Copyright (c) 2006-2009 Novell, Inc.
* Copyright (c) 2009-2017 The PWM Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package password.pwm.util.cli;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.EnhancedPatternLayout;
import org.apache.log4j.Layout;
import org.apache.log4j.Logger;
import org.apache.log4j.varia.NullAppender;
import password.pwm.PwmApplication;
import password.pwm.PwmApplicationMode;
import password.pwm.PwmConstants;
import password.pwm.PwmEnvironment;
import password.pwm.config.Configuration;
import password.pwm.config.PwmSetting;
import password.pwm.config.stored.ConfigurationReader;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.util.cli.commands.ClearResponsesCommand;
import password.pwm.util.cli.commands.CliCommand;
import password.pwm.util.cli.commands.ConfigDeleteCommand;
import password.pwm.util.cli.commands.ConfigLockCommand;
import password.pwm.util.cli.commands.ConfigNewCommand;
import password.pwm.util.cli.commands.ConfigResetHttpsCommand;
import password.pwm.util.cli.commands.ConfigSetPasswordCommand;
import password.pwm.util.cli.commands.ConfigUnlockCommand;
import password.pwm.util.cli.commands.ExportAuditCommand;
import password.pwm.util.cli.commands.ExportHttpsKeyStoreCommand;
import password.pwm.util.cli.commands.ExportHttpsTomcatConfigCommand;
import password.pwm.util.cli.commands.ExportLocalDBCommand;
import password.pwm.util.cli.commands.ExportLogsCommand;
import password.pwm.util.cli.commands.ExportResponsesCommand;
import password.pwm.util.cli.commands.ExportStatsCommand;
import password.pwm.util.cli.commands.HelpCommand;
import password.pwm.util.cli.commands.ImportHttpsKeyStoreCommand;
import password.pwm.util.cli.commands.ImportLocalDBCommand;
import password.pwm.util.cli.commands.ImportResponsesCommand;
import password.pwm.util.cli.commands.LdapSchemaExtendCommand;
import password.pwm.util.cli.commands.LocalDBInfoCommand;
import password.pwm.util.cli.commands.ResponseStatsCommand;
import password.pwm.util.cli.commands.ShellCommand;
import password.pwm.util.cli.commands.TokenInfoCommand;
import password.pwm.util.cli.commands.UserReportCommand;
import password.pwm.util.cli.commands.VersionCommand;
import password.pwm.util.java.FileSystemUtility;
import password.pwm.util.java.JavaHelper;
import password.pwm.util.localdb.LocalDB;
import password.pwm.util.localdb.LocalDBException;
import password.pwm.util.localdb.LocalDBFactory;
import password.pwm.util.logging.PwmLogLevel;
import password.pwm.util.logging.PwmLogManager;
import password.pwm.util.logging.PwmLogger;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.TreeMap;
public class MainClass {
private static final PwmLogger LOGGER = PwmLogger.forClass(MainClass.class);
private static final String LOGGING_PATTERN = "%d{yyyy-MM-dd'T'HH:mm:ssX}{GMT}, %-5p, %c{2}, %m%n";
private static MainOptions MAIN_OPTIONS;
public static final Map<String,CliCommand> COMMANDS;
static {
final List<CliCommand> commandList = new ArrayList<>();
commandList.add(new LocalDBInfoCommand());
commandList.add(new ExportLogsCommand());
commandList.add(new UserReportCommand());
commandList.add(new ExportLocalDBCommand());
commandList.add(new ImportLocalDBCommand());
commandList.add(new ExportAuditCommand());
commandList.add(new ConfigUnlockCommand());
commandList.add(new ConfigLockCommand());
commandList.add(new ConfigSetPasswordCommand());
commandList.add(new ExportStatsCommand());
commandList.add(new ExportResponsesCommand());
commandList.add(new ClearResponsesCommand());
commandList.add(new ImportResponsesCommand());
commandList.add(new TokenInfoCommand());
commandList.add(new ConfigNewCommand());
commandList.add(new VersionCommand());
commandList.add(new LdapSchemaExtendCommand());
commandList.add(new ConfigDeleteCommand());
commandList.add(new ResponseStatsCommand());
commandList.add(new ImportHttpsKeyStoreCommand());
commandList.add(new ExportHttpsKeyStoreCommand());
commandList.add(new ExportHttpsTomcatConfigCommand());
commandList.add(new ShellCommand());
commandList.add(new ConfigResetHttpsCommand());
commandList.add(new HelpCommand());
//commandList.add(new PasswordExpireNotificationCommand());
final Map<String,CliCommand> sortedMap = new TreeMap<>();
for (final CliCommand command : commandList) {
sortedMap.put(command.getCliParameters().commandName,command);
}
COMMANDS = Collections.unmodifiableMap(sortedMap);
}
public static String helpTextFromCommands(final Collection<CliCommand> commands) {
final StringBuilder output = new StringBuilder();
for (final CliCommand command : commands) {
output.append(command.getCliParameters().commandName);
if (command.getCliParameters().options != null) {
for (final CliParameters.Option option : command.getCliParameters().options) {
output.append(" ");
if (option.isOptional()) {
output.append("<").append(option.getName()).append(">");
} else {
output.append("[").append(option.getName()).append("]");
}
}
}
output.append("\n");
output.append(" ").append(command.getCliParameters().description);
output.append("\n");
}
return output.toString();
}
private static String makeHelpTextOutput() {
final StringBuilder output = new StringBuilder();
output.append(helpTextFromCommands(COMMANDS.values()));
output.append("\n");
output.append("options:\n");
output.append(" -force force operations skipping any confirmation\n");
output.append(" -debugLevel=x set the debug level where x is TRACE, DEBUG, INFO, ERROR, WARN or FATAL\n");
output.append(" -applicationPath=x set the application path, default is current path\n");
output.append("\n");
output.append("usage: \n");
output.append(" command[.bat/.sh] <options> CommandName <command options>");
return output.toString();
}
private static CliEnvironment createEnv(
final CliParameters parameters,
final List<String> args
)
throws Exception
{
final Map<String,Object> options = parseCommandOptions(parameters, args);
final File applicationPath = figureApplicationPath(MAIN_OPTIONS);
out("applicationPath=" + applicationPath.getAbsolutePath());
PwmEnvironment.verifyApplicationPath(applicationPath);
final File configurationFile = locateConfigurationFile(applicationPath);
final ConfigurationReader configReader = loadConfiguration(configurationFile);
final Configuration config = configReader.getConfiguration();
final PwmApplication pwmApplication;
final LocalDB localDB;
if (parameters.needsPwmApplication) {
pwmApplication = loadPwmApplication(applicationPath, MAIN_OPTIONS.getApplicationFlags(), config, configurationFile, parameters.readOnly);
localDB = pwmApplication.getLocalDB();
} else if (parameters.needsLocalDB) {
pwmApplication = null;
localDB = loadPwmDB(config, parameters.readOnly, applicationPath);
} else {
pwmApplication = null;
localDB = null;
}
out("environment initialized");
out("");
return new CliEnvironment(
configReader,
configurationFile,
config,
applicationPath,
pwmApplication,
localDB,
new OutputStreamWriter(System.out),
options,
MAIN_OPTIONS
);
}
public static Map<String,Object> parseCommandOptions(
final CliParameters cliParameters,
final List<String> args
)
throws CliException
{
final Queue<String> argQueue = new LinkedList<>(args);
final Map<String,Object> returnObj = new LinkedHashMap<>();
if (cliParameters.options != null) {
for (final CliParameters.Option option : cliParameters.options) {
if (!option.isOptional() && argQueue.isEmpty()) {
throw new CliException("missing required option '" + option.getName() + "'");
}
if (!argQueue.isEmpty()) {
final String argument = argQueue.poll();
switch (option.getType()) {
case NEW_FILE:
try {
final File theFile = new File(argument);
if (theFile.exists()) {
throw new CliException("file for option '" + option.getName() + "' at '" + theFile.getAbsolutePath() + "' already exists");
}
returnObj.put(option.getName(),theFile);
} catch (Exception e) {
if (e instanceof CliException) {
throw (CliException)e;
}
throw new CliException("cannot access file for option '" + option.getName() + "', " + e.getMessage());
}
break;
case EXISTING_FILE:
try {
final File theFile = new File(argument);
if (!theFile.exists()) {
throw new CliException("file for option '" + option.getName() + "' at '" + theFile.getAbsolutePath() + "' does not exist");
}
returnObj.put(option.getName(),theFile);
} catch (Exception e) {
if (e instanceof CliException) {
throw (CliException)e;
}
throw new CliException("cannot access file for option '" + option.getName() + "', " + e.getMessage());
}
break;
case STRING:
returnObj.put(option.getName(), argument);
break;
default:
JavaHelper.unhandledSwitchStatement(option.getType());
}
}
}
}
if (!argQueue.isEmpty()) {
throw new CliException("unknown option '" + argQueue.poll() + "'");
}
return returnObj;
}
public static void main(final String[] args)
throws Exception
{
out(PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " Command Line Utility");
MAIN_OPTIONS = MainOptions.parseMainCommandLineOptions(args, new OutputStreamWriter(System.out));
final String[] workingArgs = MAIN_OPTIONS.getRemainingArguments();
initLog4j(MAIN_OPTIONS.getPwmLogLevel());
final String commandStr = workingArgs == null || workingArgs.length < 1 ? null : workingArgs[0];
if (commandStr == null) {
out("\n");
out(makeHelpTextOutput());
} else {
for (final CliCommand command : COMMANDS.values()) {
if (commandStr.equalsIgnoreCase(command.getCliParameters().commandName)) {
executeCommand(command, commandStr, workingArgs);
break;
}
}
out("unknown command '" + workingArgs[0] + "'");
}
}
private static void executeCommand(
final CliCommand command,
final String commandStr,
final String[] args
) {
final List<String> argList = new LinkedList<>(Arrays.asList(args));
argList.remove(0);
final CliEnvironment cliEnvironment;
try {
cliEnvironment = createEnv(command.getCliParameters(), argList);
} catch (Exception e) {
final String errorMsg = "unable to establish operating environment: " + e.getMessage();
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_ENVIRONMENT_ERROR, errorMsg);
LOGGER.error(errorInformation.toDebugStr(),e);
out("unable to establish operating environment: " + e.getMessage());
System.exit(-1);
return;
}
try {
command.execute(commandStr, cliEnvironment);
} catch (Exception e) {
System.out.println(e.getMessage());
//System.exit(-1);
return;
}
if (cliEnvironment.getPwmApplication() != null) {
try {
cliEnvironment.getPwmApplication().shutdown();
} catch (Exception e) {
out("error closing operating environment: " + e.getMessage());
e.printStackTrace();
}
}
if (cliEnvironment.getLocalDB() != null) {
try {
cliEnvironment.getLocalDB().close();
} catch (Exception e) {
out("error closing LocalDB environment: " + e.getMessage());
}
}
System.exit(0);
return;
}
private static void initLog4j(final PwmLogLevel logLevel) {
if (logLevel == null) {
Logger.getRootLogger().removeAllAppenders();
Logger.getRootLogger().addAppender(new NullAppender());
PwmLogger.markInitialized();
return;
}
final Layout patternLayout = new EnhancedPatternLayout(LOGGING_PATTERN);
final ConsoleAppender consoleAppender = new ConsoleAppender(patternLayout);
for (final Package logPackage : PwmLogManager.LOGGING_PACKAGES) {
if (logPackage != null) {
final Logger logger = Logger.getLogger(logPackage.getName());
logger.addAppender(consoleAppender);
logger.setLevel(logLevel.getLog4jLevel());
}
}
PwmLogger.markInitialized();
}
private static LocalDB loadPwmDB(
final Configuration config,
final boolean readonly,
final File applicationPath
)
throws Exception
{
final File databaseDirectory;
final String pwmDBLocationSetting = config.readSettingAsString(PwmSetting.PWMDB_LOCATION);
databaseDirectory = FileSystemUtility.figureFilepath(pwmDBLocationSetting, applicationPath);
return LocalDBFactory.getInstance(databaseDirectory, readonly, null, config);
}
private static ConfigurationReader loadConfiguration(final File configurationFile) throws Exception {
final ConfigurationReader reader = new ConfigurationReader(configurationFile);
if (reader.getConfigMode() == PwmApplicationMode.ERROR) {
final String errorMsg = reader.getConfigFileError() == null ? "error" : reader.getConfigFileError().toDebugStr();
out("unable to load configuration: " + errorMsg);
System.exit(-1);
}
return reader;
}
private static PwmApplication loadPwmApplication(
final File applicationPath,
final Collection<PwmEnvironment.ApplicationFlag> flags,
final Configuration config,
final File configurationFile,
final boolean readonly
)
throws LocalDBException, PwmUnrecoverableException
{
final PwmApplicationMode mode = readonly ? PwmApplicationMode.READ_ONLY : PwmApplicationMode.RUNNING;
final Collection<PwmEnvironment.ApplicationFlag> applicationFlags = new HashSet<>();
if (flags == null) {
applicationFlags.addAll(PwmEnvironment.ParseHelper.readApplicationFlagsFromSystem(null));
} else {
applicationFlags.addAll(flags);
}
applicationFlags.add(PwmEnvironment.ApplicationFlag.CommandLineInstance);
final PwmEnvironment pwmEnvironment = new PwmEnvironment.Builder(config, applicationPath)
.setApplicationMode(mode)
.setConfigurationFile(configurationFile)
.setFlags(applicationFlags)
.createPwmEnvironment();
final PwmApplication pwmApplication = new PwmApplication(pwmEnvironment);
final PwmApplicationMode runningMode = pwmApplication.getApplicationMode();
if (runningMode != mode) {
out("unable to start application in required state '" + mode + "', current state: " + runningMode);
System.exit(-1);
}
return pwmApplication;
}
private static File locateConfigurationFile(final File applicationPath) {
return new File(applicationPath + File.separator + PwmConstants.DEFAULT_CONFIG_FILE_FILENAME);
}
private static void out(final CharSequence txt) {
System.out.println(txt);
}
private static File figureApplicationPath(final MainOptions mainOptions) throws IOException, PwmUnrecoverableException {
final File applicationPath;
if (mainOptions != null && mainOptions.getApplicationPath() != null) {
applicationPath = mainOptions.getApplicationPath();
} else {
final String appPathStr = PwmEnvironment.ParseHelper.readValueFromSystem(PwmEnvironment.EnvironmentParameter.applicationPath,null);
if (appPathStr != null && !appPathStr.isEmpty()) {
applicationPath = new File(appPathStr);
} else {
final String errorMsg = "unable to locate applicationPath. Specify using -applicationPath option, java option "
+ "\"" + PwmEnvironment.EnvironmentParameter.applicationPath.conicalJavaOptionSystemName() + "\""
+ ", or system environment setting "
+ "\"" + PwmEnvironment.EnvironmentParameter.applicationPath.conicalEnvironmentSystemName() + "\"";
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_STARTUP_ERROR,errorMsg));
}
}
LOGGER.debug("using applicationPath " + applicationPath.getAbsolutePath());
return applicationPath;
}
}