1 /*
   2  * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.rmi.registry;
  27 
  28 import java.util.Enumeration;
  29 import java.util.Hashtable;
  30 import java.util.MissingResourceException;
  31 import java.util.ResourceBundle;
  32 import java.io.FilePermission;
  33 import java.net.*;
  34 import java.rmi.*;
  35 import java.rmi.server.ObjID;
  36 import java.rmi.server.ServerNotActiveException;
  37 import java.rmi.registry.Registry;
  38 import java.rmi.server.RMIClientSocketFactory;
  39 import java.rmi.server.RMIServerSocketFactory;
  40 import java.security.AccessControlContext;
  41 import java.security.AccessController;
  42 import java.security.CodeSource;
  43 import java.security.Policy;
  44 import java.security.PrivilegedActionException;
  45 import java.security.PrivilegedExceptionAction;
  46 import java.security.PermissionCollection;
  47 import java.security.Permissions;
  48 import java.security.PrivilegedAction;
  49 import java.security.ProtectionDomain;
  50 import java.security.Security;
  51 import java.text.MessageFormat;
  52 
  53 import sun.misc.ObjectInputFilter;
  54 
  55 import sun.rmi.runtime.Log;
  56 import sun.rmi.server.UnicastRef;
  57 import sun.rmi.server.UnicastServerRef;
  58 import sun.rmi.server.UnicastServerRef2;
  59 import sun.rmi.transport.LiveRef;
  60 
  61 /**
  62  * A "registry" exists on every node that allows RMI connections to
  63  * servers on that node.  The registry on a particular node contains a
  64  * transient database that maps names to remote objects.  When the
  65  * node boots, the registry database is empty.  The names stored in the
  66  * registry are pure and are not parsed.  A service storing itself in
  67  * the registry may want to prefix its name of the service by a package
  68  * name (although not required), to reduce name collisions in the
  69  * registry.
  70  *
  71  * The LocateRegistry class is used to obtain registry for different hosts.
  72  * <p>
  73  * The default RegistryImpl exported restricts access to clients on the local host
  74  * for the methods {@link #bind}, {@link #rebind}, {@link #unbind} by checking
  75  * the client host in the skeleton.
  76  *
  77  * @see java.rmi.registry.LocateRegistry
  78  */
  79 public class RegistryImpl extends java.rmi.server.RemoteServer
  80         implements Registry
  81 {
  82 
  83     /* indicate compatibility with JDK 1.1.x version of class */
  84     private static final long serialVersionUID = 4666870661827494597L;
  85     private Hashtable<String, Remote> bindings
  86         = new Hashtable<>(101);
  87     private static Hashtable<InetAddress, InetAddress> allowedAccessCache
  88         = new Hashtable<>(3);
  89     private static RegistryImpl registry;
  90     private static ObjID id = new ObjID(ObjID.REGISTRY_ID);
  91 
  92     private static ResourceBundle resources = null;
  93 
  94     /**
  95      * Property name of the RMI Registry serial filter to augment
  96      * the built-in list of allowed types.
  97      * Setting the property in the {@code lib/security/java.security} file
  98      * will enable the augmented filter.
  99      */
 100     private static final String REGISTRY_FILTER_PROPNAME = "sun.rmi.registry.registryFilter";
 101 
 102     /** Registry max depth of remote invocations. **/
 103     private static final int REGISTRY_MAX_DEPTH = 20;
 104 
 105     /** Registry maximum array size in remote invocations. **/
 106     private static final int REGISTRY_MAX_ARRAY_SIZE = 10000;
 107 
 108     /**
 109      * The registryFilter created from the value of the {@code "sun.rmi.registry.registryFilter"}
 110      * property.
 111      */
 112     private static final ObjectInputFilter registryFilter =
 113             AccessController.doPrivileged((PrivilegedAction<ObjectInputFilter>)RegistryImpl::initRegistryFilter);
 114 
 115     /**
 116      * Initialize the registryFilter from the security properties or system property; if any
 117      * @return an ObjectInputFilter, or null
 118      */
 119     private static ObjectInputFilter initRegistryFilter() {
 120         ObjectInputFilter filter = null;
 121         String props = System.getProperty(REGISTRY_FILTER_PROPNAME);
 122         if (props == null) {
 123             props = Security.getProperty(REGISTRY_FILTER_PROPNAME);
 124         }
 125         if (props != null) {
 126             filter = ObjectInputFilter.Config.createFilter(props);
 127             Log regLog = Log.getLog("sun.rmi.registry", "registry", -1);
 128             if (regLog.isLoggable(Log.BRIEF)) {
 129                 regLog.log(Log.BRIEF, "registryFilter = " + filter);
 130             }
 131         }
 132         return filter;
 133     }
 134 
 135     /**
 136      * Construct a new RegistryImpl on the specified port with the
 137      * given custom socket factory pair.
 138      */
 139     public RegistryImpl(int port,
 140                         RMIClientSocketFactory csf,
 141                         RMIServerSocketFactory ssf)
 142         throws RemoteException
 143     {
 144         this(port, csf, ssf, RegistryImpl::registryFilter);
 145     }
 146 
 147 
 148     /**
 149      * Construct a new RegistryImpl on the specified port with the
 150      * given custom socket factory pair and ObjectInputFilter.
 151      */
 152     public RegistryImpl(int port,
 153                         RMIClientSocketFactory csf,
 154                         RMIServerSocketFactory ssf,
 155                         ObjectInputFilter serialFilter)
 156         throws RemoteException
 157     {
 158         if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) {
 159             // grant permission for default port only.
 160             try {
 161                 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
 162                     public Void run() throws RemoteException {
 163                         LiveRef lref = new LiveRef(id, port, csf, ssf);
 164                         setup(new UnicastServerRef2(lref, serialFilter));
 165                         return null;
 166                     }
 167                 }, null, new SocketPermission("localhost:"+port, "listen,accept"));
 168             } catch (PrivilegedActionException pae) {
 169                 throw (RemoteException)pae.getException();
 170             }
 171         } else {
 172             LiveRef lref = new LiveRef(id, port, csf, ssf);
 173             setup(new UnicastServerRef2(lref, RegistryImpl::registryFilter));
 174         }
 175     }
 176 
 177     /**
 178      * Construct a new RegistryImpl on the specified port.
 179      */
 180     public RegistryImpl(int port)
 181         throws RemoteException
 182     {
 183         if (port == Registry.REGISTRY_PORT && System.getSecurityManager() != null) {
 184             // grant permission for default port only.
 185             try {
 186                 AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
 187                     public Void run() throws RemoteException {
 188                         LiveRef lref = new LiveRef(id, port);
 189                         setup(new UnicastServerRef(lref, RegistryImpl::registryFilter));
 190                         return null;
 191                     }
 192                 }, null, new SocketPermission("localhost:"+port, "listen,accept"));
 193             } catch (PrivilegedActionException pae) {
 194                 throw (RemoteException)pae.getException();
 195             }
 196         } else {
 197             LiveRef lref = new LiveRef(id, port);
 198             setup(new UnicastServerRef(lref, RegistryImpl::registryFilter));
 199         }
 200     }
 201 
 202     /*
 203      * Create the export the object using the parameter
 204      * <code>uref</code>
 205      */
 206     private void setup(UnicastServerRef uref)
 207         throws RemoteException
 208     {
 209         /* Server ref must be created and assigned before remote
 210          * object 'this' can be exported.
 211          */
 212         ref = uref;
 213         uref.exportObject(this, null, true);
 214     }
 215 
 216     /**
 217      * Returns the remote object for specified name in the registry.
 218      * @exception RemoteException If remote operation failed.
 219      * @exception NotBoundException If name is not currently bound.
 220      */
 221     public Remote lookup(String name)
 222         throws RemoteException, NotBoundException
 223     {
 224         synchronized (bindings) {
 225             Remote obj = bindings.get(name);
 226             if (obj == null)
 227                 throw new NotBoundException(name);
 228             return obj;
 229         }
 230     }
 231 
 232     /**
 233      * Binds the name to the specified remote object.
 234      * @exception RemoteException If remote operation failed.
 235      * @exception AlreadyBoundException If name is already bound.
 236      */
 237     public void bind(String name, Remote obj)
 238         throws RemoteException, AlreadyBoundException, AccessException
 239     {
 240         // The access check preventing remote access is done in the skeleton
 241         // and is not applicable to local access.
 242         synchronized (bindings) {
 243             Remote curr = bindings.get(name);
 244             if (curr != null)
 245                 throw new AlreadyBoundException(name);
 246             bindings.put(name, obj);
 247         }
 248     }
 249 
 250     /**
 251      * Unbind the name.
 252      * @exception RemoteException If remote operation failed.
 253      * @exception NotBoundException If name is not currently bound.
 254      */
 255     public void unbind(String name)
 256         throws RemoteException, NotBoundException, AccessException
 257     {
 258         // The access check preventing remote access is done in the skeleton
 259         // and is not applicable to local access.
 260         synchronized (bindings) {
 261             Remote obj = bindings.get(name);
 262             if (obj == null)
 263                 throw new NotBoundException(name);
 264             bindings.remove(name);
 265         }
 266     }
 267 
 268     /**
 269      * Rebind the name to a new object, replaces any existing binding.
 270      * @exception RemoteException If remote operation failed.
 271      */
 272     public void rebind(String name, Remote obj)
 273         throws RemoteException, AccessException
 274     {
 275         // The access check preventing remote access is done in the skeleton
 276         // and is not applicable to local access.
 277         bindings.put(name, obj);
 278     }
 279 
 280     /**
 281      * Returns an enumeration of the names in the registry.
 282      * @exception RemoteException If remote operation failed.
 283      */
 284     public String[] list()
 285         throws RemoteException
 286     {
 287         String[] names;
 288         synchronized (bindings) {
 289             int i = bindings.size();
 290             names = new String[i];
 291             Enumeration<String> enum_ = bindings.keys();
 292             while ((--i) >= 0)
 293                 names[i] = enum_.nextElement();
 294         }
 295         return names;
 296     }
 297 
 298     /**
 299      * Check that the caller has access to perform indicated operation.
 300      * The client must be on same the same host as this server.
 301      */
 302     public static void checkAccess(String op) throws AccessException {
 303         try {
 304             /*
 305              * Get client host that this registry operation was made from.
 306              */
 307             final String clientHostName = getClientHost();
 308             InetAddress clientHost;
 309 
 310             try {
 311                 clientHost = java.security.AccessController.doPrivileged(
 312                     new java.security.PrivilegedExceptionAction<InetAddress>() {
 313                         public InetAddress run()
 314                             throws java.net.UnknownHostException
 315                         {
 316                             return InetAddress.getByName(clientHostName);
 317                         }
 318                     });
 319             } catch (PrivilegedActionException pae) {
 320                 throw (java.net.UnknownHostException) pae.getException();
 321             }
 322 
 323             // if client not yet seen, make sure client allowed access
 324             if (allowedAccessCache.get(clientHost) == null) {
 325 
 326                 if (clientHost.isAnyLocalAddress()) {
 327                     throw new AccessException(
 328                         op + " disallowed; origin unknown");
 329                 }
 330 
 331                 try {
 332                     final InetAddress finalClientHost = clientHost;
 333 
 334                     java.security.AccessController.doPrivileged(
 335                         new java.security.PrivilegedExceptionAction<Void>() {
 336                             public Void run() throws java.io.IOException {
 337                                 /*
 338                                  * if a ServerSocket can be bound to the client's
 339                                  * address then that address must be local
 340                                  */
 341                                 (new ServerSocket(0, 10, finalClientHost)).close();
 342                                 allowedAccessCache.put(finalClientHost,
 343                                                        finalClientHost);
 344                                 return null;
 345                             }
 346                     });
 347                 } catch (PrivilegedActionException pae) {
 348                     // must have been an IOException
 349 
 350                     throw new AccessException(
 351                         op + " disallowed; origin " +
 352                         clientHost + " is non-local host");
 353                 }
 354             }
 355         } catch (ServerNotActiveException ex) {
 356             /*
 357              * Local call from this VM: allow access.
 358              */
 359         } catch (java.net.UnknownHostException ex) {
 360             throw new AccessException(op + " disallowed; origin is unknown host");
 361         }
 362     }
 363 
 364     public static ObjID getID() {
 365         return id;
 366     }
 367 
 368     /**
 369      * Retrieves text resources from the locale-specific properties file.
 370      */
 371     private static String getTextResource(String key) {
 372         if (resources == null) {
 373             try {
 374                 resources = ResourceBundle.getBundle(
 375                     "sun.rmi.registry.resources.rmiregistry");
 376             } catch (MissingResourceException mre) {
 377             }
 378             if (resources == null) {
 379                 // throwing an Error is a bit extreme, methinks
 380                 return ("[missing resource file: " + key + "]");
 381             }
 382         }
 383 
 384         String val = null;
 385         try {
 386             val = resources.getString(key);
 387         } catch (MissingResourceException mre) {
 388         }
 389 
 390         if (val == null) {
 391             return ("[missing resource: " + key + "]");
 392         } else {
 393             return (val);
 394         }
 395     }
 396 
 397     /**
 398      * ObjectInputFilter to filter Registry input objects.
 399      * The list of acceptable classes is limited to classes normally
 400      * stored in a registry.
 401      *
 402      * @param filterInfo access to the class, array length, etc.
 403      * @return  {@link ObjectInputFilter.Status#ALLOWED} if allowed,
 404      *          {@link ObjectInputFilter.Status#REJECTED} if rejected,
 405      *          otherwise {@link ObjectInputFilter.Status#UNDECIDED}
 406      */
 407     private static ObjectInputFilter.Status registryFilter(ObjectInputFilter.FilterInfo filterInfo) {
 408         if (registryFilter != null) {
 409             ObjectInputFilter.Status status = registryFilter.checkInput(filterInfo);
 410             if (status != ObjectInputFilter.Status.UNDECIDED) {
 411                 // The Registry filter can override the built-in white-list
 412                 return status;
 413             }
 414         }
 415 
 416         if (filterInfo.depth() > REGISTRY_MAX_DEPTH) {
 417             return ObjectInputFilter.Status.REJECTED;
 418         }
 419         Class<?> clazz = filterInfo.serialClass();
 420         if (clazz != null) {
 421             if (clazz.isArray()) {
 422                 if (filterInfo.arrayLength() >= 0 && filterInfo.arrayLength() > REGISTRY_MAX_ARRAY_SIZE) {
 423                     return ObjectInputFilter.Status.REJECTED;
 424                 }
 425                 do {
 426                     // Arrays are allowed depending on the component type
 427                     clazz = clazz.getComponentType();
 428                 } while (clazz.isArray());
 429             }
 430             if (clazz.isPrimitive()) {
 431                 // Arrays of primitives are allowed
 432                 return ObjectInputFilter.Status.ALLOWED;
 433             }
 434             if (String.class == clazz
 435                     || java.lang.Number.class.isAssignableFrom(clazz)
 436                     || Remote.class.isAssignableFrom(clazz)
 437                     || java.lang.reflect.Proxy.class.isAssignableFrom(clazz)
 438                     || UnicastRef.class.isAssignableFrom(clazz)
 439                     || RMIClientSocketFactory.class.isAssignableFrom(clazz)
 440                     || RMIServerSocketFactory.class.isAssignableFrom(clazz)
 441                     || java.rmi.activation.ActivationID.class.isAssignableFrom(clazz)
 442                     || java.rmi.server.UID.class.isAssignableFrom(clazz)) {
 443                 return ObjectInputFilter.Status.ALLOWED;
 444             } else {
 445                 return ObjectInputFilter.Status.REJECTED;
 446             }
 447         }
 448         return ObjectInputFilter.Status.UNDECIDED;
 449     }
 450 
 451     /**
 452      * Main program to start a registry. <br>
 453      * The port number can be specified on the command line.
 454      */
 455     public static void main(String args[])
 456     {
 457         // Create and install the security manager if one is not installed
 458         // already.
 459         if (System.getSecurityManager() == null) {
 460             System.setSecurityManager(new RMISecurityManager());
 461         }
 462 
 463         try {
 464             /*
 465              * Fix bugid 4147561: When JDK tools are executed, the value of
 466              * the CLASSPATH environment variable for the shell in which they
 467              * were invoked is no longer incorporated into the application
 468              * class path; CLASSPATH's only effect is to be the value of the
 469              * system property "env.class.path".  To preserve the previous
 470              * (JDK1.1 and JDK1.2beta3) behavior of this tool, however, its
 471              * CLASSPATH should still be considered when resolving classes
 472              * being unmarshalled.  To effect this old behavior, a class
 473              * loader that loads from the file path specified in the
 474              * "env.class.path" property is created and set to be the context
 475              * class loader before the remote object is exported.
 476              */
 477             String envcp = System.getProperty("env.class.path");
 478             if (envcp == null) {
 479                 envcp = ".";            // preserve old default behavior
 480             }
 481             URL[] urls = sun.misc.URLClassPath.pathToURLs(envcp);
 482             ClassLoader cl = new URLClassLoader(urls);
 483 
 484             /*
 485              * Fix bugid 4242317: Classes defined by this class loader should
 486              * be annotated with the value of the "java.rmi.server.codebase"
 487              * property, not the "file:" URLs for the CLASSPATH elements.
 488              */
 489             sun.rmi.server.LoaderHandler.registerCodebaseLoader(cl);
 490 
 491             Thread.currentThread().setContextClassLoader(cl);
 492 
 493             final int regPort = (args.length >= 1) ? Integer.parseInt(args[0])
 494                                                    : Registry.REGISTRY_PORT;
 495             try {
 496                 registry = AccessController.doPrivileged(
 497                     new PrivilegedExceptionAction<RegistryImpl>() {
 498                         public RegistryImpl run() throws RemoteException {
 499                             return new RegistryImpl(regPort);
 500                         }
 501                     }, getAccessControlContext(regPort));
 502             } catch (PrivilegedActionException ex) {
 503                 throw (RemoteException) ex.getException();
 504             }
 505 
 506             // prevent registry from exiting
 507             while (true) {
 508                 try {
 509                     Thread.sleep(Long.MAX_VALUE);
 510                 } catch (InterruptedException e) {
 511                 }
 512             }
 513         } catch (NumberFormatException e) {
 514             System.err.println(MessageFormat.format(
 515                 getTextResource("rmiregistry.port.badnumber"),
 516                 args[0] ));
 517             System.err.println(MessageFormat.format(
 518                 getTextResource("rmiregistry.usage"),
 519                 "rmiregistry" ));
 520         } catch (Exception e) {
 521             e.printStackTrace();
 522         }
 523         System.exit(1);
 524     }
 525 
 526     /**
 527      * Generates an AccessControlContext with minimal permissions.
 528      * The approach used here is taken from the similar method
 529      * getAccessControlContext() in the sun.applet.AppletPanel class.
 530      */
 531     private static AccessControlContext getAccessControlContext(int port) {
 532         // begin with permissions granted to all code in current policy
 533         PermissionCollection perms = AccessController.doPrivileged(
 534             new java.security.PrivilegedAction<PermissionCollection>() {
 535                 public PermissionCollection run() {
 536                     CodeSource codesource = new CodeSource(null,
 537                         (java.security.cert.Certificate[]) null);
 538                     Policy p = java.security.Policy.getPolicy();
 539                     if (p != null) {
 540                         return p.getPermissions(codesource);
 541                     } else {
 542                         return new Permissions();
 543                     }
 544                 }
 545             });
 546 
 547         /*
 548          * Anyone can connect to the registry and the registry can connect
 549          * to and possibly download stubs from anywhere. Downloaded stubs and
 550          * related classes themselves are more tightly limited by RMI.
 551          */
 552         perms.add(new SocketPermission("*", "connect,accept"));
 553         perms.add(new SocketPermission("localhost:"+port, "listen,accept"));
 554 
 555         perms.add(new RuntimePermission("accessClassInPackage.sun.jvmstat.*"));
 556         perms.add(new RuntimePermission("accessClassInPackage.sun.jvm.hotspot.*"));
 557 
 558         perms.add(new FilePermission("<<ALL FILES>>", "read"));
 559 
 560         /*
 561          * Create an AccessControlContext that consists of a single
 562          * protection domain with only the permissions calculated above.
 563          */
 564         ProtectionDomain pd = new ProtectionDomain(
 565             new CodeSource(null,
 566                 (java.security.cert.Certificate[]) null), perms);
 567         return new AccessControlContext(new ProtectionDomain[] { pd });
 568     }
 569 }