/*
 * @(#)RMISecurityManager.java	1.10 97/01/06
 * 
 * Copyright (c) 1995, 1996 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 * 
 * 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.
 * 
 * CopyrightVersion 1.1_beta
 */

package java.rmi;

import java.net.URL;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.FileDescriptor;
import java.rmi.server.RMIClassLoader;

/**
 * This class defines the RMI Stub security policy for applications
 * (not applets).  For code loaded as a Stub, the security manager
 * disables all functions except class definition and access. To set a
 * RMISecurityManager, add the following to an application's main()
 * method: <br>
 * 
 * System.setSecurityManager(new RMISecurityManager()); If no security
 * manager has been set, RMI will load stub classes only from the
 * local files as defined by CLASSPATH.<p>
 *
 * @version	1.10, 06 Jan 1997
 * @author Roger Riggs
 */
public class RMISecurityManager extends SecurityManager {
    
    /**
     * Construct and initialize.
     */
    public RMISecurityManager() {
    }

    /**
     * True if called indirectly from a Stub.
     */
    private boolean inStub() {
	return inClassLoader();
    }

    /**
     * Returns the security context (e.g., a URL).
     */
    public Object getSecurityContext() {
	return RMIClassLoader.getSecurityContext(currentClassLoader());
    }


    /**
     * Stubs are not allowed to create class loaders, or even execute any
     * of ClassLoader's methods.
     */
    public synchronized void checkCreateClassLoader() {
	if (inStub()) {
	    throw new RMISecurityException("classloader");
	}
    }

    /**
     * Stubs are not allowed to manipulate threads.
     */
    public synchronized void checkAccess(Thread t) {
	if (inStub()) {
	    throw new RMISecurityException("thread");
	}
    }

    /**
     * Stubs are not allowed to manipulate thread groups.
     */
    public synchronized void checkAccess(ThreadGroup g) {
	if (inStub()) {
	    throw new RMISecurityException("threadgroup");
	}
    }

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

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

    /**
     * Stubs are not allowed to link dynamic libraries.
     */
    public synchronized void checkLink(String lib){
	switch (classLoaderDepth()) {
	  case 2: // Runtime.load
	  case 3: // System.loadLibrary
	    throw new RMISecurityException("link", lib);
	  default:
	    break;
	}
    }

    /**
     * Stubs are not allowed to access the entire system properties
     * list, only properties explicitly labeled as accessible to stubs.
     */
    public synchronized void checkPropertiesAccess() {
	if (classLoaderDepth() == 2) {
	    throw new RMISecurityException("properties");
	}
    }

    /**
     * Stubs can access the system property named by <i>key</i>
     * only if its twin <i>key.stub</i> property is set to true.
     * For example, the property <code>java.home</code> can be read by
     * stubs only if <code>java.home.stub</code> is <code>true</code>.
     */
    public synchronized void checkPropertyAccess(String key) {
	if (classLoaderDepth() == 2) {
	    if (!"true".equalsIgnoreCase(System.getProperty(key + ".rmi"))) {
		throw new RMISecurityException("properties");
            }
	}
    }

    /**
     * Check if a stub can read a particular file.
     */
    public synchronized void checkRead(String file) {
	if (inStub())
	    throw new RMISecurityException("file.read", file);
    }

    /**
     * No file reads are valid from a stub.
     * @exception  SecurityException If called from a stub class.
     */
    public void checkRead(String file, Object context) {
	if (inStub())
	    throw new RMISecurityException("file.read", file);
    }

    /**
     * Check if a Stub can write a particular file.
     * @exception  SecurityException If called from a stub class.
     */
    public synchronized void checkWrite(String file) {
	if (inStub()) {
	    throw new RMISecurityException("file.write", file);
	}
    }

    /**
     * Check if a file with the specified system dependent
     * file name can be deleted.
     * @param file the system dependent file name
     * @exception  SecurityException If the file is not found.
     */
    public void checkDelete(String file) {
	if (inStub()) {
	    throw new RMISecurityException("file.delete", file);
	}
    }

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

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

    /**
     * For now stubs can't listen on any port.
     */
    public synchronized void checkListen(int port) {
	if (inStub()) {
	    throw new RMISecurityException("socket.listen", String.valueOf(port));
	}
    }

    /**
     * For now stubs can't accept connections on any port.
     */
    public synchronized void checkAccept(String host, int port) {
	if (inStub()) {
	    throw new RMISecurityException("socket.accept", host + ":" + String.valueOf(port));
	}
    }

