/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.jini.security;

import com.sun.jini.collection.WeakIdentityMap;
import com.sun.jini.logging.Levels;
import com.sun.jini.resource.Service;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.rmi.RemoteException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.DomainCombiner;
import java.security.Permission;
import java.security.Policy;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.SubjectDomainCombiner;
import net.jini.security.policy.DynamicPolicy;
import net.jini.security.policy.SecurityContextSource;

/**
 * Provides methods for executing actions with privileges enabled, for
 * snapshotting security contexts, for verifying trust in proxies, for
 * verifying codebase integrity, and for dynamically granting permissions.
 * This class cannot be instantiated.
 *
 * @com.sun.jini.impl
 * This implementation uses the {@link Logger} named
 * <code>net.jini.security.integrity</code> to log information at
 * the following levels:
 * <table summary="Describes what is logged by Security to
 * the integrity logger at various logging levels" border=1 cellpadding=5>
 * <tr>
 * <th>Level</th>
 * <th>Description</th>
 * </tr>
 * <tr>
 * <td>{@link Levels#FAILED FAILED}</td>
 * <td><code>verifyCodebaseIntegrity</code> throws a
 * <code>SecurityException</code> because no integrity verifier verifies
 * a URL</td>
 * </tr>
 * <tr>
 * <td>{@link Level#FINE FINE}</td>
 * <td>integrity verifier returns <code>true</code></td>
 * </tr>
 * <tr>
 * <td>{@link Level#FINE FINE}</td>
 * <td>creation of cached integrity verifiers</td>
 * </tr>
 * </table>
 * <p>
 * This implementation uses the {@link Logger} named
 * <code>net.jini.security.policy</code> to log information at
 * the following level:
 * <table summary="Describes what is logged by Security to
 * the policy logger at various logging levels" border=1 cellpadding=5>
 * <tr>
 * <th>Level</th>
 * <th>Description</th>
 * </tr>
 * <tr>
 * <td>{@link Level#FINER FINER}</td>
 * <td>dynamic permission grants</td>
 * </tr>
 * </table>
 * <p>
 * This implementation uses the {@link Logger} named
 * <code>net.jini.security.trust</code> to log information at
 * the following levels:
 * <table summary="Describes what is logged by Security to
 * the trust logger at various logging levels" border=1 cellpadding=5>
 * <tr>
 * <th>Level</th>
 * <th>Description</th>
 * </tr>
 * <tr>
 * <td>{@link Levels#FAILED FAILED}</td>
 * <td><code>verifyObjectTrust</code> throws a <code>SecurityException</code>
 * because no trust verifier trusts the specified object</td>
 * </tr>
 * <tr>
 * <td>{@link Levels#FAILED FAILED}</td>
 * <td><code>TrustVerifier.Context.isTrustedObject</code> throws an
 * exception</td>
 * </tr>
 * <tr>
 * <td>{@link Levels#HANDLED HANDLED}</td>
 * <td>trust verifier throws a <code>RemoteException</code> or a
 * <code>SecurityException</code></td>
 * </tr>
 * <tr>
 * <td>{@link Level#FINE FINE}</td>
 * <td>trust verifier returns <code>true</code></td>
 * </tr>
 * <tr>
 * <td>{@link Level#FINE FINE}</td>
 * <td>creation of cached trust verifiers</td>
 * </tr>
 * <tr>
 * <td>{@link Level#FINE FINE}</td>
 * <td><code>TrustVerifier.Context.isTrustedObject</code> returns
 * <code>false</code> because no trust verifier trusts the specified
 * object</td>
 * </tr>
 * </table>
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 */
public final class Security {

    private static final Logger trustLogger =
		Logger.getLogger("net.jini.security.trust");
    private static final Logger integrityLogger =
		Logger.getLogger("net.jini.security.integrity");
    private static final Logger policyLogger =
		Logger.getLogger("net.jini.security.policy");

    /**
     * Weak map from String to [URL[], SoftReference(key)]
     */
    private static Map pathToURLsCache = new WeakHashMap(5);
    /**
     * Weak map from ClassLoader to SoftReference(IntegrityVerifier[]).
     */
    private static final WeakIdentityMap integrityMap = new WeakIdentityMap();

