// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. package net.sourceforge.jnlp.runtime; import java.awt.EventQueue; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.Authenticator; import java.net.ProxySelector; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.security.AllPermission; import java.security.KeyStore; import java.security.Policy; import java.security.Security; import java.text.MessageFormat; import java.util.List; import java.util.ResourceBundle; import javax.jnlp.ServiceManager; import javax.naming.ConfigurationException; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.swing.UIManager; import javax.swing.text.html.parser.ParserDelegator; import net.sourceforge.jnlp.DefaultLaunchHandler; import net.sourceforge.jnlp.GuiLaunchHandler; import net.sourceforge.jnlp.LaunchHandler; import net.sourceforge.jnlp.Launcher; import net.sourceforge.jnlp.browser.BrowserAwareProxySelector; import net.sourceforge.jnlp.cache.CacheUtil; import net.sourceforge.jnlp.cache.DefaultDownloadIndicator; import net.sourceforge.jnlp.cache.DownloadIndicator; import net.sourceforge.jnlp.cache.UpdatePolicy; import net.sourceforge.jnlp.config.DeploymentConfiguration; import net.sourceforge.jnlp.security.JNLPAuthenticator; import net.sourceforge.jnlp.security.KeyStores; import net.sourceforge.jnlp.security.SecurityDialogMessageHandler; import net.sourceforge.jnlp.services.XServiceManagerStub; import net.sourceforge.jnlp.util.FileUtils; import net.sourceforge.jnlp.util.logging.JavaConsole; import net.sourceforge.jnlp.util.logging.OutputController; import net.sourceforge.jnlp.util.logging.LogConfig; import sun.net.www.protocol.jar.URLJarFile; /** * Configure and access the runtime environment. This class * stores global jnlp properties such as default download * indicators, the install/base directory, the default resource * update policy, etc. Some settings, such as the base directory, * cannot be changed once the runtime has been initialized.
* * The JNLP runtime can be locked to prevent further changes to * the runtime environment except by a specified class. If set, * only instances of the exit class can exit the JVM or * change the JNLP runtime settings once the runtime has been * initialized.
*
* @author Jon A. Maxwell (JAM) - initial author
* @version $Revision: 1.19 $
*/
public class JNLPRuntime {
static {
loadResources();
}
/** the localized resource strings */
private static ResourceBundle resources;
private static DeploymentConfiguration config;
/** the security manager */
private static JNLPSecurityManager security;
/** the security policy */
private static JNLPPolicy policy;
/** handles all security message to show appropriate security dialogs */
private static SecurityDialogMessageHandler securityDialogMessageHandler;
/** a default launch handler */
private static LaunchHandler handler = null;
/** default download indicator */
private static DownloadIndicator indicator = null;
/** update policy that controls when to check for updates */
private static UpdatePolicy updatePolicy = UpdatePolicy.ALWAYS;
/** whether initialized */
private static boolean initialized = false;
/** whether netx is in command-line mode (headless) */
private static boolean headless = false;
/** whether we'll be checking for jar signing */
private static boolean verify = true;
/** whether the runtime uses security */
private static boolean securityEnabled = true;
/** whether debug mode is on */
private static boolean debug = false;
/**
* whether plugin debug mode is on
*/
private static Boolean pluginDebug = null;
/** mutex to wait on, for initialization */
public static Object initMutex = new Object();
/** set to true if this is a webstart application. */
private static boolean isWebstartApplication;
/** set to false to indicate another JVM should not be spawned, even if necessary */
private static boolean forksAllowed = true;
/** all security dialogs will be consumed and pretented as being verified by user and allowed.*/
private static boolean trustAll=false;
/**
* Header is not checked and so eg. gifar exploit is possible
* @see http://en.wikipedia.org/wiki/Gifar for this kind of attack.
* However if jar file is a bit corrupted, then it sometimes can work so
* this switch can disable the header check.
*
*/
private static boolean ignoreHeaders=false;
/** contains the arguments passed to the jnlp runtime */
private static List
*
* This method should be called from the main AppContext/Thread.
*
* This method cannot be called more than once. Once
* initialized, methods that alter the runtime can only be
* called by the exit class.
*
* @param isApplication is true if a webstart application is being initialized
*
* @throws IllegalStateException if the runtime was previously initialized
*/
public static void initialize(boolean isApplication) throws IllegalStateException {
checkInitialized();
if (JavaConsole.canShowOnStartup(isApplication)) {
JavaConsole.getConsole().showConsoleLater();
}
/* exit if there is a fatal exception loading the configuration */
if (isApplication && getConfiguration().getLoadingException() != null) {
OutputController.getLogger().log(OutputController.Level.WARNING_ALL, getMessage("RConfigurationError")+": "+getConfiguration().getLoadingException().getMessage());
}
KeyStores.setConfiguration(getConfiguration());
isWebstartApplication = isApplication;
//Setting the system property for javawebstart's version.
//The version stored will be the same as java's version.
System.setProperty("javawebstart.version", "javaws-" +
System.getProperty("java.version"));
if (headless == false)
checkHeadless();
if (!headless && indicator == null)
indicator = new DefaultDownloadIndicator();
if (handler == null) {
if (headless) {
handler = new DefaultLaunchHandler(OutputController.getLogger());
} else {
handler = new GuiLaunchHandler(OutputController.getLogger());
}
}
ServiceManager.setServiceManagerStub(new XServiceManagerStub()); // ignored if we're running under Web Start
policy = new JNLPPolicy();
security = new JNLPSecurityManager(); // side effect: create JWindow
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e);
}
doMainAppContextHacks();
if (securityEnabled) {
Policy.setPolicy(policy); // do first b/c our SM blocks setPolicy
System.setSecurityManager(security);
}
securityDialogMessageHandler = startSecurityThreads();
// wire in custom authenticator for SSL connections
try {
SSLSocketFactory sslSocketFactory;
SSLContext context = SSLContext.getInstance("SSL");
KeyStore ks = KeyStores.getKeyStore(KeyStores.Level.USER, KeyStores.Type.CLIENT_CERTS);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, KeyStores.getPassword());
TrustManager[] trust = new TrustManager[] { getSSLSocketTrustManager() };
context.init(kmf.getKeyManagers(), trust, null);
sslSocketFactory = context.getSocketFactory();
HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
} catch (Exception e) {
OutputController.getLogger().log(OutputController.Level.ERROR_ALL, "Unable to set SSLSocketfactory (may _prevent_ access to sites that should be trusted)! Continuing anyway...");
OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e);
}
// plug in a custom authenticator and proxy selector
Authenticator.setDefault(new JNLPAuthenticator());
BrowserAwareProxySelector proxySelector = new BrowserAwareProxySelector(getConfiguration());
proxySelector.initialize();
ProxySelector.setDefault(proxySelector);
// Restrict access to netx classes
Security.setProperty("package.access",
Security.getProperty("package.access")+",net.sourceforge.jnlp");
URLJarFile.setCallBack(CachedJarFileCallback.getInstance());
initialized = true;
}
/**
* Returns a TrustManager ideal for the running VM.
*
* @return TrustManager the trust manager to use for verifying https certificates
*/
private static TrustManager getSSLSocketTrustManager() throws
ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
try {
Class> trustManagerClass;
Constructor> tmCtor = null;
if (System.getProperty("java.version").startsWith("1.6")) { // Java 6
try {
trustManagerClass = Class.forName("net.sourceforge.jnlp.security.VariableX509TrustManagerJDK6");
} catch (ClassNotFoundException cnfe) {
OutputController.getLogger().log(OutputController.Level.ERROR_ALL, "Unable to find class net.sourceforge.jnlp.security.VariableX509TrustManagerJDK6");
return null;
}
} else { // Java 7 or more (technically could be <= 1.5 but <= 1.5 is unsupported)
try {
trustManagerClass = Class.forName("net.sourceforge.jnlp.security.VariableX509TrustManagerJDK7");
} catch (ClassNotFoundException cnfe) {
OutputController.getLogger().log(OutputController.Level.ERROR_ALL, "Unable to find class net.sourceforge.jnlp.security.VariableX509TrustManagerJDK7");
return null;
}
}
Constructor>[] tmCtors = trustManagerClass.getDeclaredConstructors();
tmCtor = tmCtors[0];
for (Constructor> ctor : tmCtors) {
if (tmCtor.getGenericParameterTypes().length == 0) {
tmCtor = ctor;
break;
}
}
return (TrustManager) tmCtor.newInstance();
} catch (RuntimeException e) {
OutputController.getLogger().log(OutputController.Level.ERROR_ALL, "Unable to load JDK-specific TrustManager. Was this version of IcedTea-Web compiled with JDK 6 or 7?");
OutputController.getLogger().log(e);
throw e;
}
}
/**
* This must NOT be called form the application ThreadGroup. An application
* can inject events into its {@link EventQueue} and bypass the security
* dialogs.
*
* @return a {@link SecurityDialogMessageHandler} that can be used to post
* security messages
*/
private static SecurityDialogMessageHandler startSecurityThreads() {
ThreadGroup securityThreadGroup = new ThreadGroup("NetxSecurityThreadGroup");
SecurityDialogMessageHandler runner = new SecurityDialogMessageHandler();
Thread securityThread = new Thread(securityThreadGroup, runner, "NetxSecurityThread");
securityThread.setDaemon(true);
securityThread.start();
return runner;
}
/**
* Performs a few hacks that are needed for the main AppContext
*
* @see Launcher#doPerApplicationAppContextHacks
*/
private static void doMainAppContextHacks() {
/*
* With OpenJDK6 (but not with 7) a per-AppContext dtd is maintained.
* This dtd is created by the ParserDelgate. However, the code in
* HTMLEditorKit (used to render HTML in labels and textpanes) creates
* the ParserDelegate only if there are no existing ParserDelegates. The
* result is that all other AppContexts see a null dtd.
*/
new ParserDelegator();
}
/**
* Gets the Configuration associated with this runtime
* @return a {@link DeploymentConfiguration} object that can be queried to
* find relevant configuration settings
*/
public synchronized static DeploymentConfiguration getConfiguration() {
if (config == null){
config = new DeploymentConfiguration();
try{
config.load();
config.copyTo(System.getProperties());
}catch(ConfigurationException ex){
OutputController.getLogger().log(ex);
//mark first occurence of exception so we can react later
if (config.getLoadingException() == null){
config.setLoadingException(ex);
}
}
}
return config;
}
/**
* Returns true if a webstart application has been initialized, and false
* for a plugin applet.
*/
public static boolean isWebstartApplication() {
return isWebstartApplication;
}
/**
* Returns whether the JNLP client will use any AWT/Swing
* components.
*/
public static boolean isHeadless() {
return headless;
}
/**
* Returns whether we are verifying code signing.
*/
public static boolean isVerifying() {
return verify;
}
/**
* Sets whether the JNLP client will use any AWT/Swing
* components. In headless mode, client features that use the
* AWT are disabled such that the client can be used in
* headless mode (
*
* @param enabled whether security should be enabled
* @throws IllegalStateException if the runtime is already initialized
*/
public static void setSecurityEnabled(boolean enabled) {
checkInitialized();
securityEnabled = enabled;
}
/**
*
* @return the {@link SecurityDialogMessageHandler} that should be used to
* post security dialog messages
*/
public static SecurityDialogMessageHandler getSecurityDialogHandler() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new AllPermission());
}
return securityDialogMessageHandler;
}
/**
* Set a class that can exit the JVM; if not set then any class
* can exit the JVM.
*
* @throws IllegalStateException if caller is not the exit class
*/
public static void setExitClass(Class> exitClass) {
checkExitClass();
security.setExitClass(exitClass);
}
/**
* Disables applets from calling exit.
*
* Once disabled, exit cannot be re-enabled for the duration of the JVM instance
*/
public static void disableExit() {
security.disableExit();
}
/**
* Return the current Application, or null if none can be
* determined.
*/
public static ApplicationInstance getApplication() {
return security.getApplication();
}
/**
* Return whether debug statements for the JNLP client code
* should be printed.
*/
public static boolean isDebug() {
return isSetDebug() || isPluginDebug() || LogConfig.getLogConfig().isEnableLogging();
}
public static boolean isSetDebug() {
return debug;
}
/**
* Sets whether debug statements for the JNLP client code
* should be printed to the standard output.
*
* @throws IllegalStateException if caller is not the exit class
*/
public static void setDebug(boolean enabled) {
checkExitClass();
debug = enabled;
}
/**
* Sets the default update policy.
*
* @throws IllegalStateException if caller is not the exit class
*/
public static void setDefaultUpdatePolicy(UpdatePolicy policy) {
checkExitClass();
updatePolicy = policy;
}
/**
* Returns the default update policy.
*/
public static UpdatePolicy getDefaultUpdatePolicy() {
return updatePolicy;
}
/**
* Sets the default launch handler.
*/
public static void setDefaultLaunchHandler(LaunchHandler handler) {
checkExitClass();
JNLPRuntime.handler = handler;
}
/**
* Returns the default launch handler.
*/
public static LaunchHandler getDefaultLaunchHandler() {
return handler;
}
/**
* Sets the default download indicator.
*
* @throws IllegalStateException if caller is not the exit class
*/
public static void setDefaultDownloadIndicator(DownloadIndicator indicator) {
checkExitClass();
JNLPRuntime.indicator = indicator;
}
/**
* Returns the default download indicator.
*/
public static DownloadIndicator getDefaultDownloadIndicator() {
return indicator;
}
/**
* Returns the localized resource string identified by the
* specified key. If the message is empty, a null is
* returned.
*/
public static String getMessage(String key) {
try {
String result = resources.getString(key);
if (result.length() == 0)
return null;
else
return result;
} catch (Exception ex) {
if (!key.equals("RNoResource"))
return getMessage("RNoResource", new Object[] { key });
else
return "Missing resource: " + key;
}
}
/**
* Returns the localized resource string using the specified
* arguments.
*
* @param args the formatting arguments to the resource string
*/
public static String getMessage(String key, Object... args) {
return MessageFormat.format(getMessage(key), args);
}
/**
* Returns true if the current runtime will fork
*/
public static boolean getForksAllowed() {
return forksAllowed;
}
public static void setForksAllowed(boolean value) {
checkInitialized();
forksAllowed = value;
}
/**
* Throws an exception if called when the runtime is
* already initialized.
*/
private static void checkInitialized() {
if (initialized)
throw new IllegalStateException("JNLPRuntime already initialized.");
}
/**
* Throws an exception if called with security enabled but
* a caller is not the exit class and the runtime has been
* initialized.
*/
private static void checkExitClass() {
if (securityEnabled && initialized)
if (!security.isExitClass())
throw new IllegalStateException("Caller is not the exit class");
}
/**
* Check whether the VM is in headless mode.
*/
private static void checkHeadless() {
//if (GraphicsEnvironment.isHeadless()) // jdk1.4+ only
// headless = true;
try {
if ("true".equalsIgnoreCase(System.getProperty("java.awt.headless")))
headless = true;
} catch (SecurityException ex) {
}
}
/**
* Load the resources.
*/
private static void loadResources() {
try {
resources = ResourceBundle.getBundle("net.sourceforge.jnlp.resources.Messages");
} catch (Exception ex) {
throw new IllegalStateException("Missing resource bundle in netx.jar:net/sourceforge/jnlp/resource/Messages.properties");
}
}
/**
* @return true if running on Windows
*/
public static boolean isWindows() {
String os = System.getProperty("os.name");
return (os != null && os.startsWith("Windows"));
}
/**
* @return true if running on a Unix or Unix-like system (including Linux
* and *BSD)
*/
public static boolean isUnix() {
String sep = System.getProperty("file.separator");
return (sep != null && sep.equals("/"));
}
public static void setInitialArgments(Listjava.awt.headless=true
).
*
* @throws IllegalStateException if the runtime was previously initialized
*/
public static void setHeadless(boolean enabled) {
checkInitialized();
headless = enabled;
}
/**
* Sets whether we will verify code signing.
* @throws IllegalStateException if the runtime was previously initialized
*/
public static void setVerify(boolean enabled) {
checkInitialized();
verify = enabled;
}
/**
* Returns whether the secure runtime environment is enabled.
*/
public static boolean isSecurityEnabled() {
return securityEnabled;
}
/**
* Sets whether to enable the secure runtime environment.
* Disabling security can increase performance for some
* applications, and can be used to use netx with other code
* that uses its own security manager or policy.
*
* Disabling security is not recommended and should only be
* used if the JNLP files opened are trusted. This method can
* only be called before initalizing the runtime.