1 /*
   2  * Copyright (c) 1996, 2011, 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.io.IOException;
  34 import java.net.*;
  35 import java.rmi.*;
  36 import java.rmi.server.ObjID;
  37 import java.rmi.server.RemoteServer;
  38 import java.rmi.server.ServerNotActiveException;
  39 import java.rmi.registry.Registry;
  40 import java.rmi.server.RMIClientSocketFactory;
  41 import java.rmi.server.RMIServerSocketFactory;
  42 import java.security.AccessControlContext;
  43 import java.security.AccessController;
  44 import java.security.CodeSource;
  45 import java.security.Policy;
  46 import java.security.PrivilegedActionException;
  47 import java.security.PrivilegedExceptionAction;
  48 import java.security.PermissionCollection;
  49 import java.security.Permissions;
  50 import java.security.ProtectionDomain;
  51 import java.text.MessageFormat;
  52 import sun.rmi.server.LoaderHandler;
  53 import sun.rmi.server.UnicastServerRef;
  54 import sun.rmi.server.UnicastServerRef2;
  55 import sun.rmi.transport.LiveRef;
  56 import sun.rmi.transport.ObjectTable;
  57 import sun.rmi.transport.Target;
  58 
  59 /**
  60  * A "registry" exists on every node that allows RMI connections to
  61  * servers on that node.  The registry on a particular node contains a
  62  * transient database that maps names to remote objects.  When the
  63  * node boots, the registry database is empty.  The names stored in the
  64  * registry are pure and are not parsed.  A service storing itself in
  65  * the registry may want to prefix its name of the service by a package
  66  * name (although not required), to reduce name collisions in the
  67  * registry.
  68  *
  69  * The LocateRegistry class is used to obtain registry for different hosts.
  70  *
  71  * @see java.rmi.registry.LocateRegistry
  72  */
  73 public class RegistryImpl extends java.rmi.server.RemoteServer
  74         implements Registry
  75 {
  76 
  77     /* indicate compatibility with JDK 1.1.x version of class */
  78     private static final long serialVersionUID = 4666870661827494597L;
  79     private Hashtable<String, Remote> bindings
  80         = new Hashtable<String, Remote>(101);
  81     private static Hashtable<InetAddress, InetAddress> allowedAccessCache
  82         = new Hashtable<InetAddress, InetAddress>(3);
  83     private static RegistryImpl registry;
  84     private static ObjID id = new ObjID(ObjID.REGISTRY_ID);
  85 
  86     private static ResourceBundle resources = null;
  87 
  88     /**
  89      * Construct a new RegistryImpl on the specified port with the
  90      * given custom socket factory pair.
  91      */
  92     public RegistryImpl(int port,
  93                         RMIClientSocketFactory csf,
  94                         RMIServerSocketFactory ssf)
  95         throws RemoteException
  96     {
  97         LiveRef lref = new LiveRef(id, port, csf, ssf);
  98         setup(new UnicastServerRef2(lref));
  99     }
 100 
 101     /**
 102      * Construct a new RegistryImpl on the specified port.
 103      */
 104     public RegistryImpl(int port)
 105         throws RemoteException
 106     {
 107         LiveRef lref = new LiveRef(id, port);
 108         setup(new UnicastServerRef(lref));
 109     }
 110 
 111     /*
 112      * Create the export the object using the parameter
 113      * <code>uref</code>
 114      */
 115     private void setup(UnicastServerRef uref)
 116         throws RemoteException
 117     {
 118         /* Server ref must be created and assigned before remote
 119          * object 'this' can be exported.
 120          */
 121         ref = uref;
 122         uref.exportObject(this, null, true);
 123     }
 124 
 125     /**
 126      * Returns the remote object for specified name in the registry.
 127      * @exception RemoteException If remote operation failed.
 128      * @exception NotBound If name is not currently bound.
 129      */
 130     public Remote lookup(String name)
 131         throws RemoteException, NotBoundException
 132     {
 133         synchronized (bindings) {
 134             Remote obj = bindings.get(name);
 135             if (obj == null)
 136                 throw new NotBoundException(name);
 137             return obj;
 138         }
 139     }
 140 
 141     /**
 142      * Binds the name to the specified remote object.
 143      * @exception RemoteException If remote operation failed.
 144      * @exception AlreadyBoundException If name is already bound.
 145      */
 146     public void bind(String name, Remote obj)
 147         throws RemoteException, AlreadyBoundException, AccessException
 148     {
 149         checkAccess("Registry.bind");
 150         synchronized (bindings) {
 151             Remote curr = bindings.get(name);
 152             if (curr != null)
 153                 throw new AlreadyBoundException(name);
 154             bindings.put(name, obj);
 155         }
 156     }
 157 
 158     /**
 159      * Unbind the name.
 160      * @exception RemoteException If remote operation failed.
 161      * @exception NotBound If name is not currently bound.
 162      */
 163     public void unbind(String name)
 164         throws RemoteException, NotBoundException, AccessException
 165     {
 166         checkAccess("Registry.unbind");
 167         synchronized (bindings) {
 168             Remote obj = bindings.get(name);
 169             if (obj == null)
 170                 throw new NotBoundException(name);
 171             bindings.remove(name);
 172         }
 173     }
 174 
 175     /**
 176      * Rebind the name to a new object, replaces any existing binding.
 177      * @exception RemoteException If remote operation failed.
 178      */
 179     public void rebind(String name, Remote obj)
 180         throws RemoteException, AccessException
 181     {
 182         checkAccess("Registry.rebind");
 183         bindings.put(name, obj);
 184     }
 185 
 186     /**
 187      * Returns an enumeration of the names in the registry.
 188      * @exception RemoteException If remote operation failed.
 189      */
 190     public String[] list()
 191         throws RemoteException
 192     {
 193         String[] names;
 194         synchronized (bindings) {
 195             int i = bindings.size();
 196             names = new String[i];
 197             Enumeration enum_ = bindings.keys();
 198             while ((--i) >= 0)
 199                 names[i] = (String)enum_.nextElement();
 200         }
 201         return names;
 202     }
 203 
 204     /**
 205      * Check that the caller has access to perform indicated operation.
 206      * The client must be on same the same host as this server.
 207      */
 208     public static void checkAccess(String op) throws AccessException {
 209 
 210         try {
 211             /*
 212              * Get client host that this registry operation was made from.
 213              */
 214             final String clientHostName = getClientHost();
 215             InetAddress clientHost;
 216 
 217             try {
 218                 clientHost = java.security.AccessController.doPrivileged(
 219                     new java.security.PrivilegedExceptionAction<InetAddress>() {
 220                         public InetAddress run()
 221                             throws java.net.UnknownHostException
 222                         {
 223                             return InetAddress.getByName(clientHostName);
 224                         }
 225                     });
 226             } catch (PrivilegedActionException pae) {
 227                 throw (java.net.UnknownHostException) pae.getException();
 228             }
 229 
 230             // if client not yet seen, make sure client allowed access
 231             if (allowedAccessCache.get(clientHost) == null) {
 232 
 233                 if (clientHost.isAnyLocalAddress()) {
 234                     throw new AccessException(
 235                         "Registry." + op + " disallowed; origin unknown");
 236                 }
 237 
 238                 try {
 239                     final InetAddress finalClientHost = clientHost;
 240 
 241                     java.security.AccessController.doPrivileged(
 242                         new java.security.PrivilegedExceptionAction<Void>() {
 243                             public Void run() throws java.io.IOException {
 244                                 /*
 245                                  * if a ServerSocket can be bound to the client's
 246                                  * address then that address must be local
 247                                  */
 248                                 (new ServerSocket(0, 10, finalClientHost)).close();
 249                                 allowedAccessCache.put(finalClientHost,
 250                                                        finalClientHost);
 251                                 return null;
 252                             }
 253                     });
 254                 } catch (PrivilegedActionException pae) {
 255                     // must have been an IOException
 256 
 257                     throw new AccessException(
 258                         "Registry." + op + " disallowed; origin " +
 259                         clientHost + " is non-local host");
 260                 }
 261             }
 262         } catch (ServerNotActiveException ex) {
 263             /*
 264              * Local call from this VM: allow access.
 265              */
 266         } catch (java.net.UnknownHostException ex) {
 267             throw new AccessException("Registry." + op +
 268                                       " disallowed; origin is unknown host");
 269         }
 270     }
 271 
 272     public static ObjID getID() {
 273         return id;
 274     }
 275 
 276     /**
 277      * Retrieves text resources from the locale-specific properties file.
 278      */
 279     private static String getTextResource(String key) {
 280         if (resources == null) {
 281             try {
 282                 resources = ResourceBundle.getBundle(
 283                     "sun.rmi.registry.resources.rmiregistry");
 284             } catch (MissingResourceException mre) {
 285             }
 286             if (resources == null) {
 287                 // throwing an Error is a bit extreme, methinks
 288                 return ("[missing resource file: " + key + "]");
 289             }
 290         }
 291 
 292         String val = null;
 293         try {
 294             val = resources.getString(key);
 295         } catch (MissingResourceException mre) {
 296         }
 297 
 298         if (val == null) {
 299             return ("[missing resource: " + key + "]");
 300         } else {
 301             return (val);
 302         }
 303     }
 304 
 305     /**
 306      * Main program to start a registry. <br>
 307      * The port number can be specified on the command line.
 308      */
 309     public static void main(String args[])
 310     {
 311         // Create and install the security manager if one is not installed
 312         // already.
 313         if (System.getSecurityManager() == null) {
 314             System.setSecurityManager(new RMISecurityManager());
 315         }
 316 
 317         try {
 318             /*
 319              * Fix bugid 4147561: When JDK tools are executed, the value of
 320              * the CLASSPATH environment variable for the shell in which they
 321              * were invoked is no longer incorporated into the application
 322              * class path; CLASSPATH's only effect is to be the value of the
 323              * system property "env.class.path".  To preserve the previous
 324              * (JDK1.1 and JDK1.2beta3) behavior of this tool, however, its
 325              * CLASSPATH should still be considered when resolving classes
 326              * being unmarshalled.  To effect this old behavior, a class
 327              * loader that loads from the file path specified in the
 328              * "env.class.path" property is created and set to be the context
 329              * class loader before the remote object is exported.
 330              */
 331             String envcp = System.getProperty("env.class.path");
 332             if (envcp == null) {
 333                 envcp = ".";            // preserve old default behavior
 334             }
 335             URL[] urls = sun.misc.URLClassPath.pathToURLs(envcp);
 336             ClassLoader cl = new URLClassLoader(urls);
 337 
 338             /*
 339              * Fix bugid 4242317: Classes defined by this class loader should
 340              * be annotated with the value of the "java.rmi.server.codebase"
 341              * property, not the "file:" URLs for the CLASSPATH elements.
 342              */
 343             sun.rmi.server.LoaderHandler.registerCodebaseLoader(cl);
 344 
 345             Thread.currentThread().setContextClassLoader(cl);
 346 
 347             final int regPort = (args.length >= 1) ? Integer.parseInt(args[0])
 348                                                    : Registry.REGISTRY_PORT;
 349             try {
 350                 registry = AccessController.doPrivileged(
 351                     new PrivilegedExceptionAction<RegistryImpl>() {
 352                         public RegistryImpl run() throws RemoteException {
 353                             return new RegistryImpl(regPort);
 354                         }
 355                     }, getAccessControlContext());
 356             } catch (PrivilegedActionException ex) {
 357                 throw (RemoteException) ex.getException();
 358             }
 359 
 360             // prevent registry from exiting
 361             while (true) {
 362                 try {
 363                     Thread.sleep(Long.MAX_VALUE);
 364                 } catch (InterruptedException e) {
 365                 }
 366             }
 367         } catch (NumberFormatException e) {
 368             System.err.println(MessageFormat.format(
 369                 getTextResource("rmiregistry.port.badnumber"),
 370                 args[0] ));
 371             System.err.println(MessageFormat.format(
 372                 getTextResource("rmiregistry.usage"),
 373                 "rmiregistry" ));
 374         } catch (Exception e) {
 375             e.printStackTrace();
 376         }
 377         System.exit(1);
 378     }
 379 
 380     /**
 381      * Generates an AccessControlContext with minimal permissions.
 382      * The approach used here is taken from the similar method
 383      * getAccessControlContext() in the sun.applet.AppletPanel class.
 384      */
 385     private static AccessControlContext getAccessControlContext() {
 386         // begin with permissions granted to all code in current policy
 387         PermissionCollection perms = AccessController.doPrivileged(
 388             new java.security.PrivilegedAction<PermissionCollection>() {
 389                 public PermissionCollection run() {
 390                     CodeSource codesource = new CodeSource(null,
 391                         (java.security.cert.Certificate[]) null);
 392                     Policy p = java.security.Policy.getPolicy();
 393                     if (p != null) {
 394                         return p.getPermissions(codesource);
 395                     } else {
 396                         return new Permissions();
 397                     }
 398                 }
 399             });
 400 
 401         /*
 402          * Anyone can connect to the registry and the registry can connect
 403          * to and possibly download stubs from anywhere. Downloaded stubs and
 404          * related classes themselves are more tightly limited by RMI.
 405          */
 406         perms.add(new SocketPermission("*", "connect,accept"));
 407 
 408         perms.add(new RuntimePermission("accessClassInPackage.sun.*"));
 409 
 410         perms.add(new FilePermission("<<ALL FILES>>", "read"));
 411 
 412         /*
 413          * Create an AccessControlContext that consists of a single
 414          * protection domain with only the permissions calculated above.
 415          */
 416         ProtectionDomain pd = new ProtectionDomain(
 417             new CodeSource(null,
 418                 (java.security.cert.Certificate[]) null), perms);
 419         return new AccessControlContext(new ProtectionDomain[] { pd });
 420     }
 421 }