/*
 * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.rmi.registry;

import java.io.ObjectInputFilter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.PrivilegedAction;
import java.security.Security;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.net.*;
import java.rmi.*;
import java.rmi.server.ObjID;
import java.rmi.server.ServerNotActiveException;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Policy;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.text.MessageFormat;

import jdk.internal.misc.SharedSecrets;
import sun.rmi.runtime.Log;
import sun.rmi.server.UnicastRef;
import sun.rmi.server.UnicastServerRef;
import sun.rmi.server.UnicastServerRef2;
import sun.rmi.transport.LiveRef;

A "registry" exists on every node that allows RMI connections to servers on that node. The registry on a particular node contains a transient database that maps names to remote objects. When the node boots, the registry database is empty. The names stored in the registry are pure and are not parsed. A service storing itself in the registry may want to prefix its name of the service by a package name (although not required), to reduce name collisions in the registry. The LocateRegistry class is used to obtain registry for different hosts.

The default RegistryImpl exported restricts access to clients on the local host for the methods bind, rebind, unbind by checking the client host in the skeleton.

See Also:
/** * A "registry" exists on every node that allows RMI connections to * servers on that node. The registry on a particular node contains a * transient database that maps names to remote objects. When the * node boots, the registry database is empty. The names stored in the * registry are pure and are not parsed. A service storing itself in * the registry may want to prefix its name of the service by a package * name (although not required), to reduce name collisions in the * registry. * * The LocateRegistry class is used to obtain registry for different hosts. * <p> * The default RegistryImpl exported restricts access to clients on the local host * for the methods {@link #bind}, {@link #rebind}, {@link #unbind} by checking * the client host in the skeleton. * * @see java.rmi.registry.LocateRegistry */
public class RegistryImpl extends java.rmi.server.RemoteServer implements Registry { /* indicate compatibility with JDK 1.1.x version of class */ private static final long serialVersionUID = 4666870661827494597L; private Hashtable<String, Remote> bindings = new Hashtable<>(101); private static Hashtable<InetAddress, InetAddress> allowedAccessCache = new Hashtable<>(3); private static RegistryImpl registry; private static ObjID id = new ObjID(ObjID.REGISTRY_ID); private static ResourceBundle resources = null;
Property name of the RMI Registry serial filter to augment the built-in list of allowed types. Setting the property in the conf/security/java.security file will enable the augmented filter.
/** * Property name of the RMI Registry serial filter to augment * the built-in list of allowed types. * Setting the property in the {@code conf/security/java.security} file * will enable the augmented filter. */
private static final String REGISTRY_FILTER_PROPNAME = "sun.rmi.registry.registryFilter";
Registry max depth of remote invocations.
/** Registry max depth of remote invocations. **/
private static final int REGISTRY_MAX_DEPTH = 20;
Registry maximum array size in remote invocations.
/** Registry maximum array size in remote invocations. **/
private static final int REGISTRY_MAX_ARRAY_SIZE = 1_000_000;
The registryFilter created from the value of the "sun.rmi.registry.registryFilter" property.
/** * The registryFilter created from the value of the {@code "sun.rmi.registry.registryFilter"} * property. */
private static final ObjectInputFilter registryFilter = AccessController.doPrivileged((PrivilegedAction<ObjectInputFilter>)RegistryImpl::initRegistryFilter);
Initialize the registryFilter from the security properties or system property; if any
Returns:an ObjectInputFilter, or null
/** * Initialize the registryFilter from the security properties or system property; if any * @return an ObjectInputFilter, or null */
@SuppressWarnings("deprecation") private static ObjectInputFilter initRegistryFilter() { ObjectInputFilter filter = null; String props = System.getProperty(REGISTRY_FILTER_PROPNAME); if (props == null) { props = Security.getProperty(REGISTRY_FILTER_PROPNAME); } if (props != null) { filter = SharedSecrets.getJavaObjectInputFilterAccess().createFilter2(props); Log regLog = Log.getLog("sun.rmi.registry", "registry", -1); if (regLog.isLoggable(Log.BRIEF)) { regLog.log(Log.BRIEF, "registryFilter = " + filter); } } return filter; }
Construct a new RegistryImpl on the specified port with the given custom socket factory pair.
/** * Construct a new RegistryImpl on the specified port with the * given custom socket factory pair. */
public RegistryImpl(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException { this(port, csf, ssf, RegistryImpl::registryFilter); }
Construct a new RegistryImpl on the specified port with the given custom socket factory pair and ObjectInputFilter.
/** * Construct a new RegistryImpl on the specified port with the * given custom socket factory pair and ObjectInputFilter. */
public RegistryImpl(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf, ObjectInputFilter serialFilter) throws RemoteException { if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) { // grant permission for default port only. try { AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { public Void run() throws RemoteException { LiveRef lref = new LiveRef(id, port, csf, ssf); setup(new UnicastServerRef2(lref, serialFilter)); return null; } }, null, new SocketPermission("localhost:"+port, "listen,accept")); } catch (PrivilegedActionException pae) { throw (RemoteException)pae.getException(); } } else { LiveRef lref = new LiveRef(id, port, csf, ssf); setup(new UnicastServerRef2(lref, serialFilter)); } }
Construct a new RegistryImpl on the specified port.
/** * Construct a new RegistryImpl on the specified port. */
public RegistryImpl(int port) throws RemoteException { if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) { // grant permission for default port only. try { AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { public Void run() throws RemoteException { LiveRef lref = new LiveRef(id, port); setup(new UnicastServerRef(lref, RegistryImpl::registryFilter)); return null; } }, null, new SocketPermission("localhost:"+port, "listen,accept")); } catch (PrivilegedActionException pae) { throw (RemoteException)pae.getException(); } } else { LiveRef lref = new LiveRef(id, port); setup(new UnicastServerRef(lref, RegistryImpl::registryFilter)); } } /* * Create the export the object using the parameter * <code>uref</code> */ private void setup(UnicastServerRef uref) throws RemoteException { /* Server ref must be created and assigned before remote * object 'this' can be exported. */ ref = uref; uref.exportObject(this, null, true); }
Returns the remote object for specified name in the registry.
Throws:
  • RemoteException – If remote operation failed.
  • NotBoundException – If name is not currently bound.
/** * Returns the remote object for specified name in the registry. * @exception RemoteException If remote operation failed. * @exception NotBoundException If name is not currently bound. */
public Remote lookup(String name) throws RemoteException, NotBoundException { synchronized (bindings) { Remote obj = bindings.get(name); if (obj == null) throw new NotBoundException(name); return obj; } }
Binds the name to the specified remote object.
Throws:
/** * Binds the name to the specified remote object. * @exception RemoteException If remote operation failed. * @exception AlreadyBoundException If name is already bound. */
public void bind(String name, Remote obj) throws RemoteException, AlreadyBoundException, AccessException { // The access check preventing remote access is done in the skeleton // and is not applicable to local access. synchronized (bindings) { Remote curr = bindings.get(name); if (curr != null) throw new AlreadyBoundException(name); bindings.put(name, obj); } }
Unbind the name.
Throws:
  • RemoteException – If remote operation failed.
  • NotBoundException – If name is not currently bound.
/** * Unbind the name. * @exception RemoteException If remote operation failed. * @exception NotBoundException If name is not currently bound. */
public void unbind(String name) throws RemoteException, NotBoundException, AccessException { // The access check preventing remote access is done in the skeleton // and is not applicable to local access. synchronized (bindings) { Remote obj = bindings.get(name); if (obj == null) throw new NotBoundException(name); bindings.remove(name); } }
Rebind the name to a new object, replaces any existing binding.
Throws:
  • RemoteException – If remote operation failed.
/** * Rebind the name to a new object, replaces any existing binding. * @exception RemoteException If remote operation failed. */
public void rebind(String name, Remote obj) throws RemoteException, AccessException { // The access check preventing remote access is done in the skeleton // and is not applicable to local access. bindings.put(name, obj); }
Returns an enumeration of the names in the registry.
Throws:
  • RemoteException – If remote operation failed.
/** * Returns an enumeration of the names in the registry. * @exception RemoteException If remote operation failed. */
public String[] list() throws RemoteException { String[] names; synchronized (bindings) { int i = bindings.size(); names = new String[i]; Enumeration<String> enum_ = bindings.keys(); while ((--i) >= 0) names[i] = enum_.nextElement(); } return names; }
Check that the caller has access to perform indicated operation. The client must be on same the same host as this server.
/** * Check that the caller has access to perform indicated operation. * The client must be on same the same host as this server. */
public static void checkAccess(String op) throws AccessException { try { /* * Get client host that this registry operation was made from. */ final String clientHostName = getClientHost(); InetAddress clientHost; try { clientHost = java.security.AccessController.doPrivileged( new java.security.PrivilegedExceptionAction<InetAddress>() { public InetAddress run() throws java.net.UnknownHostException { return InetAddress.getByName(clientHostName); } }); } catch (PrivilegedActionException pae) { throw (java.net.UnknownHostException) pae.getException(); } // if client not yet seen, make sure client allowed access if (allowedAccessCache.get(clientHost) == null) { if (clientHost.isAnyLocalAddress()) { throw new AccessException( op + " disallowed; origin unknown"); } try { final InetAddress finalClientHost = clientHost; java.security.AccessController.doPrivileged( new java.security.PrivilegedExceptionAction<Void>() { public Void run() throws java.io.IOException { /* * if a ServerSocket can be bound to the client's * address then that address must be local */ (new ServerSocket(0, 10, finalClientHost)).close(); allowedAccessCache.put(finalClientHost, finalClientHost); return null; } }); } catch (PrivilegedActionException pae) { // must have been an IOException throw new AccessException( op + " disallowed; origin " + clientHost + " is non-local host"); } } } catch (ServerNotActiveException ex) { /* * Local call from this VM: allow access. */ } catch (java.net.UnknownHostException ex) { throw new AccessException(op + " disallowed; origin is unknown host"); } } public static ObjID getID() { return id; }
Retrieves text resources from the locale-specific properties file.
/** * Retrieves text resources from the locale-specific properties file. */
private static String getTextResource(String key) { if (resources == null) { try { resources = ResourceBundle.getBundle( "sun.rmi.registry.resources.rmiregistry"); } catch (MissingResourceException mre) { } if (resources == null) { // throwing an Error is a bit extreme, methinks return ("[missing resource file: " + key + "]"); } } String val = null; try { val = resources.getString(key); } catch (MissingResourceException mre) { } if (val == null) { return ("[missing resource: " + key + "]"); } else { return (val); } }
Convert class path specification into an array of file URLs. The path of the file is converted to a URI then into URL form so that reserved characters can safely appear in the path.
/** * Convert class path specification into an array of file URLs. * * The path of the file is converted to a URI then into URL * form so that reserved characters can safely appear in the path. */
private static URL[] pathToURLs(String path) { List<URL> paths = new ArrayList<>(); for (String entry: path.split(File.pathSeparator)) { Path p = Paths.get(entry); try { p = p.toRealPath(); } catch (IOException x) { p = p.toAbsolutePath(); } try { paths.add(p.toUri().toURL()); } catch (MalformedURLException e) { //ignore / skip entry } } return paths.toArray(new URL[0]); }
ObjectInputFilter to filter Registry input objects. The list of acceptable classes is limited to classes normally stored in a registry.
Params:
  • filterInfo – access to the class, array length, etc.
Returns: Status.ALLOWED if allowed, Status.REJECTED if rejected, otherwise Status.UNDECIDED
/** * ObjectInputFilter to filter Registry input objects. * The list of acceptable classes is limited to classes normally * stored in a registry. * * @param filterInfo access to the class, array length, etc. * @return {@link ObjectInputFilter.Status#ALLOWED} if allowed, * {@link ObjectInputFilter.Status#REJECTED} if rejected, * otherwise {@link ObjectInputFilter.Status#UNDECIDED} */
private static ObjectInputFilter.Status registryFilter(ObjectInputFilter.FilterInfo filterInfo) { if (registryFilter != null) { ObjectInputFilter.Status status = registryFilter.checkInput(filterInfo); if (status != ObjectInputFilter.Status.UNDECIDED) { // The Registry filter can override the built-in white-list return status; } } if (filterInfo.depth() > REGISTRY_MAX_DEPTH) { return ObjectInputFilter.Status.REJECTED; } Class<?> clazz = filterInfo.serialClass(); if (clazz != null) { if (clazz.isArray()) { // Arrays are REJECTED only if they exceed the limit return (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > REGISTRY_MAX_ARRAY_SIZE) ? ObjectInputFilter.Status.REJECTED : ObjectInputFilter.Status.UNDECIDED; } if (String.class == clazz || java.lang.Number.class.isAssignableFrom(clazz) || Remote.class.isAssignableFrom(clazz) || java.lang.reflect.Proxy.class.isAssignableFrom(clazz) || UnicastRef.class.isAssignableFrom(clazz) || RMIClientSocketFactory.class.isAssignableFrom(clazz) || RMIServerSocketFactory.class.isAssignableFrom(clazz) || java.rmi.activation.ActivationID.class.isAssignableFrom(clazz) || java.rmi.server.UID.class.isAssignableFrom(clazz)) { return ObjectInputFilter.Status.ALLOWED; } else { return ObjectInputFilter.Status.REJECTED; } } return ObjectInputFilter.Status.UNDECIDED; }
Return a new RegistryImpl on the requested port and export it to serve registry requests. A classloader is initialized from the system property "env.class.path" and a security manager is set unless one is already set.

The returned Registry is fully functional within the current process and is usable for internal and testing purposes.

Params:
  • regPort – port on which the rmiregistry accepts requests; if 0, an implementation specific port is assigned
Throws:
Returns:a RegistryImpl instance
Since:9
/** * Return a new RegistryImpl on the requested port and export it to serve * registry requests. A classloader is initialized from the system property * "env.class.path" and a security manager is set unless one is already set. * <p> * The returned Registry is fully functional within the current process and * is usable for internal and testing purposes. * * @param regPort port on which the rmiregistry accepts requests; * if 0, an implementation specific port is assigned * @return a RegistryImpl instance * @exception RemoteException If remote operation failed. * @since 9 */
public static RegistryImpl createRegistry(int regPort) throws RemoteException { // Create and install the security manager if one is not installed // already. if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } /* * Fix bugid 4147561: When JDK tools are executed, the value of * the CLASSPATH environment variable for the shell in which they * were invoked is no longer incorporated into the application * class path; CLASSPATH's only effect is to be the value of the * system property "env.class.path". To preserve the previous * (JDK1.1 and JDK1.2beta3) behavior of this tool, however, its * CLASSPATH should still be considered when resolving classes * being unmarshalled. To effect this old behavior, a class * loader that loads from the file path specified in the * "env.class.path" property is created and set to be the context * class loader before the remote object is exported. */ String envcp = System.getProperty("env.class.path"); if (envcp == null) { envcp = "."; // preserve old default behavior } URL[] urls = pathToURLs(envcp); ClassLoader cl = new URLClassLoader(urls); /* * Fix bugid 4242317: Classes defined by this class loader should * be annotated with the value of the "java.rmi.server.codebase" * property, not the "file:" URLs for the CLASSPATH elements. */ sun.rmi.server.LoaderHandler.registerCodebaseLoader(cl); Thread.currentThread().setContextClassLoader(cl); RegistryImpl registryImpl = null; try { registryImpl = AccessController.doPrivileged( new PrivilegedExceptionAction<RegistryImpl>() { public RegistryImpl run() throws RemoteException { return new RegistryImpl(regPort); } }, getAccessControlContext(regPort)); } catch (PrivilegedActionException ex) { throw (RemoteException) ex.getException(); } return registryImpl; }
Main program to start a registry.
The port number can be specified on the command line.
/** * Main program to start a registry. <br> * The port number can be specified on the command line. */
public static void main(String args[]) { try { final int regPort = (args.length >= 1) ? Integer.parseInt(args[0]) : Registry.REGISTRY_PORT; registry = createRegistry(regPort); // prevent registry from exiting while (true) { try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException e) { } } } catch (NumberFormatException e) { System.err.println(MessageFormat.format( getTextResource("rmiregistry.port.badnumber"), args[0] )); System.err.println(MessageFormat.format( getTextResource("rmiregistry.usage"), "rmiregistry" )); } catch (Exception e) { e.printStackTrace(); } System.exit(1); }
Generates an AccessControlContext with minimal permissions. The approach used here is taken from the similar method getAccessControlContext() in the sun.applet.AppletPanel class.
/** * Generates an AccessControlContext with minimal permissions. * The approach used here is taken from the similar method * getAccessControlContext() in the sun.applet.AppletPanel class. */
private static AccessControlContext getAccessControlContext(int port) { // begin with permissions granted to all code in current policy PermissionCollection perms = AccessController.doPrivileged( new java.security.PrivilegedAction<PermissionCollection>() { public PermissionCollection run() { CodeSource codesource = new CodeSource(null, (java.security.cert.Certificate[]) null); Policy p = java.security.Policy.getPolicy(); if (p != null) { return p.getPermissions(codesource); } else { return new Permissions(); } } }); /* * Anyone can connect to the registry and the registry can connect * to and possibly download stubs from anywhere. Downloaded stubs and * related classes themselves are more tightly limited by RMI. */ perms.add(new SocketPermission("*", "connect,accept")); perms.add(new SocketPermission("localhost:"+port, "listen,accept")); perms.add(new RuntimePermission("accessClassInPackage.sun.jvmstat.*")); perms.add(new RuntimePermission("accessClassInPackage.sun.jvm.hotspot.*")); perms.add(new FilePermission("<<ALL FILES>>", "read")); /* * Create an AccessControlContext that consists of a single * protection domain with only the permissions calculated above. */ ProtectionDomain pd = new ProtectionDomain( new CodeSource(null, (java.security.cert.Certificate[]) null), perms); return new AccessControlContext(new ProtectionDomain[] { pd }); } }