    /**
     * SecurityManager instance used to obtain caller's Class.
     */
    private static final ClassContextAccess ctxAccess = (ClassContextAccess)
	AccessController.doPrivileged(new PrivilegedAction() {
	    public Object run() { return new ClassContextAccess(); }
	});

    /**
     * Non-instantiable.
     */
    private Security() {}

    /**
     * Verifies that the specified object can be trusted to correctly implement
     * its contract, using verifiers from the specified class loader and
     * using the specified collection of context objects as necessary. If a
     * <code>null</code> class loader is specified, the context class loader
     * of the current thread is used instead. Code that is itself downloaded
     * and that carries its own trust verifiers (to trust other downloaded
     * code) should specify an explicit class loader unless the calling code
     * is known to be reachable from the context class loader.
     * <p>
     * A {@link TrustVerifier.Context} is created, containing an ordered list
     * of trust verifiers (obtained as specified below) and the specified class
     * loader and collection of context objects. The
     * {@link TrustVerifier.Context#isTrustedObject isTrustedObject} method
     * of that context is then called with the specified object. If that call
     * returns <code>true</code>, then this method returns normally. If that
     * call throws a <code>RemoteException</code> or
     * <code>SecurityException</code> exception, that exception is thrown by
     * this method. If that call returns <code>false</code>, a
     * <code>SecurityException</code> is thrown.
     * <p>
     * The collection of context objects is provided as a means for the
     * caller to communicate additional information to the trust verifiers.
     * The meaning of an element in this collection is determined by its
     * type. As a specific example, if any trust verifiers might communicate
     * with a remote server (in particular, when verifying a proxy for a
     * remote server), the caller might be responsible for specifying any
     * necessary client constraints as a context object of type
     * {@link net.jini.core.constraint.MethodConstraints}.
     * <p>
     * When security is a concern, this method should be called with a
     * downloaded proxy before making any other use of the proxy, in order to
     * verify basic trust in the proxy to correctly implement its contract.
     * This method can also be used to verify trust in other types of objects,
     * depending on what verifiers have been configured. In general,
     * verification of an object involves verification of all of its
     * constituent objects. However, for objects that are instances of
     * {@link net.jini.core.constraint.RemoteMethodControl},
     * the client constraints (that would be returned by
     * {@link net.jini.core.constraint.RemoteMethodControl#getConstraints
     * RemoteMethodControl.getConstraints}) are not verified; it is assumed
     * that the caller will either replace them or independently decide that
     * it trusts them. Verification of other types of objects may similarly
     * exempt certain application-controlled state.
     * <p>
     * The list of trust verifiers is obtained as follows. For each resource
     * named
     * <code>META-INF/services/net.jini.security.TrustVerifier</code>
     * that is visible to the specified class loader, the contents of the
     * resource are parsed as UTF-8 text to produce a list of class names.
     * The resource must contain a list of fully qualified class names, one per
     * line. Space and tab characters surrounding each name, as well as blank
     * lines, are ignored.  The comment character is <tt>'#'</tt>; all
     * characters on each line starting with the first comment character are
     * ignored. Each class name (that is not a duplicate of any previous class
     * name) is loaded through the specified class loader, and the resulting
     * class must be assignable to {@link TrustVerifier} and have a public
     * no-argument constructor. The constructor is invoked to create a trust
     * verifier instance. An implementation of this method is permitted to
     * cache the verifier instances associated with a class loader, rather than
     * recreating them on every call.
     *
     * @param obj the object in which to verify trust
     * @param loader the class loader for finding trust verifiers, or
     * <code>null</code> to use the context class loader
     * @param context a collection of context objects for use by trust
     * verifiers
     * @throws SecurityException if the object is not trusted, or if a
     * <code>SecurityException</code> is thrown by the trust verifier context
     * @throws RemoteException if a communication-related exception occurs
     * @throws NullPointerException if the collection is <code>null</code>
     */
    public static void verifyObjectTrust(Object obj,
					 ClassLoader loader,
					 Collection context)
	throws RemoteException
    {
	if (context == null) {
	    throw new NullPointerException("collection cannot be null");
	}
	if (new Context(loader, context).isTrustedObject(obj)) {
	    return;
	}
	SecurityException e = new SecurityException(
					    "object is not trusted: " + obj);
	if (trustLogger.isLoggable(Levels.FAILED)) {
	    logThrow(trustLogger, Levels.FAILED,
		     Security.class.getName(), "verifyObjectTrust",
		     "no verifier trusts {0}",
		     new Object[]{obj}, e);
	}
	throw e;
    }
    