    /**
     * Checks to see if current execution context is allowed to use
     * (join/leave/send/receive) IP multicast (disallowed from stubs).
     */
    public void checkMulticast(InetAddress maddr) {
	if (inStub()) {
	    throw new RMISecurityException("checkmulticast");
	}
    }

    /**
     * Checks to see if current execution context is allowed to use
     * (join/leave/send/receive) IP multicast (disallowed from stubs).
     */
    public void checkMulticast(InetAddress maddr, byte ttl) {
	if (inStub()) {
	    throw new RMISecurityException("checkmulticast");
	}
    }
    
    /**
     * Stubs can make connections if called through the RMI transport.
     */
    public synchronized void checkConnect(String host, int port) {
	if (!inStub()) {
	    return;
	}
	
	// REMIND: This is only appropriate for our transport
	// implementation.
	int depth = classDepth("sun.rmi.transport.tcp.TCPChannel");
	if (depth > 1) {
	    // Called through our RMI transport
	    return;
	} else {
	    Object url = getSecurityContext();
	    if (url != null && url instanceof java.net.URL) {
		checkConnect(((URL)url).getHost(), host);
	    } else {
		throw new RMISecurityException("checkConnect",
					       "To " + host + ":" + port);
	    }
	}
    }
    
    private synchronized void checkConnect(String fromHost, String toHost) {
	try {
	    inCheck = true;
	    InetAddress toHostAddr, fromHostAddr;
	    if (!fromHost.equals(toHost)) {
		try {
		    // the only time we allow non-matching strings
		    // is when IPs and the IPs match.
		    toHostAddr = InetAddress.getByName(toHost);
		    fromHostAddr = InetAddress.getByName(fromHost);
			
		    if (fromHostAddr.equals(toHostAddr)) {
			return;
		    } else {
			throw new RMISecurityException("checkConnect",
						       "To " + toHost);
		    }
		} catch (UnknownHostException e) {
		    throw new RMISecurityException("checkConnect",
						   "To " + toHost);
		}  
	    } else {
		try {
		    toHostAddr = InetAddress.getByName(toHost);
			
		    // strings match: if we have IP, we're homefree, 
		    return;
		} catch (UnknownHostException e) {
		    throw new RMISecurityException("checkConnect",
						   "To " + toHost);
		}
	    }
	} finally {
	    inCheck = false;
	}
    }
    
    /**
     * Downloaded classes (including stubs) can make connections if
     * called through the RMI transport.
     */
    public void checkConnect(String host, int port, Object context) {
	checkConnect(host, port);
	if (context != null) {
	    if (context instanceof URL) {
		checkConnect(((URL)context).getHost(), host);
	    } else {
	       throw new RMISecurityException("checkConnect (unknown context)",
					       "To " + host);
	    }
	}
    }

    /**
     * Allow caller to create top-level windows.
     * Allow stubs to create windows with warnings.
     */
    public synchronized boolean checkTopLevelWindow(Object window) {
	if (inStub())
	    return false;
	return true;
    }

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

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

    /**
     * Check if a stub can set a networking-related object factory.
     */
    public synchronized void checkSetFactory() {
	throw new SecurityException("SetFactory");
    }

    /**
     * Disallow printing from stubs.
     */
    public void checkPrintJobAccess() {
	if (inStub()) {
	    throw new RMISecurityException("getPrintJob");
	}
    }

    /**
     * Checks to see if an client can get access to the System Clipboard
     * (disallowed from stubs).
     */
    public void checkSystemClipboardAccess() {
	if (inStub()) {
	    throw new RMISecurityException("checksystemclipboardaccess");
	}
	
    }

    /**
     * Checks to see if an client can get access to the AWT event queue
     * (disallowed from stubs).
     */
    public void checkAwtEventQueueAccess() {
	if (inStub()) {
	    throw new RMISecurityException("checkawteventqueueaccess");
	}
    }

    /**
     * Checks to see if client code can access class members.
     * Allow access to all public information.
     * Allow non-stubs to access default, package, and private declarations
     * (and data).
     */
    public void checkMemberAccess(Class clazz, int which) {
	if (which != java.lang.reflect.Member.PUBLIC && inStub())
	    throw new SecurityException();
    }

    /**
     * Stubs cannot perform security provider operations.
     */
    public void checkSecurityAccess(String provider) {
	if (inStub()) {
	    throw new RMISecurityException("checksecurityaccess", provider);
	}
    }
}
