/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.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://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */

package net.jini.security.proxytrust;

import com.sun.jini.logging.Levels;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.security.TrustVerifier;

/**
 * Trust verifier for service proxies that use dynamically downloaded code.
 * This class is intended to be specified in a resource to configure the
 * operation of {@link net.jini.security.Security#verifyObjectTrust
 * Security.verifyObjectTrust}.
 *
 * @com.sun.jini.impl
 * 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 ProxyTrustVerifier 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>no verifier is obtained from a {@link ProxyTrustIterator}</td>
 * </tr>
 * <tr>
 * <td>{@link Levels#HANDLED HANDLED}</td>
 * <td><code>RemoteException</code> being passed to
 * {@link ProxyTrustIterator#setException ProxyTrustIterator.setException}</td>
 * </tr>
 * <tr>
 * <td>{@link Level#FINE FINE}</td>
 * <td>{@link ProxyTrust#getProxyVerifier ProxyTrust.getProxyVerifier} remote
 * call returns a trust verifier</td>
 * </tr>
 * <tr>
 * <td>{@link Level#FINER FINER}</td>
 * <td>an object with a <code>getProxyTrustIterator</code> method is
 * encountered</td>
 * </tr>
 * <tr>
 * <td>{@link Level#FINER FINER}</td>
 * <td>each object produced by a {@link ProxyTrustIterator}</td>
 * </tr>
 * </table>
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 */
public class ProxyTrustVerifier implements TrustVerifier {

    private static final Logger logger =
		Logger.getLogger("net.jini.security.trust");

    /** ProxyTrust.getProxyVerifier */
    private static Method gpvMethod;

    static {
	try {
	    gpvMethod =
		ProxyTrust.class.getMethod("getProxyVerifier", new Class[0]);
	} catch (Exception e) {
	    throw new ExceptionInInitializerError(e);
	}
    }

    /**
     * Creates an instance.
     */
    public ProxyTrustVerifier() {
    }

    /**
     * Returns <code>true</code> if the specified object is known to be
     * trusted to correctly implement its contract; returns <code>false</code>
     * otherwise.
     * <p>
     * This method returns <code>false</code> if the caller context collection
     * of the specified trust verifier context does not contain a
     * {@link MethodConstraints} instance with non-empty constraints for the
     * {@link ProxyTrust#getProxyVerifier ProxyTrust.getProxyVerifier}
     * method, or if a <code>TrustVerifier</code> cannot be obtained from the
     * specified object using the steps described below. Otherwise a
     * <code>TrustVerifier</code> is obtained, its
     * {@link TrustVerifier#isTrustedObject isTrustedObject} method is called
     * with the same arguments passed to this method, and the result of that
     * call is returned by this method.
     * <p>
     * A verifier is obtained from a candidate object as follows. The
     * candidate object's class must have a non-<code>static</code> member
     * method with signature:
     * <pre>ProxyTrustIterator getProxyTrustIterator();</pre>
     * or the candidate object must be an instance of a dynamically generated
     * {@link Proxy} class and the contained invocation handler's class must
     * have such a member method. If neither condition holds, no verifier is
     * obtained from the candidate object. Otherwise, the
     * <code>getProxyTrustIterator</code> method is called (on the candidate
     * object or its invocation handler). For each object produced by the
     * {@link ProxyTrustIterator#next next} method of the returned iterator,
     * the following checks are made, until either a verifier is obtained or
     * the iteration terminates.
     * <ul>
     * <li>If object is a {@link ProxyTrust} instance, and it is a
     * <code>RemoteMethodControl</code> instance, it is verified for trust by
     * calling the specified context's
     * {@link net.jini.security.TrustVerifier.Context#isTrustedObject isTrustedObject}
     * method with the <code>ProxyTrust</code> instance. If it is not a
     * <code>RemoteMethodControl</code> instance or if
     * <code>isTrustedObject</code> returns <code>false</code>, iteration
     * continues. If <code>isTrustedObject</code> returns <code>true</code>,
     * then the <code>ProxyTrust</code> instance's
     * {@link ProxyTrust#getProxyVerifier getProxyVerifier} method is called,
     * using as the client constraints for the remote call the first
     * <code>MethodConstraints</code> instance obtained from the caller
     * context collection (of the specified trust verifier context) that has
     * non-empty constraints for that <code>getProxyVerifier</code> method.
     * The verifier returned by that remote call is the verifier for the
     * candidate object. If the call to <code>isTrustedObject</code> or
     * <code>getProxyVerifier</code> throws a <code>RemoteException</code>,
     * that exception is passed to the {@link ProxyTrustIterator#setException
     * setException} method of the iterator, and the iteration continues.
     * <li>If the object is not a <code>ProxyTrust</code> instance, it is
     * in turn considered a new candidate object, and if a verifier can be
     * obtained for it (recursively using the same steps as for the original
     * candidate object), that verifier is the verifier for the original
     * candidate object. If a verifier cannot be obtained for it, the
     * iteration continues.
     * </ul>
     * If no verifier can be obtained, but one or more of the intermediate
     * operations throws a <code>RemoteException</code>, the last such
     * <code>RemoteException</code> is thrown by this method (rather than
     * this method returning <code>false</code>). If any intermediate
     * operation throws a <code>SecurityException</code> exception, that
     * exception is immediately thrown to the caller.
     *
     * @throws NullPointerException {@inheritDoc}
     * @throws SecurityException {@inheritDoc}
     */
    public boolean isTrustedObject(Object obj, TrustVerifier.Context ctx)
	throws RemoteException
    {
	if (obj == null || ctx == null) {
	    throw new NullPointerException();
	}
	for (Iterator iter = ctx.getCallerContext().iterator();
	     iter.hasNext(); )
	{
	    Object elt = iter.next();
	    if (elt instanceof MethodConstraints) {
		MethodConstraints mc = (MethodConstraints) elt;
		if (!mc.getConstraints(gpvMethod).isEmpty()) {
		    TrustVerifier verifier = getVerifier(obj, ctx, mc);
		    return (verifier != null &&
			    verifier.isTrustedObject(obj, ctx));
		}
	    }
	}
	return false;
    }