    /**
     * Verifies that the URLs in the specified codebase all provide content
     * integrity, using verifiers from the specified class loader. If a
     * <code>null</code> class loader is specified, the context class loader of
     * the current thread is used instead. An ordered list of integrity
     * verifiers is obtained as specified below. For each URL (if any) in the
     * specified codebase, the {@link IntegrityVerifier#providesIntegrity
     * providesIntegrity} method of each verifier is called (in order) with
     * the URL. If any verifier call returns <code>true</code>, the URL is
     * verified (and no further verifiers are called with that URL). If all of
     * the verifier calls return <code>false</code> for a URL, this method
     * throws a <code>SecurityException</code>. If all of the URLs are
     * verified, this method returns normally.
     * <p>
     * The list of integrity verifiers is obtained as follows. For each
     * resource named
     * <code>META-INF/services/net.jini.security.IntegrityVerifier</code>
     * that is visible to the specified class loader, the contents of the
     * resource are parsed as UTF-8 text to produce a list of class names.
     * The resource must contain a list of fully qualified class names, one per
     * line. Space and tab characters surrounding each name, as well as blank
     * lines, are ignored.  The comment character is <tt>'#'</tt>; all
     * characters on each line starting with the first comment character are
     * ignored. Each class name (that is not a duplicate of any previous class
     * name) is loaded through the specified class loader, and the resulting
     * class must be assignable to {@link IntegrityVerifier} and have a public
     * no-argument constructor. The constructor is invoked to create an
     * integrity verifier instance. An implementation of this method is
     * permitted to cache the verifier instances associated with a
     * class loader, rather than recreating them on every call.
     *
     * @param codebase space-separated list of URLs, or <code>null</code>
     * @param loader the class loader for finding integrity verifiers, or
     * <code>null</code> to use the context class loader
     * @throws MalformedURLException if the specified codebase contains
     * an invalid URL
     * @throws SecurityException if any URL in the specified codebase
     * does not provide content integrity
     */
    public static void verifyCodebaseIntegrity(String codebase,
					       ClassLoader loader)
	throws MalformedURLException
    {
	if (codebase == null) {
	    return;
	}
	if (loader == null) {
	    loader = getContextClassLoader();
	}
	URL[] urls = pathToURLs(codebase);
	IntegrityVerifier[] verifiers = getIntegrityVerifiers(loader);
    outer:
	for (int i = urls.length; --i >= 0; ) {
	    for (int j = 0; j < verifiers.length; j++) {
		if (verifiers[j].providesIntegrity(urls[i])) {
		    if (integrityLogger.isLoggable(Level.FINE)) {
			integrityLogger.log(Level.FINE, 
					    "{0} verifies {1}",
					     new Object[]{verifiers[j],
							  urls[i]});
		    }
		    continue outer;
		}
	    }
	    SecurityException e =
		new SecurityException("URL does not provide integrity: " +
				      urls[i]);
	    if (integrityLogger.isLoggable(Levels.FAILED)) {
		logThrow(integrityLogger, Levels.FAILED,
			 Security.class.getName(), "verifyCodebaseIntegrity",
			 "no verifier verifies {0}", new Object[]{urls[i]}, e);
	    }
	    throw e;
	}
    }

    /**
     * Log a throw.
     */
    private static void logThrow(Logger logger,
				 Level level,
				 String clazz,
				 String method,
				 String msg,
				 Object[] args,
				 Throwable t)
    {
	LogRecord lr = new LogRecord(level, msg);
	lr.setLoggerName(logger.getName());
	lr.setSourceClassName(clazz);
	lr.setSourceMethodName(method);
	lr.setParameters(args);
	lr.setThrown(t);
	logger.log(lr);
    }

