Milán Major

netscape-revival

1 branch
Code

sun-java/classsrc/netscape/applet/AppletSecurity.java

/*
 * @(#)AppletSecurity.java	1.29 95/12/08  
 *
 * Copyright (c) 1994 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL purposes and without
 * fee is hereby granted provided that this copyright notice
 * appears in all copies. Please refer to the file "copyright.html"
 * for further important copyright and licensing information.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 */

package netscape.applet;

import java.io.File;
import java.io.FileDescriptor;
import java.net.URL;
import java.net.InetAddress;
import java.util.StringTokenizer;
import java.util.Vector;
import java.lang.SecurityManager;

/**
 * This class defines an applet security policy
 *
 * @version 	1.29, 08 Dec 1995
 * @author	Sami Shaio
 * @author 	Arthur van Hoff
 */
public
class AppletSecurity extends SecurityManager {
    boolean initACL;
    String readACL[];
    String writeACL[];
    int networkMode;

    final static int NETWORK_NONE = 1;
    final static int NETWORK_HOST = 2;
    final static int NETWORK_UNRESTRICTED = 3;

    /**
     * Construct and initialize.
     */
    public AppletSecurity() {
	reset();
    }

    /**
     * Reset from Properties
     */
    void reset() {
	SecurityManager.setScopePermission();
	String str = System.getProperty("appletviewer.security.mode");
	SecurityManager.resetScopePermission();
	if (str == null) {
	    str = "host";
	}

	if (str.equals("unrestricted")) {
	    networkMode = NETWORK_UNRESTRICTED;
	} else if (str.equals("none")) {
	    networkMode = NETWORK_NONE;
	} else {
	    networkMode = NETWORK_HOST;
	}
    }

    /** 
     * Returns whether there is a security check in progress.
     * We are synchonized here, to block other threads from reading 
     * status that pertains to sibling threads (that are 
     * already running in checkConnect())  We can remove this method
     * if the super class is defined to run synchronized.
     */
    public synchronized boolean getInCheck() {
	return super.getInCheck();
    }

    /**
     * True if called directly from an applet.
     * XXX: Is the following used anywhere? It should be removed.
     */
    boolean fromApplet() {
	return checkClassLoader(1);
    }

    /**
     * True if called indirectly from an applet.
     */
    boolean inApplet() {
	return inClassLoader();
    }

    /**
     * The only variable that currently affects whether an applet can
     * perform certain operations is the host it came from.
     */
    public Object getSecurityContext() {
	AppletClassLoader loader = (AppletClassLoader)currentClassLoader();
	if (loader == null) {
	    return null;
	} else {
	    return loader.codeBaseURL;
	}
    }

    /**
     * Applets are not allowed to create class loaders.
     */
    public synchronized void checkCreateClassLoader(int caller_depth) {
	if (checkClassLoader(caller_depth+1)) {
	    throw new AppletSecurityException("classloader");
	}
    }

    /**
     * Applets are not allowed to manipulate threads outside
     * applet thread groups.
     */
    public synchronized void checkAccess(Thread t, int caller_depth) {
	if (!checkScopePermission(caller_depth+1)
                && !(t.getThreadGroup() instanceof AppletThreadGroup)) {
	    throw new AppletSecurityException("thread");
	}
    }

    /**
     * Applets are not allowed to send exceptions other than
     * ThreadDeath exception.
     */
    public synchronized void checkAccess(Thread t, Throwable o, int caller_depth) {
	if (!(o instanceof java.lang.ThreadDeath)
                && !checkScopePermission(caller_depth+1)) {
	    throw new AppletSecurityException("thread can't send exception");
	}
    }

    /**
     * Applets are not allowed to manipulate thread groups outside
     * applet thread groups.
     */
    public synchronized void checkAccess(ThreadGroup g, int caller_depth) {
	if (!checkScopePermission(caller_depth+1)
                && !(g instanceof AppletThreadGroup)) {
	    throw new AppletSecurityException("threadgroup", g.toString());
	}
    }

