/* * 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.http; import password.pwm.AppProperty; 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.stored.ConfigurationProperty; import password.pwm.config.stored.ConfigurationReader; import password.pwm.config.stored.StoredConfigurationImpl; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.util.java.JavaHelper; import password.pwm.util.logging.PwmLogger; import password.pwm.util.secure.PwmRandom; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.io.File; import java.io.InputStream; import java.io.Serializable; import java.util.Collection; import java.util.Date; import java.util.Locale; import java.util.Map; import java.util.Timer; import java.util.TimerTask; public class ContextManager implements Serializable { // ------------------------------ FIELDS ------------------------------ private static final PwmLogger LOGGER = PwmLogger.forClass(ContextManager.class); private ServletContext servletContext; private Timer taskMaster; private transient PwmApplication pwmApplication; private ConfigurationReader configReader; private ErrorInformation startupErrorInformation; private volatile boolean restartRequestedFlag = false; private int restartCount = 0; private final String instanceGuid; private String contextPath; private static final String UNSPECIFIED_VALUE = "unspecified"; public ContextManager(final ServletContext servletContext) { this.servletContext = servletContext; this.instanceGuid = PwmRandom.getInstance().randomUUID().toString(); this.contextPath = servletContext.getContextPath(); } // -------------------------- STATIC METHODS -------------------------- public static PwmApplication getPwmApplication(final HttpServletRequest request) throws PwmUnrecoverableException { return getPwmApplication(request.getSession()); } public static PwmApplication getPwmApplication(final HttpSession session) throws PwmUnrecoverableException { return getContextManager(session.getServletContext()).getPwmApplication(); } public static PwmApplication getPwmApplication(final ServletContext theContext) throws PwmUnrecoverableException { return getContextManager(theContext).getPwmApplication(); } public static ContextManager getContextManager(final HttpSession session) throws PwmUnrecoverableException { return getContextManager(session.getServletContext()); } public static ContextManager getContextManager(final PwmRequest pwmRequest) throws PwmUnrecoverableException { return getContextManager(pwmRequest.getHttpServletRequest().getSession()); } public static ContextManager getContextManager(final ServletContext theContext) throws PwmUnrecoverableException { // context manager is initialized at servlet context startup. final Object theManager = theContext.getAttribute(PwmConstants.CONTEXT_ATTR_CONTEXT_MANAGER); if (theManager == null) { final String errorMsg = "unable to load the context manager from servlet context"; final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_APP_UNAVAILABLE,errorMsg); throw new PwmUnrecoverableException(errorInformation); } return (ContextManager) theManager; } // --------------------- GETTER / SETTER METHODS --------------------- public PwmApplication getPwmApplication() throws PwmUnrecoverableException { if (pwmApplication == null) { final ErrorInformation errorInformation; if (startupErrorInformation != null) { errorInformation = startupErrorInformation; } else { errorInformation = new ErrorInformation(PwmError.ERROR_APP_UNAVAILABLE,"application is not yet available, please try again in a moment."); } throw new PwmUnrecoverableException(errorInformation); } return pwmApplication; } // -------------------------- OTHER METHODS -------------------------- public void initialize() { try { Locale.setDefault(PwmConstants.DEFAULT_LOCALE); } catch (Exception e) { outputError("unable to set default locale as Java machine default locale: " + e.getMessage()); } final EnvironmentTest[] tests = new EnvironmentTest[]{ new JavaVersionCheck(), }; for (final EnvironmentTest doTest : tests) { startupErrorInformation = doTest.doTest(); } Configuration configuration = null; PwmApplicationMode mode = PwmApplicationMode.ERROR; final ParameterReader parameterReader = new ParameterReader(servletContext); final File applicationPath; { final String applicationPathStr = parameterReader.readApplicationPath(); if (applicationPathStr == null || applicationPathStr.isEmpty()) { startupErrorInformation = new ErrorInformation(PwmError.ERROR_ENVIRONMENT_ERROR,"application path is not specified"); return; } else { applicationPath = new File(applicationPathStr); } } File configurationFile = null; try { configurationFile = locateConfigurationFile(applicationPath); configReader = new ConfigurationReader(configurationFile); configReader.getStoredConfiguration().lock(); configuration = configReader.getConfiguration(); if (configReader == null) { mode = startupErrorInformation == null ? PwmApplicationMode.ERROR : PwmApplicationMode.ERROR; } else { mode = startupErrorInformation == null ? configReader.getConfigMode() : PwmApplicationMode.ERROR; } if (startupErrorInformation == null) { startupErrorInformation = configReader.getConfigFileError(); } if (PwmApplicationMode.ERROR == mode) { outputError("Startup Error: " + (startupErrorInformation == null ? "un-specified error" : startupErrorInformation.toDebugStr())); } } catch (Throwable e) { handleStartupError("unable to initialize application due to configuration related error: ", e); } LOGGER.debug("configuration file was loaded from " + (configurationFile == null ? "null" : configurationFile.getAbsoluteFile())); final Collection<PwmEnvironment.ApplicationFlag> applicationFlags = parameterReader.readApplicationFlags(); final Map<PwmEnvironment.ApplicationParameter,String> applicationParams = parameterReader.readApplicationParams(); try { final PwmEnvironment pwmEnvironment= new PwmEnvironment.Builder(configuration, applicationPath) .setApplicationMode(mode) .setConfigurationFile(configurationFile) .setContextManager(this) .setFlags(applicationFlags) .setParams(applicationParams) .createPwmEnvironment(); pwmApplication = new PwmApplication(pwmEnvironment); } catch (Exception e) { handleStartupError("unable to initialize application: ", e); } final String threadName = JavaHelper.makeThreadName(pwmApplication, this.getClass()) + " timer"; taskMaster = new Timer(threadName, true); taskMaster.schedule(new RestartFlagWatcher(), 1031, 1031); boolean reloadOnChange = true; long fileScanFrequencyMs = 5000; { if (pwmApplication != null) { reloadOnChange = Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.CONFIG_RELOAD_ON_CHANGE)); fileScanFrequencyMs = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.CONFIG_FILE_SCAN_FREQUENCY)); } if (reloadOnChange) { taskMaster.schedule(new ConfigFileWatcher(), fileScanFrequencyMs, fileScanFrequencyMs); } checkConfigForSaveOnRestart(configReader, pwmApplication); } } private void checkConfigForSaveOnRestart( final ConfigurationReader configReader, final PwmApplication pwmApplication ) { if (configReader == null || configReader.getStoredConfiguration() == null) { return; } final String saveConfigOnRestartStrValue = configReader.getStoredConfiguration().readConfigProperty( ConfigurationProperty.CONFIG_ON_START); if (saveConfigOnRestartStrValue == null || !Boolean.parseBoolean(saveConfigOnRestartStrValue)) { return; } LOGGER.warn("configuration file contains property \"" + ConfigurationProperty.CONFIG_ON_START + "\"=true, will save configuration and set property to false."); try { final StoredConfigurationImpl newConfig = StoredConfigurationImpl.copy(configReader.getStoredConfiguration()); newConfig.writeConfigProperty(ConfigurationProperty.CONFIG_ON_START, "false"); configReader.saveConfiguration(newConfig, pwmApplication, null); restartRequestedFlag = true; } catch (Exception e) { LOGGER.error("error while saving configuration file commanded by property \"" + ConfigurationProperty.CONFIG_ON_START + "\"=true, error: " + e.getMessage()); } } private void handleStartupError(final String msgPrefix, final Throwable throwable) { final String errorMsg; if (throwable instanceof OutOfMemoryError) { errorMsg = "JAVA OUT OF MEMORY ERROR!, please allocate more memory for java: " + throwable.getMessage(); startupErrorInformation = new ErrorInformation(PwmError.ERROR_STARTUP_ERROR,errorMsg); } else if (throwable instanceof PwmException) { startupErrorInformation = ((PwmException)throwable).getErrorInformation().wrapWithNewErrorCode(PwmError.ERROR_STARTUP_ERROR); } else { errorMsg = throwable.getMessage(); startupErrorInformation = new ErrorInformation(PwmError.ERROR_APP_UNAVAILABLE, msgPrefix + errorMsg); throwable.printStackTrace(); } try { LOGGER.fatal(startupErrorInformation.getDetailedErrorMsg()); } catch (Exception e2) { // noop } outputError(startupErrorInformation.getDetailedErrorMsg()); } public void shutdown() { startupErrorInformation = new ErrorInformation(PwmError.ERROR_APP_UNAVAILABLE, "shutting down"); if (pwmApplication != null) { try { pwmApplication.shutdown(); } catch (Exception e) { LOGGER.error("unexpected error attempting to close application: " + e.getMessage()); } } taskMaster.cancel(); this.pwmApplication = null; startupErrorInformation = null; } public void requestPwmApplicationRestart() { restartRequestedFlag = true; try { taskMaster.schedule(new ConfigFileWatcher(),0); } catch (IllegalStateException e) { LOGGER.debug("could not schedule config file watcher, timer is in illegal state: " + e.getMessage()); } } public ConfigurationReader getConfigReader() { return configReader; } private class ConfigFileWatcher extends TimerTask { @Override public void run() { if (configReader != null) { if (configReader.modifiedSinceLoad()) { LOGGER.info("configuration file modification has been detected"); restartRequestedFlag = true; } } } } private class RestartFlagWatcher extends TimerTask { public void run() { if (restartRequestedFlag) { doReinitialize(); } } private void doReinitialize() { if (configReader != null && configReader.isSaveInProgress()) { LOGGER.info("delaying restart request due to in progress file save"); return; } LOGGER.info("beginning application restart"); try { shutdown(); } catch (Exception e) { LOGGER.fatal("unexpected error during shutdown: " + e.getMessage(),e); } LOGGER.info("application restart; shutdown completed, now starting new application instance"); restartCount++; initialize(); LOGGER.info("application restart completed"); restartRequestedFlag = false; } } public ErrorInformation getStartupErrorInformation() { return startupErrorInformation; } private interface EnvironmentTest { ErrorInformation doTest(); } private static class JavaVersionCheck implements EnvironmentTest { public ErrorInformation doTest() { String stringVersion = java.lang.System.getProperty("java.version"); stringVersion = stringVersion.substring(0, 3); final Float f = Float.valueOf(stringVersion); if (f < PwmConstants.JAVA_MINIMUM_VERSION) { final String errorMsg = "the minimum java version required is Java v" + PwmConstants.JAVA_MINIMUM_VERSION; outputError(errorMsg); LOGGER.fatal(errorMsg); return new ErrorInformation(PwmError.ERROR_APP_UNAVAILABLE, errorMsg); } return null; } } public int getRestartCount() { return restartCount; } public File locateConfigurationFile(final File applicationPath) throws Exception { return new File(applicationPath.getAbsolutePath() + File.separator + PwmConstants.DEFAULT_CONFIG_FILE_FILENAME); } public File locateWebInfFilePath() { final String realPath = servletContext.getRealPath("/WEB-INF"); if (realPath != null) { final File servletPath = new File(realPath); if (servletPath.exists()) { return servletPath; } } return null; } static void outputError(final String outputText) { final String msg = PwmConstants.PWM_APP_NAME + " " + JavaHelper.toIsoDate(new Date()) + " " + outputText; System.out.println(msg); System.out.println(msg); } public String getInstanceGuid() { return instanceGuid; } public InputStream getResourceAsStream(final String path) { return servletContext.getResourceAsStream(path); } private static class ParameterReader { private final ServletContext servletContext; ParameterReader(final ServletContext servletContext) { this.servletContext = servletContext; } String readApplicationPath() { final String contextAppPathSetting = readEnvironmentParameter(PwmEnvironment.EnvironmentParameter.applicationPath); if (contextAppPathSetting != null) { return contextAppPathSetting; } final String contextPath = servletContext.getContextPath().replace("/", ""); return PwmEnvironment.ParseHelper.readValueFromSystem( PwmEnvironment.EnvironmentParameter.applicationPath, contextPath ); } Collection<PwmEnvironment.ApplicationFlag> readApplicationFlags() { final String contextAppFlagsValue = readEnvironmentParameter(PwmEnvironment.EnvironmentParameter.applicationFlags); if (contextAppFlagsValue != null && !contextAppFlagsValue.isEmpty()) { return PwmEnvironment.ParseHelper.parseApplicationFlagValueParameter(contextAppFlagsValue); } final String contextPath = servletContext.getContextPath().replace("/", ""); return PwmEnvironment.ParseHelper.readApplicationFlagsFromSystem(contextPath); } Map<PwmEnvironment.ApplicationParameter, String> readApplicationParams() { final String contextAppParamsValue = readEnvironmentParameter(PwmEnvironment.EnvironmentParameter.applicationParamFile); if (contextAppParamsValue != null && !contextAppParamsValue.isEmpty()) { return PwmEnvironment.ParseHelper.parseApplicationParamValueParameter(contextAppParamsValue); } final String contextPath = servletContext.getContextPath().replace("/", ""); return PwmEnvironment.ParseHelper.readApplicationParmsFromSystem(contextPath); } private String readEnvironmentParameter(final PwmEnvironment.EnvironmentParameter environmentParameter) { final String value = servletContext.getInitParameter( environmentParameter.toString()); if (value != null && !value.isEmpty()) { if (!UNSPECIFIED_VALUE.equalsIgnoreCase(value)) { return value; } } return null; } } public String getContextPath() { return contextPath; } }