    /**
     * Convert a string containing a space-separated list of URLs into a
     * corresponding array of URL objects, throwing a MalformedURLException
     * if any of the URLs are invalid.
     */
    private static URL[] pathToURLs(String path) throws MalformedURLException {
	synchronized (pathToURLsCache) {
	    Object[] v = (Object[]) pathToURLsCache.get(path);
	    if (v != null) {
		return (URL[]) v[0];
	    }
	}
	StringTokenizer st = new StringTokenizer(path);	// divide by spaces
	URL[] urls = new URL[st.countTokens()];
	for (int i = 0; st.hasMoreTokens(); i++) {
	    urls[i] = new URL(st.nextToken());
	}
	synchronized (pathToURLsCache) {
	    pathToURLsCache.put(path,
				new Object[]{urls, new SoftReference(path)});
	}
	return urls;
    }

    /**
     * Return the integrity verifiers for the specified class loader.
     */
    private static IntegrityVerifier[] getIntegrityVerifiers(
							final ClassLoader cl)
    {
	SoftReference ref;
	synchronized (integrityMap) {
	    ref = (SoftReference) integrityMap.get(cl);
	}
	IntegrityVerifier[] verifiers = null;
	if (ref != null) {
	    verifiers = (IntegrityVerifier[]) ref.get();
	}
	if (verifiers == null) {
	    final ArrayList list = new ArrayList(1);
	    AccessController.doPrivileged(new PrivilegedAction() {
		public Object run() {
		    for (Iterator iter =
			     Service.providers(IntegrityVerifier.class, cl);
			 iter.hasNext(); )
		    {
			list.add(iter.next());
		    }
		    return null;
		}
	    });
	    if (integrityLogger.isLoggable(Level.FINE)) {
		integrityLogger.logp(Level.FINE, Security.class.getName(),
				     "verifyCodebaseIntegrity",
				     "integrity verifiers {0}",
				     new Object[]{list});
	    }
	    verifiers = (IntegrityVerifier[]) list.toArray(
					  new IntegrityVerifier[list.size()]);
	    synchronized (integrityMap) {
		integrityMap.put(cl, new SoftReference(verifiers));
	    }
	}
	return verifiers;
    }

    /**
     * Returns a snapshot of the current security context, which can be used to
     * restore the context at a later time.  If either the installed security
     * manager or policy provider implements the {@link SecurityContextSource}
     * interface, then this method delegates to the {@link
     * SecurityContextSource#getContext getContext} method of the
     * implementing object, with precedence given to the security manager.  If
     * neither the security manager nor the policy provider implement
     * <code>SecurityContextSource</code>, then a new default
     * {@link SecurityContext} instance is
     * returned whose methods have the following semantics:
     * <ul>
     * <li>The <code>wrap</code> methods each return their respective
     * <code>PrivilegedAction</code> and <code>PrivilegedExceptionAction</code>
     * arguments, unmodified
     * <li>The <code>getAccessControlContext</code> method returns the
     * <code>AccessControlContext</code> in effect when the security context
     * was created
     * </ul>
     *
     * @return snapshot of the current security context
     */
    public static SecurityContext getContext() {
	SecurityManager sm = System.getSecurityManager();
	if (sm instanceof SecurityContextSource) {
	    return ((SecurityContextSource) sm).getContext();
	}
	Policy policy = getPolicy();
	if (policy instanceof SecurityContextSource) {
	    return ((SecurityContextSource) policy).getContext();
	}

	final AccessControlContext acc = AccessController.getContext();
	return new SecurityContext() {
	    public PrivilegedAction wrap(PrivilegedAction a) {
		if (a == null) {
		    throw new NullPointerException();
		}
		return a;
	    }

	    public PrivilegedExceptionAction wrap(PrivilegedExceptionAction a) 
	    {
		if (a == null) {
		    throw new NullPointerException();
		}
		return a;
	    }

	    public AccessControlContext getAccessControlContext() {
		return acc;
	    }
	};
    }

    /**
     * Executes the specified action's <code>run</code> method with privileges
     * enabled, preserving the domain combiner (if any) of the calling context.
     * If the action's <code>run</code> method throws an unchecked exception,
     * that exception is thrown by this method.  This method is equivalent to
     * the {@link AccessController#doPrivileged(PrivilegedAction)
     * AccessController.doPrivileged} method of the same signature, except that
     * it maintains, instead of clears, the domain combiner (if any) in place
     * at the time of the call.  This typically results in preservation of the
     * current {@link Subject} (if the combiner is a {@link
     * SubjectDomainCombiner}), thus retaining permissions granted to
     * principals of the <code>Subject</code>, as well as the ability to use
     * credentials of the <code>Subject</code> for authentication.
     * 
     * @param action the action to be executed
     * @return the object returned by the action's <code>run</code> method
     * @throws NullPointerException if the action is <code>null</code>
     */
    public static Object doPrivileged(final PrivilegedAction action) {
	final Class caller = ctxAccess.getCaller();
	final AccessControlContext acc = AccessController.getContext();
	return AccessController.doPrivileged(new PrivilegedAction() {
	    public Object run() {
		return AccessController.doPrivileged(
		    action, createPrivilegedContext(caller, acc));
	    }
	});
    }
    
