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