Path: blob/master/src/java.rmi/share/classes/sun/rmi/registry/RegistryImpl.java
41154 views
/*1* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.rmi.registry;2627import java.io.ObjectInputFilter;28import java.nio.file.Path;29import java.nio.file.Paths;30import java.security.PrivilegedAction;31import java.security.Security;32import java.util.ArrayList;33import java.util.Enumeration;34import java.util.Hashtable;35import java.util.List;36import java.util.MissingResourceException;37import java.util.ResourceBundle;38import java.io.File;39import java.io.FilePermission;40import java.io.IOException;41import java.net.*;42import java.rmi.*;43import java.rmi.server.ObjID;44import java.rmi.server.ServerNotActiveException;45import java.rmi.registry.Registry;46import java.rmi.server.RMIClientSocketFactory;47import java.rmi.server.RMIServerSocketFactory;48import java.security.AccessControlContext;49import java.security.AccessController;50import java.security.CodeSource;51import java.security.Policy;52import java.security.PrivilegedActionException;53import java.security.PrivilegedExceptionAction;54import java.security.PermissionCollection;55import java.security.Permissions;56import java.security.ProtectionDomain;57import java.text.MessageFormat;5859import jdk.internal.access.SharedSecrets;60import sun.rmi.runtime.Log;61import sun.rmi.server.UnicastRef;62import sun.rmi.server.UnicastServerRef;63import sun.rmi.server.UnicastServerRef2;64import sun.rmi.transport.LiveRef;6566/**67* A "registry" exists on every node that allows RMI connections to68* servers on that node. The registry on a particular node contains a69* transient database that maps names to remote objects. When the70* node boots, the registry database is empty. The names stored in the71* registry are pure and are not parsed. A service storing itself in72* the registry may want to prefix its name of the service by a package73* name (although not required), to reduce name collisions in the74* registry.75*76* The LocateRegistry class is used to obtain registry for different hosts.77* <p>78* The default RegistryImpl exported restricts access to clients on the local host79* for the methods {@link #bind}, {@link #rebind}, {@link #unbind} by checking80* the client host in the skeleton.81*82* @see java.rmi.registry.LocateRegistry83*/84public class RegistryImpl extends java.rmi.server.RemoteServer85implements Registry86{8788/* indicate compatibility with JDK 1.1.x version of class */89private static final long serialVersionUID = 4666870661827494597L;90private Hashtable<String, Remote> bindings91= new Hashtable<>(101);92private static Hashtable<InetAddress, InetAddress> allowedAccessCache93= new Hashtable<>(3);94private static RegistryImpl registry;95private static ObjID id = new ObjID(ObjID.REGISTRY_ID);9697private static ResourceBundle resources = null;9899/**100* Property name of the RMI Registry serial filter to augment101* the built-in list of allowed types.102* Setting the property in the {@code conf/security/java.security} file103* will enable the augmented filter.104*/105private static final String REGISTRY_FILTER_PROPNAME = "sun.rmi.registry.registryFilter";106107/** Registry max depth of remote invocations. **/108private static final int REGISTRY_MAX_DEPTH = 20;109110/** Registry maximum array size in remote invocations. **/111private static final int REGISTRY_MAX_ARRAY_SIZE = 1_000_000;112113/**114* The registryFilter created from the value of the {@code "sun.rmi.registry.registryFilter"}115* property.116*/117@SuppressWarnings("removal")118private static final ObjectInputFilter registryFilter =119AccessController.doPrivileged((PrivilegedAction<ObjectInputFilter>)RegistryImpl::initRegistryFilter);120121/**122* Initialize the registryFilter from the security properties or system property; if any123* @return an ObjectInputFilter, or null124*/125@SuppressWarnings("deprecation")126private static ObjectInputFilter initRegistryFilter() {127ObjectInputFilter filter = null;128String props = System.getProperty(REGISTRY_FILTER_PROPNAME);129if (props == null) {130props = Security.getProperty(REGISTRY_FILTER_PROPNAME);131}132if (props != null) {133filter = SharedSecrets.getJavaObjectInputFilterAccess().createFilter2(props);134Log regLog = Log.getLog("sun.rmi.registry", "registry", -1);135if (regLog.isLoggable(Log.BRIEF)) {136regLog.log(Log.BRIEF, "registryFilter = " + filter);137}138}139return filter;140}141142/**143* Construct a new RegistryImpl on the specified port with the144* given custom socket factory pair.145*/146public RegistryImpl(int port,147RMIClientSocketFactory csf,148RMIServerSocketFactory ssf)149throws RemoteException150{151this(port, csf, ssf, RegistryImpl::registryFilter);152}153154155/**156* Construct a new RegistryImpl on the specified port with the157* given custom socket factory pair and ObjectInputFilter.158*/159@SuppressWarnings("removal")160public RegistryImpl(int port,161RMIClientSocketFactory csf,162RMIServerSocketFactory ssf,163ObjectInputFilter serialFilter)164throws RemoteException165{166if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) {167// grant permission for default port only.168try {169AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {170public Void run() throws RemoteException {171LiveRef lref = new LiveRef(id, port, csf, ssf);172setup(new UnicastServerRef2(lref, serialFilter));173return null;174}175}, null, new SocketPermission("localhost:"+port, "listen,accept"));176} catch (PrivilegedActionException pae) {177throw (RemoteException)pae.getException();178}179} else {180LiveRef lref = new LiveRef(id, port, csf, ssf);181setup(new UnicastServerRef2(lref, serialFilter));182}183}184185/**186* Construct a new RegistryImpl on the specified port.187*/188@SuppressWarnings("removal")189public RegistryImpl(int port)190throws RemoteException191{192if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) {193// grant permission for default port only.194try {195AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {196public Void run() throws RemoteException {197LiveRef lref = new LiveRef(id, port);198setup(new UnicastServerRef(lref, RegistryImpl::registryFilter));199return null;200}201}, null, new SocketPermission("localhost:"+port, "listen,accept"));202} catch (PrivilegedActionException pae) {203throw (RemoteException)pae.getException();204}205} else {206LiveRef lref = new LiveRef(id, port);207setup(new UnicastServerRef(lref, RegistryImpl::registryFilter));208}209}210211/*212* Create the export the object using the parameter213* <code>uref</code>214*/215private void setup(UnicastServerRef uref)216throws RemoteException217{218/* Server ref must be created and assigned before remote219* object 'this' can be exported.220*/221ref = uref;222uref.exportObject(this, null, true);223}224225/**226* Returns the remote object for specified name in the registry.227* @exception RemoteException If remote operation failed.228* @exception NotBoundException If name is not currently bound.229*/230public Remote lookup(String name)231throws RemoteException, NotBoundException232{233synchronized (bindings) {234Remote obj = bindings.get(name);235if (obj == null)236throw new NotBoundException(name);237return obj;238}239}240241/**242* Binds the name to the specified remote object.243* @exception RemoteException If remote operation failed.244* @exception AlreadyBoundException If name is already bound.245*/246public void bind(String name, Remote obj)247throws RemoteException, AlreadyBoundException, AccessException248{249// The access check preventing remote access is done in the skeleton250// and is not applicable to local access.251synchronized (bindings) {252Remote curr = bindings.get(name);253if (curr != null)254throw new AlreadyBoundException(name);255bindings.put(name, obj);256}257}258259/**260* Unbind the name.261* @exception RemoteException If remote operation failed.262* @exception NotBoundException If name is not currently bound.263*/264public void unbind(String name)265throws RemoteException, NotBoundException, AccessException266{267// The access check preventing remote access is done in the skeleton268// and is not applicable to local access.269synchronized (bindings) {270Remote obj = bindings.get(name);271if (obj == null)272throw new NotBoundException(name);273bindings.remove(name);274}275}276277/**278* Rebind the name to a new object, replaces any existing binding.279* @exception RemoteException If remote operation failed.280*/281public void rebind(String name, Remote obj)282throws RemoteException, AccessException283{284// The access check preventing remote access is done in the skeleton285// and is not applicable to local access.286bindings.put(name, obj);287}288289/**290* Returns an enumeration of the names in the registry.291* @exception RemoteException If remote operation failed.292*/293public String[] list()294throws RemoteException295{296String[] names;297synchronized (bindings) {298int i = bindings.size();299names = new String[i];300Enumeration<String> enum_ = bindings.keys();301while ((--i) >= 0)302names[i] = enum_.nextElement();303}304return names;305}306307/**308* Check that the caller has access to perform indicated operation.309* The client must be on same the same host as this server.310*/311@SuppressWarnings("removal")312public static void checkAccess(String op) throws AccessException {313314try {315/*316* Get client host that this registry operation was made from.317*/318final String clientHostName = getClientHost();319InetAddress clientHost;320321try {322clientHost = java.security.AccessController.doPrivileged(323new java.security.PrivilegedExceptionAction<InetAddress>() {324public InetAddress run()325throws java.net.UnknownHostException326{327return InetAddress.getByName(clientHostName);328}329});330} catch (PrivilegedActionException pae) {331throw (java.net.UnknownHostException) pae.getException();332}333334// if client not yet seen, make sure client allowed access335if (allowedAccessCache.get(clientHost) == null) {336337if (clientHost.isAnyLocalAddress()) {338throw new AccessException(339op + " disallowed; origin unknown");340}341342try {343final InetAddress finalClientHost = clientHost;344345java.security.AccessController.doPrivileged(346new java.security.PrivilegedExceptionAction<Void>() {347public Void run() throws java.io.IOException {348/*349* if a ServerSocket can be bound to the client's350* address then that address must be local351*/352(new ServerSocket(0, 10, finalClientHost)).close();353allowedAccessCache.put(finalClientHost,354finalClientHost);355return null;356}357});358} catch (PrivilegedActionException pae) {359// must have been an IOException360361throw new AccessException(362op + " disallowed; origin " +363clientHost + " is non-local host");364}365}366} catch (ServerNotActiveException ex) {367/*368* Local call from this VM: allow access.369*/370} catch (java.net.UnknownHostException ex) {371throw new AccessException(op + " disallowed; origin is unknown host");372}373}374375public static ObjID getID() {376return id;377}378379/**380* Retrieves text resources from the locale-specific properties file.381*/382private static String getTextResource(String key) {383if (resources == null) {384try {385resources = ResourceBundle.getBundle(386"sun.rmi.registry.resources.rmiregistry");387} catch (MissingResourceException mre) {388}389if (resources == null) {390// throwing an Error is a bit extreme, methinks391return ("[missing resource file: " + key + "]");392}393}394395String val = null;396try {397val = resources.getString(key);398} catch (MissingResourceException mre) {399}400401if (val == null) {402return ("[missing resource: " + key + "]");403} else {404return (val);405}406}407408/**409* Convert class path specification into an array of file URLs.410*411* The path of the file is converted to a URI then into URL412* form so that reserved characters can safely appear in the path.413*/414private static URL[] pathToURLs(String path) {415List<URL> paths = new ArrayList<>();416for (String entry: path.split(File.pathSeparator)) {417Path p = Paths.get(entry);418try {419p = p.toRealPath();420} catch (IOException x) {421p = p.toAbsolutePath();422}423try {424paths.add(p.toUri().toURL());425} catch (MalformedURLException e) {426//ignore / skip entry427}428}429return paths.toArray(new URL[0]);430}431432/**433* ObjectInputFilter to filter Registry input objects.434* The list of acceptable classes is limited to classes normally435* stored in a registry.436*437* @param filterInfo access to the class, array length, etc.438* @return {@link ObjectInputFilter.Status#ALLOWED} if allowed,439* {@link ObjectInputFilter.Status#REJECTED} if rejected,440* otherwise {@link ObjectInputFilter.Status#UNDECIDED}441*/442@SuppressWarnings("removal")443private static ObjectInputFilter.Status registryFilter(ObjectInputFilter.FilterInfo filterInfo) {444if (registryFilter != null) {445ObjectInputFilter.Status status = registryFilter.checkInput(filterInfo);446if (status != ObjectInputFilter.Status.UNDECIDED) {447// The Registry filter can override the built-in allow-list448return status;449}450}451452if (filterInfo.depth() > REGISTRY_MAX_DEPTH) {453return ObjectInputFilter.Status.REJECTED;454}455Class<?> clazz = filterInfo.serialClass();456if (clazz != null) {457if (clazz.isArray()) {458// Arrays are REJECTED only if they exceed the limit459return (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > REGISTRY_MAX_ARRAY_SIZE)460? ObjectInputFilter.Status.REJECTED461: ObjectInputFilter.Status.UNDECIDED;462}463if (String.class == clazz464|| java.lang.Number.class.isAssignableFrom(clazz)465|| Remote.class.isAssignableFrom(clazz)466|| java.lang.reflect.Proxy.class.isAssignableFrom(clazz)467|| UnicastRef.class.isAssignableFrom(clazz)468|| RMIClientSocketFactory.class.isAssignableFrom(clazz)469|| RMIServerSocketFactory.class.isAssignableFrom(clazz)470|| java.rmi.server.UID.class.isAssignableFrom(clazz)) {471return ObjectInputFilter.Status.ALLOWED;472} else {473return ObjectInputFilter.Status.REJECTED;474}475}476return ObjectInputFilter.Status.UNDECIDED;477}478479/**480* Return a new RegistryImpl on the requested port and export it to serve481* registry requests. A classloader is initialized from the system property482* "env.class.path" and a security manager is set unless one is already set.483* <p>484* The returned Registry is fully functional within the current process and485* is usable for internal and testing purposes.486*487* @param regPort port on which the rmiregistry accepts requests;488* if 0, an implementation specific port is assigned489* @return a RegistryImpl instance490* @exception RemoteException If remote operation failed.491* @since 9492*/493@SuppressWarnings("removal")494public static RegistryImpl createRegistry(int regPort) throws RemoteException {495// Create and install the security manager if one is not installed496// already.497if (System.getSecurityManager() == null) {498System.setSecurityManager(new SecurityManager());499}500501/*502* Fix bugid 4147561: When JDK tools are executed, the value of503* the CLASSPATH environment variable for the shell in which they504* were invoked is no longer incorporated into the application505* class path; CLASSPATH's only effect is to be the value of the506* system property "env.class.path". To preserve the previous507* (JDK1.1 and JDK1.2beta3) behavior of this tool, however, its508* CLASSPATH should still be considered when resolving classes509* being unmarshalled. To effect this old behavior, a class510* loader that loads from the file path specified in the511* "env.class.path" property is created and set to be the context512* class loader before the remote object is exported.513*/514String envcp = System.getProperty("env.class.path");515if (envcp == null) {516envcp = "."; // preserve old default behavior517}518URL[] urls = pathToURLs(envcp);519ClassLoader cl = new URLClassLoader(urls);520521/*522* Fix bugid 4242317: Classes defined by this class loader should523* be annotated with the value of the "java.rmi.server.codebase"524* property, not the "file:" URLs for the CLASSPATH elements.525*/526sun.rmi.server.LoaderHandler.registerCodebaseLoader(cl);527528Thread.currentThread().setContextClassLoader(cl);529530RegistryImpl registryImpl = null;531try {532registryImpl = AccessController.doPrivileged(533new PrivilegedExceptionAction<RegistryImpl>() {534public RegistryImpl run() throws RemoteException {535return new RegistryImpl(regPort);536}537}, getAccessControlContext(regPort));538} catch (PrivilegedActionException ex) {539throw (RemoteException) ex.getException();540}541542return registryImpl;543}544545/**546* Main program to start a registry. <br>547* The port number can be specified on the command line.548*/549public static void main(String args[])550{551try {552final int regPort = (args.length >= 1) ? Integer.parseInt(args[0])553: Registry.REGISTRY_PORT;554555registry = createRegistry(regPort);556557// prevent registry from exiting558while (true) {559try {560Thread.sleep(Long.MAX_VALUE);561} catch (InterruptedException e) {562}563}564} catch (NumberFormatException e) {565System.err.println(MessageFormat.format(566getTextResource("rmiregistry.port.badnumber"),567args[0] ));568System.err.println(MessageFormat.format(569getTextResource("rmiregistry.usage"),570"rmiregistry" ));571} catch (Exception e) {572e.printStackTrace();573}574System.exit(1);575}576577/**578* Generates an AccessControlContext with minimal permissions.579* The approach used here is taken from the similar method580* getAccessControlContext() in the sun.applet.AppletPanel class.581*/582@SuppressWarnings("removal")583private static AccessControlContext getAccessControlContext(int port) {584// begin with permissions granted to all code in current policy585PermissionCollection perms = AccessController.doPrivileged(586new java.security.PrivilegedAction<PermissionCollection>() {587public PermissionCollection run() {588CodeSource codesource = new CodeSource(null,589(java.security.cert.Certificate[]) null);590Policy p = java.security.Policy.getPolicy();591if (p != null) {592return p.getPermissions(codesource);593} else {594return new Permissions();595}596}597});598599/*600* Anyone can connect to the registry and the registry can connect601* to and possibly download stubs from anywhere. Downloaded stubs and602* related classes themselves are more tightly limited by RMI.603*/604perms.add(new SocketPermission("*", "connect,accept"));605perms.add(new SocketPermission("localhost:"+port, "listen,accept"));606607perms.add(new RuntimePermission("accessClassInPackage.sun.jvmstat.*"));608perms.add(new RuntimePermission("accessClassInPackage.sun.jvm.hotspot.*"));609610perms.add(new FilePermission("<<ALL FILES>>", "read"));611612/*613* Create an AccessControlContext that consists of a single614* protection domain with only the permissions calculated above.615*/616ProtectionDomain pd = new ProtectionDomain(617new CodeSource(null,618(java.security.cert.Certificate[]) null), perms);619return new AccessControlContext(new ProtectionDomain[] { pd });620}621}622623624