    /**
     * Executes the specified action's <code>run</code> method with privileges
     * enabled, preserving the domain combiner (if any) of the calling context.
     * If the action's <code>run</code> method throws an unchecked exception,
     * that exception is thrown by this method.  This method is equivalent to
     * the {@link AccessController#doPrivileged(PrivilegedExceptionAction)
     * AccessController.doPrivileged} method of the same signature, except that
     * it maintains, instead of clears, the domain combiner (if any) in place
     * at the time of the call.  This typically results in preservation of the
     * current <code>Subject</code> (if the combiner is a
     * <code>SubjectDomainCombiner</code>), thus retaining permissions granted
     * to principals of the <code>Subject</code>, as well as the ability to use
     * credentials of the <code>Subject</code> for authentication.
     * 
     * @param action the action to be executed
     * @return the object returned by the action's <code>run</code> method
     * @throws PrivilegedActionException if the action's <code>run</code>
     * method throws a checked exception
     * @throws NullPointerException if the action is <code>null</code>
     */
    public static Object doPrivileged(final PrivilegedExceptionAction action)
	throws PrivilegedActionException
    {
	final Class caller = ctxAccess.getCaller();
	final AccessControlContext acc = AccessController.getContext();
	return AccessController.doPrivileged(new PrivilegedExceptionAction() {
	    public Object run() throws Exception {
		try {
		    return AccessController.doPrivileged(
			action, createPrivilegedContext(caller, acc));
		} catch (PrivilegedActionException e) {
		    throw e.getException();
		}
	    }
	});
    }
    
    /**
     * Creates privileged context that contains the protection domain of the
     * given caller class (if non-null) and uses the domain combiner of the
     * specified context.  This method assumes it is called from within a
     * privileged block.
     */
    private static AccessControlContext createPrivilegedContext(
						    Class caller,
						    AccessControlContext acc)
    {
	DomainCombiner comb = acc.getDomainCombiner();
	ProtectionDomain pd = caller.getProtectionDomain();
	ProtectionDomain[] pds = (pd != null) ?
	    new ProtectionDomain[]{pd} : null;
	if (comb != null) {
	    pds = comb.combine(pds, null);
	}
	if (pds == null) {
	    pds = new ProtectionDomain[0];
	}
	return new AccessControlContext(new AccessControlContext(pds), comb);
    }

    /**
     * Returns <code>true</code> if the installed security policy provider
     * supports dynamic permission grants--i.e., if it implements the {@link
     * DynamicPolicy} interface and calling its {@link
     * DynamicPolicy#grantSupported grantSupported} method returns
     * <code>true</code>.  Returns <code>false</code> otherwise.
     *
     * @return <code>true</code> if the installed security policy provider
     * supports dynamic permission grants
     * @see #grant(Class,Permission[])
     * @see #grant(Class,Principal[],Permission[])
     * @see #grant(Class,Class)
     */
    public static boolean grantSupported() {
	Policy policy = getPolicy();
	return (policy instanceof DynamicPolicy && 
		((DynamicPolicy) policy).grantSupported());
    }