    /**
     * Applets are not allowed to exit the VM.
     */
    public synchronized void checkExit(int status) {
	if (inApplet()) {
	    throw new AppletSecurityException("exit", String.valueOf(status));
	}
    }

    /**
     * Applets are not allowed to fork processes.
     */
    public synchronized void checkExec(String cmd){
	if (inApplet()) {
	    throw new AppletSecurityException("exec", cmd);
	}
    }

    /**
     * Applets are not allowed to link dynamic libraries.
     */
    public synchronized void checkLink(String lib, int caller_depth){

        if (!checkScopePermission(caller_depth+1))
	    throw new AppletSecurityException("link", lib);
    }

    /**
     * Applets are not allowed to access the entire system properties
     * list, only properties explicitly labeled as accessible to applets.
     */
    public synchronized void checkPropertiesAccess(int caller_depth) {
	if (!checkScopePermission(caller_depth+1)) {
	    throw new AppletSecurityException("properties");
	}
    }

    /**
     * Applets can access the system property named by <i>key</i>
     * only if its twin <i>key.applet</i> property is set to true.
     * For example, the property <code>java.home</code> can be read by
     * applets only if <code>java.home.applet</code> is <code>true</code>.
     */
    public synchronized void checkPropertyAccess(String key, int caller_depth) {
	if (!checkScopePermission(caller_depth+1)) {
	    SecurityManager.setScopePermission();
	    if (!"true".equalsIgnoreCase(System.getProperty(key + ".applet"))) {
	        throw new AppletSecurityException("properties");
            }
	}
    }

    /**
     * Parse an ACL. Deals with "~" and "+"
     */
    void parseACL(Vector v, String path, String defaultPath) {
	SecurityManager.setScopePermission();
	StringTokenizer t = new StringTokenizer(path, System.getProperty("path.separator"));
	while (t.hasMoreTokens()) {
	    String dir = t.nextToken();
	    if (dir.startsWith("~")) {
		v.addElement(System.getProperty("user.home") + dir.substring(1));
	    } else if (dir.equals("+")) {
		if (defaultPath != null) {
		    parseACL(v, defaultPath, null);
		}
	    } else {
		v.addElement(dir);
	    }
	}
    }

    /**
     * Parse an ACL.
     */
    String[] parseACL(String path, String defaultPath) {
	if (path == null) {
	    return new String[0];
	}
	if (path.equals("*")) {
	    return null;
	}
	Vector v = new Vector();
	parseACL(v, path, defaultPath);

	String acl[] = new String[v.size()];
	v.copyInto(acl);
	return acl;
    }

    /**
     * Initialize ACLs. Called only once.
     */
    void initializeACLs() {
	SecurityManager.setScopePermission();
	readACL = parseACL(System.getProperty("acl.read"), 
			   System.getProperty("acl.read.default"));
	writeACL = parseACL(System.getProperty("acl.write"), 
			    System.getProperty("acl.write.default"));
	initACL = true;
    }

    /**
     * Check if an applet can read a particular file.
     */
    public synchronized void checkRead(String file) {
	AppletClassLoader loader = (AppletClassLoader)currentClassLoader();
	if (loader != null) {
	    checkRead(file, loader.codeBaseURL);
	}
    }

    public synchronized void checkRead(String file, URL base) {
	if (base != null) {
	    if (!initACL) {
		initializeACLs();
	    }
	    if (readACL == null) {
		return;
	    }
	    for (int i = readACL.length ; i-- > 0 ;) {
		if (file.startsWith(readACL[i])) {
		    return;
		}
	    }
	    // if the applet is loaded from a file URL, allow reading
	    // in that directory
	    if (base.getProtocol().equals("file")) {
		// URL.getFile() should really be changed to return a native
		// path so that this conversion would not be necessary, but
		// that's too risky right now. -- DAC
		String dir = base.getFile().replace('/', File.separatorChar);
		if (file.startsWith(dir)) {
		    return;
		}
	    }
	    
	    throw new AppletSecurityException("file.read", file);
	}
    }