    private static TrustVerifier getVerifier(Object obj,
					     TrustVerifier.Context ctx,
					     MethodConstraints mc)
	throws RemoteException
    {
	final Class base = obj.getClass();
	Method m = (Method)
	    AccessController.doPrivileged(new PrivilegedAction() {
		public Object run() {
		    for (Class c = base; c != null; c = c.getSuperclass()) {
			try {
			    Method m =
				c.getDeclaredMethod("getProxyTrustIterator",
						    new Class[0]);
			    if (usable(m, c, base)) {
				m.setAccessible(true);
				return m;
			    }
			    break;
			} catch (NoSuchMethodException e) {
			}
		    }
		    return null;
		}
	    });
	if (m == null) {
	    if (Proxy.isProxyClass(base)) {
		return getVerifier(Proxy.getInvocationHandler(obj), ctx, mc);
	    }
	    return null;
	}
	logger.log(Level.FINER, "{0} has ProxyTrustIterator", obj);
	ProxyTrustIterator iter;
	try {
	    iter = (ProxyTrustIterator) m.invoke(obj, null);
	} catch (IllegalAccessException e) {
	    throw new AssertionError(e);
	} catch (InvocationTargetException e) {
	    Throwable t = e.getTargetException();
	    if (t instanceof RuntimeException) {
		throw (RuntimeException) t;
	    }
	    throw (Error) t;
	}
	RemoteException lastEx = null;
	while (iter.hasNext()) {
	    obj = null;
	    try {
		obj = iter.next();
		logger.log(Level.FINER, "ProxyTrustIterator produces {0}",
			   obj);
		if (obj instanceof ProxyTrust) {
		    if (obj instanceof RemoteMethodControl &&
			ctx.isTrustedObject(obj))
		    {
			obj = ((RemoteMethodControl) obj).setConstraints(mc);
			TrustVerifier verifier =
			    ((ProxyTrust) obj).getProxyVerifier();
			logger.log(Level.FINE, "verifier is {0}", verifier);
			return verifier;
		    }
		} else {
		    TrustVerifier verifier = getVerifier(obj, ctx, mc);
		    if (verifier != null) {
			return verifier;
		    }
		}
	    } catch (RemoteException e) {
		lastEx = e;
		if (obj instanceof ProxyTrust) {
		    logger.log(Levels.HANDLED,
			       "setting ProxyTrustIterator exception", e);
		    iter.setException(e);
		}
	    }
	}
	if (lastEx != null) {
	    throw lastEx;
	}
	logger.log(Levels.FAILED,
		   "no verifier obtained from ProxyTrustIterator");
	return null;
    }

    /**
     * Returns true if the method returns ProxyTrustIterator, has no
     * declared exceptions, and is a non-static member of the base class.
     */
    private static boolean usable(Method m, Class c, Class base) {
	int mods = m.getModifiers();
	return (m.getReturnType() == ProxyTrustIterator.class &&
		m.getExceptionTypes().length == 0 &&
		(mods & Modifier.STATIC) == 0 &&
		((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0 ||
		 ((mods & Modifier.PRIVATE) != 0 ?
		  c == base : samePackage(c, base))));
    }

    /**
     * Returns true if the classes are in the same package, false otherwise.
     */
    private static boolean samePackage(Class c1, Class c2) {
	if (c1.getClassLoader() == c2.getClassLoader()) {
	    String n1 = c1.getName();
	    int i1 = n1.lastIndexOf('.');
	    String n2 = c2.getName();
	    int i2 = n2.lastIndexOf('.');
	    return i1 == i2 && (i1 < 0 || n1.regionMatches(0, n2, 0, i1));
	}
	return false;
    }
}