    /**
     * If the installed security policy provider implements the
     * {@link DynamicPolicy} interface, delegates to the security policy
     * provider to grant the specified permissions to all protection domains
     * (including ones not yet created) that are associated with the class
     * loader of the given class and possess at least the principals of the
     * current subject (if any).  If the given class is <code>null</code>, then
     * the grant applies across all protection domains that possess at least
     * the current subject's principals.  The current subject is determined by
     * calling {@link Subject#getSubject Subject.getSubject} on the context
     * returned by {@link AccessController#getContext
     * AccessController.getContext}.  If the current subject is
     * <code>null</code> or has no principals, then principals are effectively
     * ignored in determining the protection domains to which the grant
     * applies.  
     * <p>
     * The given class, if non-<code>null</code>, must belong to either the
     * system domain or a protection domain whose associated class loader is
     * non-<code>null</code>.  If the class does not belong to such a
     * protection domain, then no permissions are granted and an
     * <code>UnsupportedOperationException</code> is thrown.
     * <p>
     * If a security manager is installed, its <code>checkPermission</code>
     * method is called with a {@link GrantPermission} containing the
     * permissions to grant; if the permission check fails, then no permissions
     * are granted and the resulting <code>SecurityException</code> is thrown.
     * The permissions array passed in is neither modified nor retained;
     * subsequent changes to the array have no effect on the grant operation.
     *
     * @param cl class to grant permissions to the class loader of, or
     * <code>null</code> if granting across all class loaders
     * @param permissions if non-<code>null</code>, permissions to grant
     * @throws UnsupportedOperationException if the installed security policy
     * provider does not support dynamic permission grants, or if
     * <code>cl</code> is non-<code>null</code> and belongs to a protection
     * domain other than the system domain with an associated class loader of
     * <code>null</code>
     * @throws SecurityException if a security manager is installed and the
     * calling context does not have <code>GrantPermission</code> for the given
     * permissions
     * @throws NullPointerException if any element of the permissions array is
     * <code>null</code>
     * @see #grantSupported()
     * @see DynamicPolicy#grant(Class,Principal[],Permission[])
     */
    public static void grant(Class cl, Permission[] permissions) {
	grant(cl, getCurrentPrincipals(), permissions);
    }

    /**
     * If the installed security policy provider implements the
     * {@link DynamicPolicy} interface, delegates to the security policy
     * provider to grant the specified permissions to all protection domains
     * (including ones not yet created) that are associated with the class
     * loader of the given class and possess at least the given set of
     * principals.  If the given class is <code>null</code>, then the grant
     * applies across all protection domains that possess at least the
     * specified principals.  If the list of principals is <code>null</code> or
     * empty, then principals are effectively ignored in determining the
     * protection domains to which the grant applies.  
     * <p>
     * The given class, if non-<code>null</code>, must belong to either the
     * system domain or a protection domain whose associated class loader is
     * non-<code>null</code>.  If the class does not belong to such a
     * protection domain, then no permissions are granted and an
     * <code>UnsupportedOperationException</code> is thrown.
     * <p>
     * If a security manager is installed, its <code>checkPermission</code>
     * method is called with a <code>GrantPermission</code> containing the
     * permissions to grant; if the permission check fails, then no permissions
     * are granted and the resulting <code>SecurityException</code> is thrown.
     * The principals and permissions arrays passed in are neither modified nor
     * retained; subsequent changes to the arrays have no effect on the grant
     * operation.
     *
     * @param cl class to grant permissions to the class loader of, or
     * <code>null</code> if granting across all class loaders
     * @param principals if non-<code>null</code>, minimum set of principals to
     * which grants apply
     * @param permissions if non-<code>null</code>, permissions to grant
     * @throws UnsupportedOperationException if the installed security policy
     * provider does not support dynamic permission grants, or if
     * <code>cl</code> is non-<code>null</code> and belongs to a protection
     * domain other than the system domain with an associated class loader of
     * <code>null</code>
     * @throws SecurityException if a security manager is installed and the
     * calling context does not have <code>GrantPermission</code> for the given
     * permissions
     * @throws NullPointerException if any element of the principals or
     * permissions arrays is <code>null</code>
     * @see #grantSupported()
     * @see DynamicPolicy#grant(Class,Principal[],Permission[])
     */
    public static void grant(Class cl, 
                             Principal[] principals, 
                             Permission[] permissions)
    {
	Policy policy = getPolicy();
	if (!(policy instanceof DynamicPolicy)) {
	    throw new UnsupportedOperationException("grants not supported");
	}
	((DynamicPolicy) policy).grant(cl, principals, permissions);
	if (policyLogger.isLoggable(Level.FINER)) {
	    policyLogger.log(Level.FINER, "granted {0} to {1}, {2}",
		new Object[]{
		    (permissions != null) ? Arrays.asList(permissions) : null,
		    (cl != null) ? cl.getName() : null,
		    (principals != null) ? Arrays.asList(principals) : null});
	}
    }