    /**
     * Checks to see if the current context or the indicated context are
     * both allowed to read the given file name.
     * @param file the system dependent file name
     * @param context the alternate execution context which must also
     * be checked
     * @exception  SecurityException If the file is not found.
     */
    public void checkRead(String file, Object context) {
	checkRead(file);
	if (context != null) {
	    checkRead(file, (URL) context);
	}
    }

    /**
     * Check if an applet can write a particular file.
     */
    public synchronized void checkWrite(String file) {
	if (inApplet()) {
	    if (!initACL) {
		initializeACLs();
	    }
	    if (writeACL == null) {
		return;
	    }
	    for (int i = writeACL.length ; i-- > 0 ;) {
		if (file.startsWith(writeACL[i])) {
		    return;
		}
	    }
	    throw new AppletSecurityException("file.write", file);
	}
    }

    /**
     * Applets are not allowed to open file descriptors unless
     * it is done through a socket, in which case other access
     * restrictions still apply.
     */
    public synchronized void checkRead(FileDescriptor fd) {
	if ((inApplet() && !inClass("java.net.SocketInputStream"))
	    || (!fd.valid()) ) {
	    throw new AppletSecurityException("fd.read");
	}
    }

    /**
     * Applets are not allowed to open file descriptors unless
     * it is done through a socket, in which case other access
     * restrictions still apply.
     */
    public synchronized void checkWrite(FileDescriptor fd) {
	if ( (inApplet() && !inClass("java.net.SocketOutputStream")) 
	     || (!fd.valid()) ) {
	    throw new AppletSecurityException("fd.write");
	}
    }

    /**
     * For now applets can't listen on any port.
     */
    public synchronized void checkListen(int port) {
	AppletClassLoader loader = (AppletClassLoader)currentClassLoader();
	if (loader == null) {
	    // Not called from an applet, so it is ok
	    return;
	}
	if (port > 1024) {
	    // Applets are not allowed to listen on ports that are
	    // considered to be system services
	    return;
	}
	throw new AppletSecurityException("socket.listen", String.valueOf(port));
    }

    /**
     * Check if an applet can accept a connection from the given host:port.
     * This is called after the connection has come in. One of the assumptions
     * that this makes is that the incoming address is valid (otherwise the
     * originating host check won't work). Of course, we are making that
     * assumption on the connect side as well.
     */
    public synchronized void checkAccept(String host, int port) {
	AppletClassLoader loader = (AppletClassLoader)currentClassLoader();
	if (loader == null) {
	    // Not called from an applet, so it is ok
	    return;
	}
	checkConnect(loader.codeBaseURL.getHost(), host);
    }

    /**
     * Check if an applet can connect to the given host:port.
     */
    public synchronized void checkConnect(String host, int port) {
	AppletClassLoader loader = (AppletClassLoader)currentClassLoader();
	if (loader == null) {
	    // Not called from an applet, so it is ok
	    return;
	}
	checkConnect(loader.codeBaseURL.getHost(), host);
    }

    /**
     * Checks to see if the applet and the indicated execution context
     * are both allowed to connect to the indicated host and port.
     */
    public void checkConnect(String host, int port, Object context) {
	checkConnect(host, port);
	if (context != null) {
	    checkConnect(((URL) context).getHost(), host);
	}
    }

