Modified: river/jtsk/skunk/qa_refactor/trunk/src/com/sun/jini/start/ActivateWrapper.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa_refactor/trunk/src/com/sun/jini/start/ActivateWrapper.java?rev=1593493&r1=1593492&r2=1593493&view=diff ============================================================================== --- river/jtsk/skunk/qa_refactor/trunk/src/com/sun/jini/start/ActivateWrapper.java (original) +++ river/jtsk/skunk/qa_refactor/trunk/src/com/sun/jini/start/ActivateWrapper.java Fri May 9 07:03:18 2014 @@ -1,665 +1,672 @@ -/* - * 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 com.sun.jini.start; - -import org.apache.river.api.util.Startable; -import net.jini.export.ProxyAccessor; -import net.jini.id.Uuid; -import net.jini.id.UuidFactory; -import net.jini.loader.pref.PreferredClassLoader; -import net.jini.security.policy.DynamicPolicy; -import net.jini.security.policy.DynamicPolicyProvider; -import net.jini.security.policy.PolicyFileProvider; - -import java.io.InvalidObjectException; -import java.io.ObjectStreamException; -import java.io.Serializable; -import java.lang.reflect.Constructor; -import java.net.URL; -import java.rmi.MarshalException; -import java.rmi.MarshalledObject; -import java.rmi.Remote; -import java.rmi.RemoteException; -import java.rmi.activation.Activatable; -import java.rmi.activation.ActivationDesc; -import java.rmi.activation.ActivationException; -import java.rmi.activation.ActivationGroupID; -import java.rmi.activation.ActivationID; -import java.rmi.activation.ActivationSystem; -import java.security.AccessController; -import java.security.AllPermission; -import java.security.cert.Certificate; -import java.security.CodeSource; -import java.security.Permission; -import java.security.Policy; -import java.security.PrivilegedAction; -import java.security.ProtectionDomain; -import java.security.Security; -import java.util.Arrays; -import java.util.logging.Level; -import java.util.logging.Logger; -import net.jini.io.MarshalledInstance; -import net.jini.loader.ClassLoading; - -/** - * A wrapper for activatable objects, providing separation of the import - * codebase (where the server classes are loaded from by the activation - * group) from the export codebase (where clients should load classes from - * for stubs, etc.) as well as providing an independent security policy file - * for each activatable object. This functionality allows multiple - * activatable objects to be placed in the same activation group, with each - * object maintaining a distinct codebase and policy. - * <p> - * This wrapper class is assumed to be available directly in the activation - * group VM; that is, it is assumed to be in the application classloader, - * the extension classloader, or the boot classloader, rather than being - * downloaded. Since this class also needs considerable permissions, the - * easiest thing to do is to make it an installed extension. - * <p> - * This wrapper class performs a security check to control what - * policy files can be used with a given codebase. - * It does this by querying the VM's (global) policy for - * {@link com.sun.jini.start.SharedActivationPolicyPermission} - * grants. The service's associated - * {@link com.sun.jini.start.ActivateWrapper.ActivateDesc#importLocation - * ActivateDesc.importLocation} is used as - * the {@link java.security.CodeSource} - * for selecting the appropriate permission set to - * check against. If multiple codebases are used, then all the codebases must - * have the necessary <code>SharedActivationPolicyPermission</code> grants. - * <p> - * An example of how to use this wrapper: - * <pre> - * URL[] importURLs = new URL[] {new URL("http://myhost:8080/service.jar")}; - * URL[] exportURLs = new URL[] {new URL("http://myhost:8080/service-dl.jar")}; - * ActivationID aid - * = ActivateWrapper.register( - * gid, - * new ActivateWrapper.ActivateDesc( - * "foo.bar.ServiceImpl", - * importURLs, - * exportURLs, - * "http://myhost:8080/service.policy", - * new MarshalledObject( - * new String[] { "/tmp/service.config" }) - * ), - * true, - * activationSystem); - * </pre> - * <A NAME="serviceConstructor"> - * Clients of this wrapper service need to implement the following "activation - * constructor": - * <blockquote><pre> - * <impl>(ActivationID activationID, MarshalledObject data) - * </blockquote></pre> - * where, - * <UL> - * <LI>activationID - is the object's activation identifier - * <LI>data - is the object's activation data - * </UL> - * - * Clients of this wrapper service can also implement - * {@link net.jini.export.ProxyAccessor}, which allows the service - * implementation to provide a remote reference of its choosing. - * <P> - * <A NAME="configEntries"> - * This implementation of <code>ActivateWrapper</code> - * supports the - * following {@link java.security.Security} property: - * - * <table summary="Describes the com.sun.jini.start.servicePolicyProvider - * security property" - * border="0" cellpadding="2"> - * <tr valign="top"> - * <th scope="col" summary="layout"> <font size="+1">•</font> - * <th scope="col" align="left" colspan="2"> <font size="+1"><code> - * com.sun.jini.start.servicePolicyProvider</code></font> - * <tr valign="top"> <td>   <th scope="row" align="right"> - * Default: <td> <code> - * "net.jini.security.policy.DynamicPolicyProvider" - * </code> - * <tr valign="top"> <td>   <th scope="row" align="right"> - * Description: <td> The fully qualified class name of a - * dynamic policy provider (see {@link net.jini.security.policy.DynamicPolicy}) - * which will be used to "wrap" all service policy files. - * The implementation class needs to: - * <UL> - * <LI> implement the following constructor: - * <blockquote><pre> - * public <impl>(Policy servicePolicy) - * </blockquote></pre> - * where, - * <UL> - * <LI>servicePolicy - is the service policy object to be wrapped - * </UL> - * <LI> implement {@link net.jini.security.policy.DynamicPolicy} - * <LI> be a public, non-interface, non-abstract class - * </UL> - * - * <P> - * A custom service policy provider can be very useful when trying to - * debug security related issues. - * <code>com.sun.jini.tool.DebugDynamicPolicyProvider</code> is an example - * policy provider that provides this functionality and can be located - * via the following URL: - * <A HREF="http://starterkit-examples.jini.org/"> - * http://starterkit-examples.jini.org/ - * </A><BR> - * <I>Note:</I>The custom policy implementation is assumed to be - * available from the system classloader of the virtual machine - * hosting the service. Its codebase should also be granted - * {@link java.security.AllPermission}. - * </table> - * - * @see com.sun.jini.start.SharedActivationPolicyPermission - * @see java.rmi.activation.ActivationID - * @see java.rmi.MarshalledObject - * @see java.rmi.Remote - * @see java.security.CodeSource - * @see net.jini.export.ProxyAccessor - * - * @author Sun Microsystems, Inc. - * - */ - -public class ActivateWrapper implements Remote, Serializable { - - /** Configure logger */ - static final Logger logger = Logger.getLogger("com.sun.jini.start.wrapper"); - /** - * The <code>Policy</code> object that aggregates the individual - * service policy objects. - */ - private static AggregatePolicyProvider globalPolicy; - - /** - * The <code>Policy</code> object in effect at startup. - */ - private static Policy initialGlobalPolicy; - - /** - * The "wrapped" activatable object. - * @serial - */ - private final Object impl; - - /** - * The parameter types for the "activation constructor". - */ - private static final Class[] actTypes = { - ActivationID.class, MarshalledObject.class - }; - - /** - * Fully qualified name of custom, service policy provider - */ - private static String servicePolicyProvider = - ((String) AccessController.doPrivileged( - new PrivilegedAction() { - public Object run() { - return Security.getProperty( - "com.sun.jini.start." + - "servicePolicyProvider"); - } - })); - - /** - * The parameter types for the - * "custom, service policy constructor". - */ - private static final Class[] policyTypes = { - Policy.class - }; - - /** - * Descriptor for registering a "wrapped" activatable object. This - * descriptor gets stored as the <code>MarshalledObject</code> - * initialization data in the <code>ActivationDesc</code>. - */ - public static class ActivateDesc implements Serializable { - - private static final long serialVersionUID = 2L; - - /** - * The activatable object's class name. - * @serial - */ - public final String className; - /** - * The codebase where the server classes are loaded from by the - * activation group. - * @serial - */ - public final URL[] importLocation; - /** - * The codebase where clients should load classes from for stubs, etc. - * @serial - */ - public final URL[] exportLocation; - /** - * The security policy filename or URL. - * @serial - */ - public final String policy; - /** - * The activatable object's initialization data. - * @serial - */ - public final MarshalledObject data; - - /** - * Trivial constructor. - */ - public ActivateDesc(String className, - URL[] importLocation, - URL[] exportLocation, - String policy, - MarshalledObject data) - { -//TODO - clone non-String objects? - this.className = className; - this.importLocation = importLocation; - this.exportLocation = exportLocation; - this.policy = policy; - this.data = data; - } - // Javadoc inherited from supertype - public String toString() { - return "[className=" + className + "," - + "importLocation=" - + ((importLocation == null) - ? null : Arrays.asList(importLocation)) - + "," - + "exportLocation=" - + ((exportLocation == null) - ? null : Arrays.asList(exportLocation)) - + "," - + "policy=" + policy + "," - + "data=" + data + "]"; - } - } - - /** - * A simple subclass of <code>PreferredClassLoader</code> that overrides - * <code>getURLs</code> to - * return the <code>URL</code>s of the provided export codebase. - * <code>getURLs</code> - * is called by the RMI subsystem in order to annotate objects - * leaving the virtual machine. - */ - /* - * Implementation note. Subclasses of this class that override - * getClassAnnotation might need to override getURLs because getURLs - * uses a "cached" version of the export annotation. - */ - static class ExportClassLoader extends PreferredClassLoader - { - /** Cached value of the provided export codebase <code>URL</code>s */ - private final URL[] exportURLs; - - /** Id field used to make toString() unique */ - private final Uuid id = UuidFactory.generate(); - - /** Trivial constructor that calls - * <pre> - * super(importURLs, parent, urlsToPath(exportURLs), false); - * </pre> - * and assigns <code>exportURLs</code> to an internal field. - */ - public ExportClassLoader(URL[] importURLs, URL[] exportURLs, - ClassLoader parent) - { - super(importURLs, parent, urlsToPath(exportURLs), false); - // Not safe to call getClassAnnotation() w/i cons if subclassed, - // so need to redo "super" logic here. - if (exportURLs == null) { - this.exportURLs = importURLs; - } else { - this.exportURLs = exportURLs; - } - } - //Javadoc inherited from super type - public URL[] getURLs() { - return (URL[])exportURLs.clone(); - } - - // Javadoc inherited from supertype - public String toString() { - URL[] urls = super.getURLs(); - return this.getClass().getName() - + "[importURLs=" - + (urls==null?null:Arrays.asList(urls)) - + "," - + "exportURLs=" - + (exportURLs==null?null:Arrays.asList(exportURLs)) - + "," - + "parent=" + getParent() - + "," - + "id=" + id - + "]"; - } - } - - /** - * Activatable constructor. This constructor: - * <UL> - * <LI>Retrieves an <code>ActivateDesc</code> from the - * provided <code>data</code> parameter. - * <LI>creates an <code>ExportClassLoader</code> using the - * import and export codebases obtained from the provided - * <code>ActivateDesc</code>, - * <LI>checks the import codebase(s) for the required - * <code>SharedActivationPolicyPermission</code> - * <LI>associates the newly created <code>ExportClassLoader</code> - * and the corresponding policy file obtained from the - * <code>ActivateDesc</code> with the - * <code>AggregatePolicyProvider</code> - * <LI>loads the "wrapped" activatable object's class and - * calls its activation constructor with the context classloader - * set to the newly created <code>ExportClassLoader</code>. - * <LI> resets the context class loader to the original - * context classloader - * </UL> - * The first instance of this class will also replace the VM's - * existing <code>Policy</code> object, if any, - * with a <code>AggregatePolicyProvider</code>. - * - * @param id The <code>ActivationID</code> of this object - * @param data The activation data for this object - * - * @see com.sun.jini.start.ActivateWrapper.ExportClassLoader - * @see com.sun.jini.start.ActivateWrapper.ActivateDesc - * @see com.sun.jini.start.AggregatePolicyProvider - * @see com.sun.jini.start.SharedActivationPolicyPermission - * @see java.security.Policy - * - */ - public ActivateWrapper(ActivationID id, MarshalledObject data) - throws Exception - { - try { - logger.entering(ActivateWrapper.class.getName(), - "ActivateWrapper", new Object[] { id, data }); - - ActivateDesc desc = (ActivateDesc) - new MarshalledInstance(data).get(false); - logger.log(Level.FINEST, "ActivateDesc: {0}", desc); - - Thread t = Thread.currentThread(); - ClassLoader ccl = t.getContextClassLoader(); - logger.log(Level.FINEST, "Saved current context class loader: {0}", - ccl); - - ExportClassLoader cl = null; - try { - cl = new ExportClassLoader(desc.importLocation, - desc.exportLocation, - ccl); - logger.log(Level.FINEST, "Created ExportClassLoader: {0}", cl); - } catch (Exception e) { - logger.throwing(ActivateWrapper.class.getName(), - "ActivateWrapper", e); - throw e; - } - - checkPolicyPermission(desc.policy, desc.importLocation); - - synchronized (ActivateWrapper.class) { - // supplant global policy 1st time through - if (globalPolicy == null) { - initialGlobalPolicy = Policy.getPolicy(); - if (!(initialGlobalPolicy instanceof DynamicPolicy)) { - initialGlobalPolicy = - new DynamicPolicyProvider(initialGlobalPolicy); - } - globalPolicy = - new AggregatePolicyProvider(initialGlobalPolicy); - Policy.setPolicy(globalPolicy); - logger.log(Level.FINEST, - "Global policy set: {0}", globalPolicy); - } - Policy service_policy = - getServicePolicyProvider( - new PolicyFileProvider(desc.policy)); - Policy backstop_policy = - getServicePolicyProvider(initialGlobalPolicy); - LoaderSplitPolicyProvider split_service_policy = - new LoaderSplitPolicyProvider( - cl, service_policy, backstop_policy); - /* Grant "this" code enough permission to do its work - * under the service policy, which takes effect (below) - * after the context loader is (re)set. - * Note: Throws UnsupportedOperationException if dynamic grants - * aren't supported (because underlying policies don't support it). - */ - split_service_policy.grant( - this.getClass(), - null, /* Principal[] */ - new Permission[] { new AllPermission() } ); - globalPolicy.setPolicy(cl, split_service_policy); - logger.log(Level.FINEST, - "Added policy to set: {0}", desc.policy); - } - - boolean initialize = false; - Class ac = ClassLoading.forName(desc.className, initialize, cl); - logger.log(Level.FINEST, "Obtained implementation class: {0}", ac); - - t.setContextClassLoader(cl); - - try { - logger.log(Level.FINEST, - "Set new context class loader: {0}", cl); - Constructor constructor = - ac.getDeclaredConstructor(actTypes); - logger.log(Level.FINEST, - "Obtained implementation constructor: {0}", - constructor); - constructor.setAccessible(true); - impl = - constructor.newInstance(new Object[]{id, desc.data}); - if (impl instanceof Startable) { - ((Startable) impl).start(); - } else { - logger.log( Level.SEVERE, - "Service {0} doesn''t implement {1} {2} {3} {4} {5} {6}", - new Object [] - { - impl.getClass().getCanonicalName(), - Startable.class.getCanonicalName(), - "this service is likely to suffer from race", - "conditions caused by export performed during", - "construction, or threads started while ''this''", - "has been allowed to escape during construction", - "https://www.securecoding.cert.org/confluence/display/java/TSM01-J.+Do+not+let+the+this+reference+escape+during+object+construction" - } - ); - } - logger.log(Level.FINEST, - "Obtained implementation instance: {0}", impl); - } finally { - t.setContextClassLoader(ccl); - logger.log(Level.FINEST, "Context class loader reset to: {0}", - ccl); - } - } catch (Exception e) { - logger.throwing(ActivateWrapper.class.getName(), - "ActivateWrapper", e); - throw e; - } - logger.exiting(ActivateWrapper.class.getName(), - "ActivateWrapper"); - } - - - /** - * Return a reference to service being wrapped in place - * of this object. - */ - private Object writeReplace() throws ObjectStreamException { - Object impl_proxy = impl; - if (impl instanceof ProxyAccessor) { - impl_proxy = ((ProxyAccessor) impl).getProxy(); - logger.log(Level.FINEST, - "Obtained implementation proxy: {0}", impl_proxy); - if (impl_proxy == null) { - throw new InvalidObjectException( - "Implementation's getProxy() returned null"); - } - } - return impl_proxy; - } - - /** - * Analog to - * {@link java.rmi.activation.Activatable#register(java.rmi.activation.ActivationDesc) - * Activatable.register()} for activatable objects that want - * to use this wrapper mechanism. - * - * @return activation ID of the registered service - * - * @throws ActivationException if there was a problem registering - * the activatable class with the activation system - * @throws RemoteException if there was a problem communicating - * with the activation system - */ - public static ActivationID register(ActivationGroupID gid, - ActivateDesc desc, - boolean restart, - ActivationSystem sys) - throws ActivationException, RemoteException - { - logger.entering(ActivateWrapper.class.getName(), - "register", new Object[] { gid, desc, Boolean.valueOf(restart), sys }); - - MarshalledObject data; - try { - data = new MarshalledInstance(desc).convertToMarshalledObject(); - } catch (Exception e) { - MarshalException me = - new MarshalException("marshalling ActivateDesc", e); - logger.throwing(ActivateWrapper.class.getName(), - "register", me); - throw me; - } - - ActivationDesc adesc = - new ActivationDesc(gid, - ActivateWrapper.class.getName(), - null, - data, - restart - ); - logger.log(Level.FINEST, - "Registering descriptor with activation: {0}", adesc); - - ActivationID aid = sys.registerObject(adesc); - - logger.exiting(ActivateWrapper.class.getName(), - "register", aid); - return aid; - } - - /** - * Checks that all the provided <code>URL</code>s have permission to - * use the given policy. - */ - private static void checkPolicyPermission(String policy, URL[] urls) { - logger.entering(ActivateWrapper.class.getName(), - "checkPolicyPermission", new Object[] { policy, urlsToPath(urls) }); - // Create desired permission object - Permission perm = new SharedActivationPolicyPermission(policy); - Certificate[] certs = null; - CodeSource cs = null; - ProtectionDomain pd = null; - // Loop over all codebases - for (int i=0; i < urls.length; i++) { - // Create ProtectionDomain for given codesource - cs = new CodeSource(urls[i], certs); - pd = new ProtectionDomain(cs, null, null, null); - logger.log(Level.FINEST, - "Checking protection domain: {0}", pd); - - // Check if current domain allows desired permission - if(!pd.implies(perm)) { - SecurityException se = new SecurityException( - "ProtectionDomain " + pd - + " does not have required permission: " + perm); - logger.throwing(ActivateWrapper.class.getName(), - "checkPolicyPermission", se); - throw se; - } - } - logger.exiting(ActivateWrapper.class.getName(), - "checkPolicyPermission"); - } - - /** - * Utility method that converts a <code>URL[]</code> - * into a corresponding, space-separated string with - * the same array elements. - * - * Note that if the array has zero elements, the return value is - * null, not the empty string. - */ - private static String urlsToPath(URL[] urls) { -//TODO - check if spaces in file paths are properly escaped (i.e.% chars) - if (urls == null) { - return null; - } else if (urls.length == 0) { - return ""; - } else if (urls.length == 1) { - return urls[0].toExternalForm(); - } else { - StringBuffer path = new StringBuffer(urls[0].toExternalForm()); - for (int i = 1; i < urls.length; i++) { - path.append(' '); - path.append(urls[i].toExternalForm()); - } - return path.toString(); - } - } - - static Policy getServicePolicyProvider(Policy service_policy) throws Exception { - Policy servicePolicyWrapper = null; - if (servicePolicyProvider != null) { - Class sp = Class.forName(servicePolicyProvider); - logger.log(Level.FINEST, - "Obtained custom service policy implementation class: {0}", sp); - Constructor constructor = - sp.getConstructor(policyTypes); - logger.log(Level.FINEST, - "Obtained custom service policy implementation constructor: {0}", - constructor); - servicePolicyWrapper = (Policy) - constructor.newInstance(new Object[]{service_policy}); - logger.log(Level.FINEST, - "Obtained custom service policy implementation instance: {0}", - servicePolicyWrapper); - } else { - servicePolicyWrapper = new DynamicPolicyProvider(service_policy); - logger.log(Level.FINEST, - "Using default service policy implementation instance: {0}", - servicePolicyWrapper); - } - return servicePolicyWrapper; - } - - -} +/* + * 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 com.sun.jini.start; + +import org.apache.river.api.util.Startable; +import net.jini.export.ProxyAccessor; +import net.jini.id.Uuid; +import net.jini.id.UuidFactory; +import net.jini.loader.pref.PreferredClassLoader; +import net.jini.security.policy.DynamicPolicy; +import net.jini.security.policy.DynamicPolicyProvider; +import net.jini.security.policy.PolicyFileProvider; + +import java.io.InvalidObjectException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.net.URL; +import java.rmi.MarshalException; +import java.rmi.MarshalledObject; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.activation.ActivationDesc; +import java.rmi.activation.ActivationException; +import java.rmi.activation.ActivationGroupID; +import java.rmi.activation.ActivationID; +import java.rmi.activation.ActivationSystem; +import java.security.AccessController; +import java.security.AllPermission; +import java.security.cert.Certificate; +import java.security.CodeSource; +import java.security.Permission; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.security.Security; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import net.jini.io.MarshalledInstance; +import net.jini.loader.ClassLoading; + +/** + * A wrapper for activatable objects, providing separation of the import + * codebase (where the server classes are loaded from by the activation + * group) from the export codebase (where clients should load classes from + * for stubs, etc.) as well as providing an independent security policy file + * for each activatable object. This functionality allows multiple + * activatable objects to be placed in the same activation group, with each + * object maintaining a distinct codebase and policy. + * <p> + * This wrapper class is assumed to be available directly in the activation + * group VM; that is, it is assumed to be in the application classloader, + * the extension classloader, or the boot classloader, rather than being + * downloaded. Since this class also needs considerable permissions, the + * easiest thing to do is to make it an installed extension. + * <p> + * This wrapper class performs a security check to control what + * policy files can be used with a given codebase. + * It does this by querying the VM's (global) policy for + * {@link com.sun.jini.start.SharedActivationPolicyPermission} + * grants. The service's associated + * {@link com.sun.jini.start.ActivateWrapper.ActivateDesc#importLocation + * ActivateDesc.importLocation} is used as + * the {@link java.security.CodeSource} + * for selecting the appropriate permission set to + * check against. If multiple codebases are used, then all the codebases must + * have the necessary <code>SharedActivationPolicyPermission</code> grants. + * <p> + * An example of how to use this wrapper: + * <pre> + * URL[] importURLs = new URL[] {new URL("http://myhost:8080/service.jar")}; + * URL[] exportURLs = new URL[] {new URL("http://myhost:8080/service-dl.jar")}; + * ActivationID aid + * = ActivateWrapper.register( + * gid, + * new ActivateWrapper.ActivateDesc( + * "foo.bar.ServiceImpl", + * importURLs, + * exportURLs, + * "http://myhost:8080/service.policy", + * new MarshalledObject( + * new String[] { "/tmp/service.config" }) + * ), + * true, + * activationSystem); + * </pre> + * <A NAME="serviceConstructor"> + * Clients of this wrapper service need to implement the following "activation + * constructor": + * <blockquote><pre> + * <impl>(ActivationID activationID, MarshalledObject data) + * </blockquote></pre> + * where, + * <UL> + * <LI>activationID - is the object's activation identifier + * <LI>data - is the object's activation data + * </UL> + * + * Clients of this wrapper service can also implement + * {@link net.jini.export.ProxyAccessor}, which allows the service + * implementation to provide a remote reference of its choosing. + * <P> + * <A NAME="configEntries"> + * This implementation of <code>ActivateWrapper</code> + * supports the + * following {@link java.security.Security} property: + * + * <table summary="Describes the com.sun.jini.start.servicePolicyProvider + * security property" + * border="0" cellpadding="2"> + * <tr valign="top"> + * <th scope="col" summary="layout"> <font size="+1">•</font> + * <th scope="col" align="left" colspan="2"> <font size="+1"><code> + * com.sun.jini.start.servicePolicyProvider</code></font> + * <tr valign="top"> <td>   <th scope="row" align="right"> + * Default: <td> <code> + * "net.jini.security.policy.DynamicPolicyProvider" + * </code> + * <tr valign="top"> <td>   <th scope="row" align="right"> + * Description: <td> The fully qualified class name of a + * dynamic policy provider (see {@link net.jini.security.policy.DynamicPolicy}) + * which will be used to "wrap" all service policy files. + * The implementation class needs to: + * <UL> + * <LI> implement the following constructor: + * <blockquote><pre> + * public <impl>(Policy servicePolicy) + * </blockquote></pre> + * where, + * <UL> + * <LI>servicePolicy - is the service policy object to be wrapped + * </UL> + * <LI> implement {@link net.jini.security.policy.DynamicPolicy} + * <LI> be a public, non-interface, non-abstract class + * </UL> + * + * <P> + * A custom service policy provider can be very useful when trying to + * debug security related issues. + * <code>com.sun.jini.tool.DebugDynamicPolicyProvider</code> is an example + * policy provider that provides this functionality and can be located + * via the following URL: + * <A HREF="http://starterkit-examples.jini.org/"> + * http://starterkit-examples.jini.org/ + * </A><BR> + * <I>Note:</I>The custom policy implementation is assumed to be + * available from the system classloader of the virtual machine + * hosting the service. Its codebase should also be granted + * {@link java.security.AllPermission}. + * </table> + * + * @see com.sun.jini.start.SharedActivationPolicyPermission + * @see java.rmi.activation.ActivationID + * @see java.rmi.MarshalledObject + * @see java.rmi.Remote + * @see java.security.CodeSource + * @see net.jini.export.ProxyAccessor + * + * @author Sun Microsystems, Inc. + * + */ + +public class ActivateWrapper implements Remote, Serializable { + + /** Configure logger */ + static final Logger logger = Logger.getLogger("com.sun.jini.start.wrapper"); + /** + * The <code>Policy</code> object that aggregates the individual + * service policy objects. + */ + private static AggregatePolicyProvider globalPolicy; + + /** + * The <code>Policy</code> object in effect at startup. + */ + private static Policy initialGlobalPolicy; + + /** + * The "wrapped" activatable object. + * @serial + */ + private final Object impl; + + /** + * The parameter types for the "activation constructor". + */ + private static final Class[] actTypes = { + ActivationID.class, MarshalledObject.class + }; + + /** + * Fully qualified name of custom, service policy provider + */ + private static final String servicePolicyProvider = + ((String) AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Object run() { + return Security.getProperty( + "com.sun.jini.start." + + "servicePolicyProvider"); + } + })); + + /** + * The parameter types for the + * "custom, service policy constructor". + */ + private static final Class[] policyTypes = { + Policy.class + }; + + /** + * Descriptor for registering a "wrapped" activatable object. This + * descriptor gets stored as the <code>MarshalledObject</code> + * initialization data in the <code>ActivationDesc</code>. + */ + public static class ActivateDesc implements Serializable { + + private static final long serialVersionUID = 2L; + + /** + * The activatable object's class name. + * @serial + */ + public final String className; + /** + * The codebase where the server classes are loaded from by the + * activation group. + * @serial + */ + public final URL[] importLocation; + /** + * The codebase where clients should load classes from for stubs, etc. + * @serial + */ + public final URL[] exportLocation; + /** + * The security policy filename or URL. + * @serial + */ + public final String policy; + /** + * The activatable object's initialization data. + * @serial + */ + public final MarshalledObject data; + + /** + * Trivial constructor. + */ + public ActivateDesc(String className, + URL[] importLocation, + URL[] exportLocation, + String policy, + MarshalledObject data) + { +//TODO - clone non-String objects? + this.className = className; + this.importLocation = importLocation; + this.exportLocation = exportLocation; + this.policy = policy; + this.data = data; + } + // Javadoc inherited from supertype + @Override + public String toString() { + return "[className=" + className + "," + + "importLocation=" + + ((importLocation == null) + ? null : Arrays.asList(importLocation)) + + "," + + "exportLocation=" + + ((exportLocation == null) + ? null : Arrays.asList(exportLocation)) + + "," + + "policy=" + policy + "," + + "data=" + data + "]"; + } + } + + /** + * A simple subclass of <code>PreferredClassLoader</code> that overrides + * <code>getURLs</code> to + * return the <code>URL</code>s of the provided export codebase. + * <code>getURLs</code> + * is called by the RMI subsystem in order to annotate objects + * leaving the virtual machine. + */ + /* + * Implementation note. Subclasses of this class that override + * getClassAnnotation might need to override getURLs because getURLs + * uses a "cached" version of the export annotation. + */ + static class ExportClassLoader extends PreferredClassLoader + { + /** Cached value of the provided export codebase <code>URL</code>s */ + private final URL[] exportURLs; + + /** Id field used to make toString() unique */ + private final Uuid id = UuidFactory.generate(); + + /** Trivial constructor that calls + * <pre> + * super(importURLs, parent, urlsToPath(exportURLs), false); + * </pre> + * and assigns <code>exportURLs</code> to an internal field. + */ + public ExportClassLoader(URL[] importURLs, URL[] exportURLs, + ClassLoader parent) + { + super(importURLs, parent, urlsToPath(exportURLs), false); + // Not safe to call getClassAnnotation() w/i cons if subclassed, + // so need to redo "super" logic here. + if (exportURLs == null) { + this.exportURLs = importURLs; + } else { + this.exportURLs = exportURLs; + } + } + //Javadoc inherited from super type + @Override + public URL[] getURLs() { + return (URL[])exportURLs.clone(); + } + + // Javadoc inherited from supertype + @Override + public String toString() { + StringBuilder builder = new StringBuilder(200); + URL[] urls = super.getURLs(); + return builder + .append(this.getClass().getName()) + .append( "[importURLs=" ) + .append(urls==null?null:Arrays.asList(urls)) + .append(",") + .append("exportURLs=") + .append(exportURLs==null?null:Arrays.asList(exportURLs)) + .append(",") + .append("parent=").append(getParent()) + .append(",") + .append("id=").append(id) + .append("]").toString(); + } + } + + /** + * Activatable constructor. This constructor: + * <UL> + * <LI>Retrieves an <code>ActivateDesc</code> from the + * provided <code>data</code> parameter. + * <LI>creates an <code>ExportClassLoader</code> using the + * import and export codebases obtained from the provided + * <code>ActivateDesc</code>, + * <LI>checks the import codebase(s) for the required + * <code>SharedActivationPolicyPermission</code> + * <LI>associates the newly created <code>ExportClassLoader</code> + * and the corresponding policy file obtained from the + * <code>ActivateDesc</code> with the + * <code>AggregatePolicyProvider</code> + * <LI>loads the "wrapped" activatable object's class and + * calls its activation constructor with the context classloader + * set to the newly created <code>ExportClassLoader</code>. + * <LI> resets the context class loader to the original + * context classloader + * </UL> + * The first instance of this class will also replace the VM's + * existing <code>Policy</code> object, if any, + * with a <code>AggregatePolicyProvider</code>. + * + * @param id The <code>ActivationID</code> of this object + * @param data The activation data for this object + * @throws java.lang.Exception + * + * @see com.sun.jini.start.ActivateWrapper.ExportClassLoader + * @see com.sun.jini.start.ActivateWrapper.ActivateDesc + * @see com.sun.jini.start.AggregatePolicyProvider + * @see com.sun.jini.start.SharedActivationPolicyPermission + * @see java.security.Policy + * + */ + public ActivateWrapper(ActivationID id, MarshalledObject data) + throws Exception + { + try { + logger.entering(ActivateWrapper.class.getName(), + "ActivateWrapper", new Object[] { id, data }); + + ActivateDesc desc = (ActivateDesc) + new MarshalledInstance(data).get(false); + logger.log(Level.FINEST, "ActivateDesc: {0}", desc); + + Thread t = Thread.currentThread(); + ClassLoader ccl = t.getContextClassLoader(); + logger.log(Level.FINEST, "Saved current context class loader: {0}", + ccl); + + ExportClassLoader cl = null; + try { + cl = new ExportClassLoader(desc.importLocation, + desc.exportLocation, + ccl); + logger.log(Level.FINEST, "Created ExportClassLoader: {0}", cl); + } catch (Exception e) { + logger.throwing(ActivateWrapper.class.getName(), + "ActivateWrapper", e); + throw e; + } + + checkPolicyPermission(desc.policy, desc.importLocation); + + synchronized (ActivateWrapper.class) { + // supplant global policy 1st time through + if (globalPolicy == null) { + initialGlobalPolicy = Policy.getPolicy(); + if (!(initialGlobalPolicy instanceof DynamicPolicy)) { + initialGlobalPolicy = + new DynamicPolicyProvider(initialGlobalPolicy); + } + globalPolicy = + new AggregatePolicyProvider(initialGlobalPolicy); + Policy.setPolicy(globalPolicy); + logger.log(Level.FINEST, + "Global policy set: {0}", globalPolicy); + } + Policy service_policy = + getServicePolicyProvider( + new PolicyFileProvider(desc.policy)); + Policy backstop_policy = + getServicePolicyProvider(initialGlobalPolicy); + LoaderSplitPolicyProvider split_service_policy = + new LoaderSplitPolicyProvider( + cl, service_policy, backstop_policy); + /* Grant "this" code enough permission to do its work + * under the service policy, which takes effect (below) + * after the context loader is (re)set. + * Note: Throws UnsupportedOperationException if dynamic grants + * aren't supported (because underlying policies don't support it). + */ + split_service_policy.grant( + this.getClass(), + null, /* Principal[] */ + new Permission[] { new AllPermission() } ); + globalPolicy.setPolicy(cl, split_service_policy); + logger.log(Level.FINEST, + "Added policy to set: {0}", desc.policy); + } + + boolean initialize = false; + Class ac = ClassLoading.forName(desc.className, initialize, cl); + logger.log(Level.FINEST, "Obtained implementation class: {0}", ac); + + t.setContextClassLoader(cl); + + try { + logger.log(Level.FINEST, + "Set new context class loader: {0}", cl); + Constructor constructor = + ac.getDeclaredConstructor(actTypes); + logger.log(Level.FINEST, + "Obtained implementation constructor: {0}", + constructor); + constructor.setAccessible(true); + impl = + constructor.newInstance(new Object[]{id, desc.data}); + if (impl instanceof Startable) { + ((Startable) impl).start(); + } else { + logger.log( Level.SEVERE, + "Service {0} doesn''t implement {1} {2} {3} {4} {5} {6}", + new Object [] + { + impl.getClass().getCanonicalName(), + Startable.class.getCanonicalName(), + "this service is likely to suffer from race", + "conditions caused by export performed during", + "construction, or threads started while ''this''", + "has been allowed to escape during construction", + "https://www.securecoding.cert.org/confluence/display/java/TSM01-J.+Do+not+let+the+this+reference+escape+during+object+construction" + } + ); + } + logger.log(Level.FINEST, + "Obtained implementation instance: {0}", impl); + } finally { + t.setContextClassLoader(ccl); + logger.log(Level.FINEST, "Context class loader reset to: {0}", + ccl); + } + } catch (Exception e) { + logger.throwing(ActivateWrapper.class.getName(), + "ActivateWrapper", e); + throw e; + } + logger.exiting(ActivateWrapper.class.getName(), + "ActivateWrapper"); + } + + + /** + * Return a reference to service being wrapped in place + * of this object. + */ + private Object writeReplace() throws ObjectStreamException { + Object impl_proxy = impl; + if (impl instanceof ProxyAccessor) { + impl_proxy = ((ProxyAccessor) impl).getProxy(); + logger.log(Level.FINEST, + "Obtained implementation proxy: {0}", impl_proxy); + if (impl_proxy == null) { + throw new InvalidObjectException( + "Implementation's getProxy() returned null"); + } + } + return impl_proxy; + } + + /** + * Analog to + * {@link java.rmi.activation.Activatable#register(java.rmi.activation.ActivationDesc) + * Activatable.register()} for activatable objects that want + * to use this wrapper mechanism. + * + * @return activation ID of the registered service + * + * @throws ActivationException if there was a problem registering + * the activatable class with the activation system + * @throws RemoteException if there was a problem communicating + * with the activation system + */ + public static ActivationID register(ActivationGroupID gid, + ActivateDesc desc, + boolean restart, + ActivationSystem sys) + throws ActivationException, RemoteException + { + logger.entering(ActivateWrapper.class.getName(), + "register", new Object[] { gid, desc, Boolean.valueOf(restart), sys }); + + MarshalledObject data; + try { + data = new MarshalledInstance(desc).convertToMarshalledObject(); + } catch (Exception e) { + MarshalException me = + new MarshalException("marshalling ActivateDesc", e); + logger.throwing(ActivateWrapper.class.getName(), + "register", me); + throw me; + } + + ActivationDesc adesc = + new ActivationDesc(gid, + ActivateWrapper.class.getName(), + null, + data, + restart + ); + logger.log(Level.FINEST, + "Registering descriptor with activation: {0}", adesc); + + ActivationID aid = sys.registerObject(adesc); + + logger.exiting(ActivateWrapper.class.getName(), + "register", aid); + return aid; + } + + /** + * Checks that all the provided <code>URL</code>s have permission to + * use the given policy. + */ + private static void checkPolicyPermission(String policy, URL[] urls) { + logger.entering(ActivateWrapper.class.getName(), + "checkPolicyPermission", new Object[] { policy, urlsToPath(urls) }); + // Create desired permission object + Permission perm = new SharedActivationPolicyPermission(policy); + Certificate[] certs = null; + CodeSource cs; + ProtectionDomain pd; + // Loop over all codebases + int l = urls.length; + for (int i=0; i < l; i++) { + // Create ProtectionDomain for given codesource + cs = new CodeSource(urls[i], certs); + pd = new ProtectionDomain(cs, null, null, null); + logger.log(Level.FINEST, + "Checking protection domain: {0}", pd); + + // Check if current domain allows desired permission + if(!pd.implies(perm)) { + SecurityException se = new SecurityException( + "ProtectionDomain " + pd + + " does not have required permission: " + perm); + logger.throwing(ActivateWrapper.class.getName(), + "checkPolicyPermission", se); + throw se; + } + } + logger.exiting(ActivateWrapper.class.getName(), + "checkPolicyPermission"); + } + + /** + * Utility method that converts a <code>URL[]</code> + * into a corresponding, space-separated string with + * the same array elements. + * + * Note that if the array has zero elements, the return value is + * null, not the empty string. + */ + private static String urlsToPath(URL[] urls) { +//TODO - check if spaces in file paths are properly escaped (i.e.% chars) + if (urls == null) { + return null; + } else if (urls.length == 0) { + return ""; + } else if (urls.length == 1) { + return urls[0].toExternalForm(); + } else { + StringBuilder path = new StringBuilder(urls[0].toExternalForm()); + for (int i = 1; i < urls.length; i++) { + path.append(' '); + path.append(urls[i].toExternalForm()); + } + return path.toString(); + } + } + + static Policy getServicePolicyProvider(Policy service_policy) throws Exception { + Policy servicePolicyWrapper; + if (servicePolicyProvider != null) { + Class sp = Class.forName(servicePolicyProvider); + logger.log(Level.FINEST, + "Obtained custom service policy implementation class: {0}", sp); + Constructor constructor = + sp.getConstructor(policyTypes); + logger.log(Level.FINEST, + "Obtained custom service policy implementation constructor: {0}", + constructor); + servicePolicyWrapper = (Policy) + constructor.newInstance(new Object[]{service_policy}); + logger.log(Level.FINEST, + "Obtained custom service policy implementation instance: {0}", + servicePolicyWrapper); + } else { + servicePolicyWrapper = new DynamicPolicyProvider(service_policy); + logger.log(Level.FINEST, + "Using default service policy implementation instance: {0}", + servicePolicyWrapper); + } + return servicePolicyWrapper; + } + + +}
