diff options
Diffstat (limited to 'netx/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java')
-rw-r--r-- | netx/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java | 541 |
1 files changed, 541 insertions, 0 deletions
diff --git a/netx/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java b/netx/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java new file mode 100644 index 0000000..3934607 --- /dev/null +++ b/netx/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java @@ -0,0 +1,541 @@ +// 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.Frame; +import java.awt.Window; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.lang.ref.WeakReference; +import java.net.SocketPermission; +import java.security.AllPermission; +import java.security.AccessControlException; +import java.security.AccessController; +import java.security.Permission; +import java.security.PrivilegedAction; +import java.security.SecurityPermission; +import java.util.PropertyPermission; + +import javax.swing.JWindow; + +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.security.SecurityWarningDialog; +import net.sourceforge.jnlp.services.ServiceUtil; +import net.sourceforge.jnlp.util.WeakList; +import sun.awt.AWTSecurityManager; +import sun.awt.AppContext; +import sun.security.util.SecurityConstants; + +/** + * Security manager for JNLP environment. This security manager + * cannot be replaced as it always denies attempts to replace the + * security manager or policy.<p> + * + * The JNLP security manager tracks windows created by an + * application, allowing those windows to be disposed when the + * application exits but the JVM does not. If security is not + * enabled then the first application to call System.exit will + * halt the JVM.<p> + * + * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.17 $ + */ +class JNLPSecurityManager extends AWTSecurityManager { + + // todo: some apps like JDiskReport can close the VM even when + // an exit class is set - fix! + + // todo: create an event dispatch thread for each application, + // so that the context classloader doesn't have to be switched + // to the foreground application (the currently the approach + // since some apps need their classloader as event dispatch + // thread's context classloader). + + // todo: use a custom Permission object to identify the current + // application in an AccessControlContext by setting a side + // effect in its implies method. Use a custom + // AllPermissions-like permission to do this for apps granted + // all permissions (but investigate whether this will nuke + // the all-permission optimizations in the JRE). + + // todo: does not exit app if close button pressed on JFrame + // with CLOSE_ON_EXIT (or whatever) set; if doesn't exit, use an + // WindowListener to catch WindowClosing event, then if exit is + // called immediately afterwards from AWT thread. + + // todo: deny all permissions to applications that should have + // already been 'shut down' by closing their resources and + // interrupt the threads if operating in a shared-VM (exit class + // set). Deny will probably will slow checks down a lot though. + + // todo: weak remember last getProperty application and + // re-install properties if another application calls, or find + // another way for different apps to have different properties + // in java.lang.Sytem with the same names. + + private static String R(String key) { return JNLPRuntime.getMessage(key); } + + /** only class that can exit the JVM, if set */ + private Object exitClass = null; + + /** this exception prevents exiting the JVM */ + private SecurityException closeAppEx = // making here prevents huge stack traces + new SecurityException(JNLPRuntime.getMessage("RShutdown")); + + /** weak list of windows created */ + private WeakList weakWindows = new WeakList(); + + /** weak list of applications corresponding to window list */ + private WeakList weakApplications = new WeakList(); + + /** weak reference to most app who's windows was most recently activated */ + private WeakReference activeApplication = null; + + /** Sets whether or not exit is allowed (in the context of the plugin, this is always false) */ + private boolean exitAllowed = true; + + /** + * The AppContext of the main application (netx). We need to store this here + * so we can return this when no code from an external application is + * running on the thread + */ + private AppContext mainAppContext; + + /** + * Creates a JNLP SecurityManager. + */ + JNLPSecurityManager() { + // this has the side-effect of creating the Swing shared Frame + // owner. Since no application is running at this time, it is + // not added to any window list when checkTopLevelWindow is + // called for it (and not disposed). + + if (!JNLPRuntime.isHeadless()) + new JWindow().getOwner(); + + mainAppContext = AppContext.getAppContext(); + } + + /** + * Returns whether the exit class is present on the stack, or + * true if no exit class is set. + */ + public boolean isExitClass() { + return isExitClass(getClassContext()); + } + + /** + * Returns whether the exit class is present on the stack, or + * true if no exit class is set. + */ + private boolean isExitClass(Class stack[]) { + if (exitClass == null) + return true; + + for (int i=0; i < stack.length; i++) + if (stack[i] == exitClass) + return true; + + return false; + } + + /** + * Set the exit class, which is the only class that can exit the + * JVM; if not set then any class can exit the JVM. + * + * @param exitClass the exit class + * @throws IllegalStateException if the exit class is already set + */ + public void setExitClass(Class exitClass) throws IllegalStateException { + if (this.exitClass != null) + throw new IllegalStateException(R("RExitTaken")); + + this.exitClass = exitClass; + } + + /** + * Return the current Application, or null if none can be + * determined. + */ + protected ApplicationInstance getApplication() { + return getApplication(getClassContext(), 0); + } + + /** + * Return the application the opened the specified window (only + * call from event dispatch thread). + */ + protected ApplicationInstance getApplication(Window window) { + for (int i = weakWindows.size(); i-->0;) { + Window w = (Window) weakWindows.get(i); + if (w == null) { + weakWindows.remove(i); + weakApplications.remove(i); + } + + if (w == window) + return (ApplicationInstance) weakApplications.get(i); + } + + return null; + } + + /** + * Return the current Application, or null. + */ + protected ApplicationInstance getApplication(Class stack[], int maxDepth) { + if (maxDepth <= 0) + maxDepth = stack.length; + + // this needs to be tightened up + for (int i=0; i < stack.length && i < maxDepth; i++) { + if (stack[i].getClassLoader() instanceof JNLPClassLoader) { + JNLPClassLoader loader = (JNLPClassLoader) stack[i].getClassLoader(); + + if (loader != null && loader.getApplication() != null) { + return loader.getApplication(); + } + } + } + + return null; + } + + /** + * Returns the application's thread group if the application can + * be determined; otherwise returns super.getThreadGroup() + */ + public ThreadGroup getThreadGroup() { + ApplicationInstance app = getApplication(); + if (app == null) + return super.getThreadGroup(); + + return app.getThreadGroup(); + } + + /** + * Throws a SecurityException if the permission is denied, + * otherwise return normally. This method always denies + * permission to change the security manager or policy. + */ + public void checkPermission(Permission perm) { + String name = perm.getName(); + + // Enable this manually -- it'll produce too much output for -verbose + // otherwise. + // if (true) + // System.out.println("Checking permission: " + perm.toString()); + + if (!JNLPRuntime.isWebstartApplication() && + ("setPolicy".equals(name) || "setSecurityManager".equals(name))) + throw new SecurityException(R("RCantReplaceSM")); + + try { + // deny all permissions to stopped applications + // The call to getApplication() below might not work if an + // application hasn't been fully initialized yet. +// if (JNLPRuntime.isDebug()) { +// if (!"getClassLoader".equals(name)) { +// ApplicationInstance app = getApplication(); +// if (app != null && !app.isRunning()) +// throw new SecurityException(R("RDenyStopped")); +// } +// } + + try { + super.checkPermission(perm); + } catch (SecurityException se) { + + //This section is a special case for dealing with SocketPermissions. + if (JNLPRuntime.isDebug()) + System.err.println("Requesting permission: " + perm.toString()); + + //Change this SocketPermission's action to connect and accept + //(and resolve). This is to avoid asking for connect permission + //on every address resolve. + Permission tmpPerm = null; + if (perm instanceof SocketPermission) { + tmpPerm = new SocketPermission(perm.getName(), + SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION); + + // before proceeding, check if we are trying to connect to same origin + ApplicationInstance app = getApplication(); + JNLPFile file = app.getJNLPFile(); + + String srcHost = file.getSourceLocation().getAuthority(); + String destHost = name; + + // host = abc.xyz.com or abc.xyz.com:<port> + if (destHost.indexOf(':') >= 0) + destHost = destHost.substring(0, destHost.indexOf(':')); + + // host = abc.xyz.com + String[] hostComponents = destHost.split("\\."); + + int length = hostComponents.length; + if (length >= 2) { + + // address is in xxx.xxx.xxx format + destHost = hostComponents[length -2] + "." + hostComponents[length -1]; + + // host = xyz.com i.e. origin + boolean isDestHostName = false; + + // make sure that it is not an ip address + try { + Integer.parseInt(hostComponents[length -1]); + } catch (NumberFormatException e) { + isDestHostName = true; + } + + if (isDestHostName) { + // okay, destination is hostname. Now figure out if it is a subset of origin + if (srcHost.endsWith(destHost)) { + addPermission(tmpPerm); + return; + } + } + } + + } else if (perm instanceof SecurityPermission) { + + // JCE's initialization requires putProviderProperty permission + if (perm.equals(new SecurityPermission("putProviderProperty.SunJCE"))) { + if (inTrustedCallChain("com.sun.crypto.provider.SunJCE", "run")) { + return; + } + } + + } else if (perm instanceof RuntimePermission) { + + // KeyGenerator's init method requires internal spec access + if (perm.equals(new SecurityPermission("accessClassInPackage.sun.security.internal.spec"))) { + if (inTrustedCallChain("javax.crypto.KeyGenerator", "init")) { + return; + } + } + + } else { + tmpPerm = perm; + } + + if (tmpPerm != null) { + //askPermission will only prompt the user on SocketPermission + //meaning we're denying all other SecurityExceptions that may arise. + if (askPermission(tmpPerm)) { + addPermission(tmpPerm); + //return quietly. + } else { + throw se; + } + } + } + } + catch (SecurityException ex) { + if (JNLPRuntime.isDebug()) { + System.out.println("Denying permission: "+perm); + } + throw ex; + } + } + + /** + * Returns weather the given class and method are in the current stack, + * and whether or not everything upto then is trusted + * + * @param className The name of the class to look for in the stack + * @param methodName The name of the method for the given class to look for in the stack + * @return Weather or not class::method() are in the chain, and everything upto there is trusted + */ + private boolean inTrustedCallChain(String className, String methodName) { + + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + + for (int i=0; i < stack.length; i++) { + + // Everything up to the desired class/method must be trusted + if (!stack[i].getClass().getProtectionDomain().implies(new AllPermission())) { + return false; + } + + if (stack[i].getClassName().equals(className) && + stack[i].getMethodName().equals(methodName)) { + return true; + } + } + + return false; + } + + /** + * Asks the user whether or not to grant permission. + * @param perm the permission to be granted + * @return true if the permission was granted, false otherwise. + */ + private boolean askPermission(Permission perm) { + + ApplicationInstance app = getApplication(); + if (app != null && !app.isSigned()) { + if (perm instanceof SocketPermission + && ServiceUtil.checkAccess(SecurityWarningDialog.AccessType.NETWORK, perm.getName())) { + return true; + } + } + + return false; + } + + /** + * Adds a permission to the JNLPClassLoader. + * @param perm the permission to add to the JNLPClassLoader + */ + private void addPermission(Permission perm) { + if (JNLPRuntime.getApplication().getClassLoader() instanceof JNLPClassLoader) { + + JNLPClassLoader cl = (JNLPClassLoader) JNLPRuntime.getApplication().getClassLoader(); + cl.addPermission(perm); + if (JNLPRuntime.isDebug()) { + if (cl.getPermissions(null).implies(perm)) + System.err.println("Added permission: " + perm.toString()); + else + System.err.println("Unable to add permission: " + perm.toString()); + } + } else { + if (JNLPRuntime.isDebug()) + System.err.println("Unable to add permission: " + perm + ", classloader not JNLP."); + } + } + + /** + * Checks whether the window can be displayed without an applet + * warning banner, and adds the window to the list of windows to + * be disposed when the calling application exits. + */ + public boolean checkTopLevelWindow(Object window) { + ApplicationInstance app = getApplication(); + + // remember window -> application mapping for focus, close on exit + if (app != null && window instanceof Window) { + Window w = (Window) window; + + if (JNLPRuntime.isDebug()) + System.err.println("SM: app: "+app.getTitle()+" is adding a window: "+window); + + weakWindows.add(window); // for mapping window -> app + weakApplications.add(app); + + app.addWindow(w); + } + + // change coffee cup to netx for default icon + if (window instanceof Window) + for (Window w = (Window)window; w != null; w = w.getOwner()) + if (window instanceof Frame) + ((Frame)window).setIconImage(JNLPRuntime.getWindowIcon()); + + // todo: set awt.appletWarning to custom message + // todo: logo on with glass pane on JFrame/JWindow? + + return super.checkTopLevelWindow(window); + } + + /** + * Checks whether the caller can exit the system. This method + * identifies whether the caller is a real call to Runtime.exec + * and has special behavior when returning from this method + * would exit the JVM and an exit class is set: if the caller is + * not the exit class then the calling application will be + * stopped and its resources destroyed (when possible), and an + * exception will be thrown to prevent the JVM from shutting + * down.<p> + * + * Calls not from Runtime.exit or with no exit class set will + * behave normally, and the exit class can always exit the JVM. + */ + public void checkExit(int status) { + + // applets are not allowed to exit, but the plugin main class (primordial loader) is + Class stack[] = getClassContext(); + if (!exitAllowed) { + for (int i=0; i < stack.length; i++) + if (stack[i].getClassLoader() != null) + throw new AccessControlException("Applets may not call System.exit()"); + } + + super.checkExit(status); + + boolean realCall = (stack[1] == Runtime.class); + + if (isExitClass(stack)) // either exitClass called or no exitClass set + return; // to Runtime.exit or fake call to see if app has permission + + // not called from Runtime.exit() + if (!realCall) { + // apps that can't exit should think they can exit normally + super.checkExit(status); + return; + } + + // but when they really call, stop only the app instead of the JVM + ApplicationInstance app = getApplication(stack, 0); + if (app == null) { + // should check caller to make sure it is JFrame.close or + // other known System.exit call + if (activeApplication != null) + app = (ApplicationInstance) activeApplication.get(); + + if (app == null) + throw new SecurityException(R("RExitNoApp")); + } + + app.destroy(); + + throw closeAppEx; + } + + protected void disableExit() { + exitAllowed = false; + } + + /** + * This returns the appropriate {@link AppContext}. Hooks in AppContext + * check if the current {@link SecurityManager} is an instance of + * AWTSecurityManager and if so, call this method to give it a chance to + * return the appropriate appContext based on the application that is + * running.<p> + * + * This can be called from any thread (possibly a swing thread) to find out + * the AppContext for the thread (which may correspond to a particular + * applet). + */ + @Override + public AppContext getAppContext() { + ApplicationInstance app = getApplication(); + if (app == null) { + /* + * if we cannot find an application based on the code on the stack, + * then assume it is the main application + */ + return mainAppContext; + } else { + return app.getAppContext(); + } + + } + +} |