    /**
     * If the installed security policy provider implements the {@link
     * DynamicPolicy} interface, takes the set of permissions dynamically
     * granted to the class loader of <code>fromClass</code> with the current
     * subject's principals, determines which of those permissions the calling
     * context is authorized to grant, and dynamically grants that subset of
     * the permissions to the class loader of <code>toClass</code>, qualified
     * with the current subject's principals.  The current subject is
     * determined by calling {@link Subject#getSubject Subject.getSubject} on
     * the context returned by {@link AccessController#getContext
     * AccessController.getContext}; the permissions dynamically granted to
     * <code>fromClass</code> are determined by calling the {@link
     * DynamicPolicy#getGrants getGrants} method of the currently installed
     * policy, and the permission grant to <code>toClass</code> is performed by
     * invoking the {@link DynamicPolicy#grant grant} method of the current
     * policy.
     * <p>
     * Both of the given classes must be non-<code>null</code>, and must belong
     * to either the system domain or a protection domain whose associated
     * class loader is non-<code>null</code>.  If either class does not belong
     * to such a protection domain, then no permissions are granted and an
     * <code>UnsupportedOperationException</code> is thrown.
     *
     * @param fromClass class indicating the source class loader of the dynamic
     * grants to propagate
     * @param toClass class indicating the target class loader of the dynamic
     * grants to propagate
     * @throws NullPointerException if <code>fromClass</code> or
     * <code>toClass</code> is <code>null</code>
     * @throws UnsupportedOperationException if currently installed policy does
     * not support dynamic permission grants, or if either specified class
     * belongs to a protection domain with a <code>null</code> class loader,
     * other than the system domain
     */
    public static void grant(Class fromClass, Class toClass) {
	if (fromClass == null || toClass == null) {
	    throw new NullPointerException();
	}
	Policy policy = getPolicy();
	if (!(policy instanceof DynamicPolicy)) {
	    throw new UnsupportedOperationException("grants not supported");
	}

	DynamicPolicy dpolicy = (DynamicPolicy) policy;
	Principal[] principals = getCurrentPrincipals();
	Permission[] permissions = 
	    grantablePermissions(dpolicy.getGrants(fromClass, principals));

	dpolicy.grant(toClass, principals, permissions);
	if (policyLogger.isLoggable(Level.FINER)) {
	    policyLogger.log(Level.FINER, "granted {0} from {1} to {2}, {3}",
		new Object[]{
		    (permissions != null) ? Arrays.asList(permissions) : null,
		    fromClass.getName(), 
		    toClass.getName(),
		    (principals != null) ? Arrays.asList(principals) : null});
	}
    }

    /**
     * Returns current thread's context class loader.
     */
    private static ClassLoader getContextClassLoader() {
	return (ClassLoader)
	    AccessController.doPrivileged(new PrivilegedAction() {
		    public Object run() {
			return Thread.currentThread().getContextClassLoader();
		    }
		});
    }

    /**
     * Returns currently installed security policy, if any.
     */
    private static Policy getPolicy() {
	return (Policy) AccessController.doPrivileged(new PrivilegedAction() {
	    public Object run() { return Policy.getPolicy(); }
	});
    }

    /**
     * Returns subset of given permissions that is grantable given the current
     * calling context.
     */
    private static Permission[] grantablePermissions(Permission[] permissions)
    {
	SecurityManager sm = System.getSecurityManager();
	if (sm == null || permissions.length == 0) {
	    return permissions;
	}

	try {
	    sm.checkPermission(new GrantPermission(permissions));
	    return permissions;
	} catch (SecurityException e) {
	}

	ArrayList list = new ArrayList(permissions.length);
	for (int i = 0; i < permissions.length; i++) {
	    try {
		Permission p = permissions[i];
		sm.checkPermission(new GrantPermission(p));
		list.add(p);
	    } catch (SecurityException e) {
	    }
	}
	return (Permission[]) list.toArray(new Permission[list.size()]);
    }

    /**
     * Returns principals of current subject, or null if no current subject.
     */
    private static Principal[] getCurrentPrincipals() {
	final AccessControlContext acc = AccessController.getContext();
	Subject s = (Subject) AccessController.doPrivileged(
	    new PrivilegedAction() {
		public Object run() { return Subject.getSubject(acc); }
	    });
	if (s != null) {
	    Set ps = s.getPrincipals();
	    return (Principal[]) ps.toArray(new Principal[ps.size()]);
	} else {
	    return null;
	}
    }