    /**
     * Check if an applet from a host can connect to another
     * host. This usually means that you need to determine whether
     * the hosts are inside or outside the firewall. For now applets
     * can only access the host they came from.
     */
    public synchronized void checkConnect(String fromHost, String toHost) {
	//System.out.println("check connect " + fromHost + " to " + toHost);
	if (fromHost == null) {
	    return;
	}

	switch (networkMode) {
	  case NETWORK_NONE:
	    throw new AppletSecurityException("socket.connect", fromHost + "->" + toHost);

	  case NETWORK_HOST:
            if (0 == fromHost.length() && 0 == toHost.length())
                return; // allow non-network connections to local host

	    // set inCheck so InetAddress knows it doesn't have to
	    // check security
	    inCheck = true;

	    // Try comparing InetAddresses
	    try {
		if (InetAddress.getByName(fromHost).
                    equals(InetAddress.getByName(toHost))) {
			return;
		}
                /* System.err.println("connection/info denied " + InetAddress.getByName(fromHost) + 
                        " vs " + InetAddress.getByName(toHost)); */
	    } catch (java.net.UnknownHostException e) {
                // System.err.println("Unknown host of either " + fromHost + " or " + toHost);
	    } finally {
		inCheck = false;
	    }
	    break;

	  case NETWORK_UNRESTRICTED:
	    return;
	}
	throw new AppletSecurityException("socket.connect", fromHost + "->" + toHost);
    }

    /**
     * Check if a URLConnection can call connect.
     */
    public synchronized void checkURLConnect(URL url) {
//	System.err.println("### Attempting to connect to URL "+url.toExternalForm()+" host="+url.getHost()+" port="+url.getPort());
	AppletClassLoader loader = (AppletClassLoader)currentClassLoader();
	if (loader == null) {
	    // Not called from an applet, so it is ok
	    return;
	}
	String codeBaseProtocol = loader.codeBaseURL.getProtocol();
	String protocol = url.getProtocol();
	if (protocol.equals(codeBaseProtocol)) {
	    if (protocol.equals("http") ||
		protocol.equals("https") ||
		protocol.equals("ftp") ||
		protocol.equals("gopher")) {
		// Make sure we can connect to the given host && port
		checkConnect(url.getHost(), url.getPort());
		return;	/* success */
	    }
	    else if (protocol.equals("file")) {
		// Make sure we can connect to the given host && port
		checkConnect(url.getHost(), url.getPort());
		
		// If this is a file: url then we also have to check that it's not trying to 
		// access anything above the directory the applet came from:
		String baseDir = loader.codeBaseURL.getFile();
		String file = url.getFile();
		if (baseDir == null || file == null) return;
		if (file.startsWith(baseDir)) return;	/* success */
	    }
	}
	throw new AppletSecurityException("protocol", protocol);
    }

    /**
     * Checks to see if top-level windows can be created by the caller.
     */
    public synchronized boolean checkTopLevelWindow(Object window) {
	if (inClassLoader()) {
	    /* XXX: this used to return depth > 3. However, this lets */
	    /* some applets create frames without warning strings. */
	    return false;
	}
	return true;
    }

    /**
     * Check if an applet can access a package.
     */
    public synchronized void checkPackageAccess(String pkg) {
	int i = pkg.indexOf('.');
	if (i > 0) {
	    pkg = pkg.substring(0, i);
	}
//	System.err.println("checkPackageAccess: " + "package.restrict.access." + pkg + " --> "
//			   + Boolean.getBoolean("package.restrict.access." + pkg));
	if (inClassLoader() && Boolean.getBoolean("package.restrict.access." + pkg)) {
	    throw new SecurityException();
	}
    }

    /**
     * Check if an applet can define classes in a package.
     */
    public synchronized void checkPackageDefinition(String pkg) {
	int i = pkg.indexOf('.');
	if (i > 0) {
	    pkg = pkg.substring(0, i);
	}
//	System.err.println("checkPackageDefinition: " + "package.restrict.definition." + pkg + " --> "
//			   + Boolean.getBoolean("package.restrict.definition." + pkg));
	if (inClassLoader() && Boolean.getBoolean("package.restrict.definition." + pkg)) {
	    throw new SecurityException();
	}
    }

    /**
     * Check if an applet can set a networking-related object factory.
     * We install our factory before the security manager is installed,
     * so just disallow factories being installed.
     */
    public synchronized void checkSetFactory() {
	throw new SecurityException();
    }

}