    /**
     * TrustVerifier.Context implementation.
     */
    private static class Context implements TrustVerifier.Context {
	/**
	 * Trust verifiers.
	 */
	private final TrustVerifier[] verifiers;
	/**
	 * The class loader.
	 */
	private final ClassLoader cl;
	/**
	 * Caller context.
	 */
	private final Collection context;

	/**
	 * Weak map from ClassLoader to SoftReference(TrustVerifier[]).
	 */
	private static final WeakIdentityMap map = new WeakIdentityMap();

	/**
	 * Creates an instance containing the trust verifiers found from
	 * the specified class loader, using the verifier cache and
	 * updating the cache as necessary.
	 */
	Context(ClassLoader cl, Collection context) {
	    this.cl = cl;
	    if (cl == null) {
		cl = getContextClassLoader();
	    }
	    SoftReference ref;
	    synchronized (map) {
		ref = (SoftReference) map.get(cl);
	    }
	    TrustVerifier[] verifiers = null;
	    if (ref != null) {
		verifiers = (TrustVerifier[]) ref.get();
	    }
	    if (verifiers == null) {
		final ArrayList list = new ArrayList(1);
		final ClassLoader scl = cl;
		AccessController.doPrivileged(new PrivilegedAction() {
		    public Object run() {
			for (Iterator iter =
				 Service.providers(TrustVerifier.class, scl);
			     iter.hasNext(); )
			{
			    list.add(iter.next());
			}
			return null;
		    }
		});
		if (trustLogger.isLoggable(Level.FINE)) {
		    trustLogger.logp(Level.FINE, Security.class.getName(),
				     "verifyObjectTrust",
				     "trust verifiers {0}", list);
		}
		verifiers = (TrustVerifier[]) list.toArray(
					       new TrustVerifier[list.size()]);
		synchronized (map) {
		    map.put(cl, new SoftReference(verifiers));
		}
	    }
	    this.verifiers = verifiers;
	    this.context = context;
	}

	public boolean isTrustedObject(Object obj)
	    throws RemoteException
	{
	    if (obj == null) {
		return true;
	    }
	    Exception ex = null;
	    for (int i = 0; i < verifiers.length; i++) {
		try {
		    if (verifiers[i].isTrustedObject(obj, this)) {
			if (trustLogger.isLoggable(Level.FINE)) {
			    trustLogger.log(Level.FINE,
					    "{0} trusts {1}",
					    new Object[]{verifiers[i], obj});
			}
			return true;
		    }
		} catch (Exception e) {
		    boolean rethrow = (e instanceof RuntimeException &&
				       !(e instanceof SecurityException));
		    Level level = rethrow ? Levels.FAILED : Levels.HANDLED;
		    if (trustLogger.isLoggable(level)) {
			logThrow(trustLogger, level,
				 this.getClass().getName(),
				 "isTrustedObject",
				 "{0} checking {1} throws",
				 new Object[]{verifiers[i], obj},
				 e);
		    }
		    if (rethrow) {
			throw (RuntimeException) e;
		    }
		    ex = e;
		}
	    }
	    if (ex != null) {
		if (trustLogger.isLoggable(Levels.FAILED)) {
		    logThrow(trustLogger, Levels.FAILED,
			     this.getClass().getName(), "isTrustedObject",
			     "checking {0} throws",
			     new Object[]{obj}, ex);
		}
		if (ex instanceof RemoteException) {
		    throw (RemoteException) ex;
		}
		throw (SecurityException) ex;
	    }
	    if (trustLogger.isLoggable(Level.FINE)) {
		trustLogger.log(Level.FINE, "no verifier trusts {0}", obj);
	    }
	    return false;
	}

	public ClassLoader getClassLoader() {
	    return cl;
	}

	public Collection getCallerContext() {
	    return context;
	}
    }
    
    /**
     * Dummy security manager providing access to getClassContext method.
     */
    private static class ClassContextAccess extends SecurityManager {
	/**
	 * Returns caller's caller class.
	 */
	Class getCaller() {
	    return getClassContext()[2];
	}
    }
}
