1/* 2 * Copyright (c) 1999, 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 26package com.sun.jndi.rmi.registry; 27 28 29import java.util.Hashtable; 30import java.util.Properties; 31import java.rmi.*; 32import java.rmi.server.*; 33import java.rmi.registry.Registry; 34import java.rmi.registry.LocateRegistry; 35import java.security.AccessController; 36import java.security.PrivilegedAction; 37 38import javax.naming.*; 39import javax.naming.spi.NamingManager; 40 41 42/** 43 * A RegistryContext is a context representing a remote RMI registry. 44 * 45 * @author Scott Seligman 46 */ 47 48 49public class RegistryContext implements Context, Referenceable { 50 51 private Hashtable<String, Object> environment; 52 private Registry registry; 53 private String host; 54 private int port; 55 private static final NameParser nameParser = new AtomicNameParser(); 56 private static final String SOCKET_FACTORY = "com.sun.jndi.rmi.factory.socket"; 57 /** 58 * Determines whether classes may be loaded from an arbitrary URL code base. 59 */ 60 static final boolean trustURLCodebase; 61 static { 62 // System property to control whether classes may be loaded from an 63 // arbitrary URL codebase 64 PrivilegedAction<String> act = () -> System.getProperty( 65 "com.sun.jndi.rmi.object.trustURLCodebase", "false"); 66 String trust = AccessController.doPrivileged(act); 67 trustURLCodebase = "true".equalsIgnoreCase(trust); 68 } 69 70 Reference reference = null; // ref used to create this context, if any 71 72 // Environment property that, if set, indicates that a security 73 // manager should be installed (if none is already in place). 74 public static final String SECURITY_MGR = 75 "java.naming.rmi.security.manager"; 76 77 /** 78 * Returns a context for the registry at a given host and port. 79 * If "host" is null, uses default host. 80 * If "port" is non-positive, uses default port. 81 * Cloning of "env" is handled by caller; see comments within 82 * RegistryContextFactory.getObjectInstance(), for example. 83 */ 84 @SuppressWarnings("unchecked") 85 public RegistryContext(String host, int port, Hashtable<?, ?> env) 86 throws NamingException 87 { 88 environment = (env == null) 89 ? new Hashtable<String, Object>(5) 90 : (Hashtable<String, Object>) env; 91 if (environment.get(SECURITY_MGR) != null) { 92 installSecurityMgr(); 93 } 94 95 // chop off '[' and ']' in an IPv6 literal address 96 if ((host != null) && (host.charAt(0) == '[')) { 97 host = host.substring(1, host.length() - 1); 98 } 99 100 RMIClientSocketFactory socketFactory = 101 (RMIClientSocketFactory) environment.get(SOCKET_FACTORY); 102 registry = getRegistry(host, port, socketFactory); 103 this.host = host; 104 this.port = port; 105 } 106 107 /** 108 * Returns a clone of a registry context. The context's private state 109 * is independent of the original's (so closing one context, for example, 110 * won't close the other). 111 */ 112 // %%% Alternatively, this could be done with a clone() method. 113 @SuppressWarnings("unchecked") // clone() 114 RegistryContext(RegistryContext ctx) { 115 environment = (Hashtable<String, Object>)ctx.environment.clone(); 116 registry = ctx.registry; 117 host = ctx.host; 118 port = ctx.port; 119 reference = ctx.reference; 120 } 121 122 @SuppressWarnings("deprecation") 123 protected void finalize() { 124 close(); 125 } 126 127 public Object lookup(Name name) throws NamingException { 128 if (name.isEmpty()) { 129 return (new RegistryContext(this)); 130 } 131 Remote obj; 132 try { 133 obj = registry.lookup(name.get(0)); 134 } catch (NotBoundException e) { 135 throw (new NameNotFoundException(name.get(0))); 136 } catch (RemoteException e) { 137 throw (NamingException)wrapRemoteException(e).fillInStackTrace(); 138 } 139 return (decodeObject(obj, name.getPrefix(1))); 140 } 141 142 public Object lookup(String name) throws NamingException { 143 return lookup(new CompositeName(name)); 144 } 145 146 /** 147 * If the object to be bound is both Remote and Referenceable, binds the 148 * object itself, not its Reference. 149 */ 150 public void bind(Name name, Object obj) throws NamingException { 151 if (name.isEmpty()) { 152 throw (new InvalidNameException( 153 "RegistryContext: Cannot bind empty name")); 154 } 155 try { 156 registry.bind(name.get(0), encodeObject(obj, name.getPrefix(1))); 157 } catch (AlreadyBoundException e) { 158 NamingException ne = new NameAlreadyBoundException(name.get(0)); 159 ne.setRootCause(e); 160 throw ne; 161 } catch (RemoteException e) { 162 throw (NamingException)wrapRemoteException(e).fillInStackTrace(); 163 } 164 } 165 166 public void bind(String name, Object obj) throws NamingException { 167 bind(new CompositeName(name), obj); 168 } 169 170 public void rebind(Name name, Object obj) throws NamingException { 171 if (name.isEmpty()) { 172 throw (new InvalidNameException( 173 "RegistryContext: Cannot rebind empty name")); 174 } 175 try { 176 registry.rebind(name.get(0), encodeObject(obj, name.getPrefix(1))); 177 } catch (RemoteException e) { 178 throw (NamingException)wrapRemoteException(e).fillInStackTrace(); 179 } 180 } 181 182 public void rebind(String name, Object obj) throws NamingException { 183 rebind(new CompositeName(name), obj); 184 } 185 186 public void unbind(Name name) throws NamingException { 187 if (name.isEmpty()) { 188 throw (new InvalidNameException( 189 "RegistryContext: Cannot unbind empty name")); 190 } 191 try { 192 registry.unbind(name.get(0)); 193 } catch (NotBoundException e) { 194 // method is idempotent 195 } catch (RemoteException e) { 196 throw (NamingException)wrapRemoteException(e).fillInStackTrace(); 197 } 198 } 199 200 public void unbind(String name) throws NamingException { 201 unbind(new CompositeName(name)); 202 } 203 204 /** 205 * Rename is implemented by this sequence of operations: 206 * lookup, bind, unbind. The sequence is not performed atomically. 207 */ 208 public void rename(Name oldName, Name newName) throws NamingException { 209 bind(newName, lookup(oldName)); 210 unbind(oldName); 211 } 212 213 public void rename(String name, String newName) throws NamingException { 214 rename(new CompositeName(name), new CompositeName(newName)); 215 } 216 217 public NamingEnumeration<NameClassPair> list(Name name) throws 218 NamingException { 219 if (!name.isEmpty()) { 220 throw (new InvalidNameException( 221 "RegistryContext: can only list \"\"")); 222 } 223 try { 224 String[] names = registry.list(); 225 return (new NameClassPairEnumeration(names)); 226 } catch (RemoteException e) { 227 throw (NamingException)wrapRemoteException(e).fillInStackTrace(); 228 } 229 } 230 231 public NamingEnumeration<NameClassPair> list(String name) throws 232 NamingException { 233 return list(new CompositeName(name)); 234 } 235 236 public NamingEnumeration<Binding> listBindings(Name name) 237 throws NamingException 238 { 239 if (!name.isEmpty()) { 240 throw (new InvalidNameException( 241 "RegistryContext: can only list \"\"")); 242 } 243 try { 244 String[] names = registry.list(); 245 return (new BindingEnumeration(this, names)); 246 } catch (RemoteException e) { 247 throw (NamingException)wrapRemoteException(e).fillInStackTrace(); 248 } 249 } 250 251 public NamingEnumeration<Binding> listBindings(String name) throws 252 NamingException { 253 return listBindings(new CompositeName(name)); 254 } 255 256 public void destroySubcontext(Name name) throws NamingException { 257 throw (new OperationNotSupportedException()); 258 } 259 260 public void destroySubcontext(String name) throws NamingException { 261 throw (new OperationNotSupportedException()); 262 } 263 264 public Context createSubcontext(Name name) throws NamingException { 265 throw (new OperationNotSupportedException()); 266 } 267 268 public Context createSubcontext(String name) throws NamingException { 269 throw (new OperationNotSupportedException()); 270 } 271 272 public Object lookupLink(Name name) throws NamingException { 273 return lookup(name); 274 } 275 276 public Object lookupLink(String name) throws NamingException { 277 return lookup(name); 278 } 279 280 public NameParser getNameParser(Name name) throws NamingException { 281 return nameParser; 282 } 283 284 public NameParser getNameParser(String name) throws NamingException { 285 return nameParser; 286 } 287 288 public Name composeName(Name name, Name prefix) throws NamingException { 289 Name result = (Name)prefix.clone(); 290 return result.addAll(name); 291 } 292 293 public String composeName(String name, String prefix) 294 throws NamingException 295 { 296 return composeName(new CompositeName(name), 297 new CompositeName(prefix)).toString(); 298 } 299 300 public Object removeFromEnvironment(String propName) 301 throws NamingException 302 { 303 return environment.remove(propName); 304 } 305 306 public Object addToEnvironment(String propName, Object propVal) 307 throws NamingException 308 { 309 if (propName.equals(SECURITY_MGR)) { 310 installSecurityMgr(); 311 } 312 return environment.put(propName, propVal); 313 } 314 315 @SuppressWarnings("unchecked") // clone() 316 public Hashtable<String, Object> getEnvironment() throws NamingException { 317 return (Hashtable<String, Object>)environment.clone(); 318 } 319 320 public void close() { 321 environment = null; 322 registry = null; 323 // &&& If we were caching registry connections, we would probably 324 // uncache this one now. 325 } 326 327 public String getNameInNamespace() { 328 return ""; // Registry has an empty name 329 } 330 331 /** 332 * Returns an RMI registry reference for this context. 333 *<p> 334 * If this context was created from a reference, that reference is 335 * returned. Otherwise, an exception is thrown if the registry's 336 * host is "localhost" or the default (null). Although this could 337 * possibly make for a valid reference, it's far more likely to be 338 * an easily made error. 339 * 340 * @see RegistryContextFactory 341 */ 342 public Reference getReference() throws NamingException { 343 if (reference != null) { 344 return (Reference)reference.clone(); // %%% clone the addrs too? 345 } 346 if (host == null || host.equals("localhost")) { 347 throw (new ConfigurationException( 348 "Cannot create a reference for an RMI registry whose " + 349 "host was unspecified or specified as \"localhost\"")); 350 } 351 String url = "rmi://"; 352 353 // Enclose IPv6 literal address in '[' and ']' 354 url = (host.indexOf(':') > -1) ? url + "[" + host + "]" : 355 url + host; 356 if (port > 0) { 357 url += ":" + Integer.toString(port); 358 } 359 RefAddr addr = new StringRefAddr(RegistryContextFactory.ADDRESS_TYPE, 360 url); 361 return (new Reference(RegistryContext.class.getName(), 362 addr, 363 RegistryContextFactory.class.getName(), 364 null)); 365 } 366 367 368 /** 369 * Wrap a RemoteException inside a NamingException. 370 */ 371 public static NamingException wrapRemoteException(RemoteException re) { 372 373 NamingException ne; 374 375 if (re instanceof ConnectException) { 376 ne = new ServiceUnavailableException(); 377 378 } else if (re instanceof AccessException) { 379 ne = new NoPermissionException(); 380 381 } else if (re instanceof StubNotFoundException || 382 re instanceof UnknownHostException) { 383 ne = new ConfigurationException(); 384 385 } else if (re instanceof ExportException || 386 re instanceof ConnectIOException || 387 re instanceof MarshalException || 388 re instanceof UnmarshalException || 389 re instanceof NoSuchObjectException) { 390 ne = new CommunicationException(); 391 392 } else if (re instanceof ServerException && 393 re.detail instanceof RemoteException) { 394 ne = wrapRemoteException((RemoteException)re.detail); 395 396 } else { 397 ne = new NamingException(); 398 } 399 ne.setRootCause(re); 400 return ne; 401 } 402 403 /** 404 * Returns the registry at a given host, port and socket factory. 405 * If "host" is null, uses default host. 406 * If "port" is non-positive, uses default port. 407 * If "socketFactory" is null, uses the default socket. 408 */ 409 private static Registry getRegistry(String host, int port, 410 RMIClientSocketFactory socketFactory) 411 throws NamingException 412 { 413 // %%% We could cache registry connections here. The transport layer 414 // may already reuse connections. 415 try { 416 if (socketFactory == null) { 417 return LocateRegistry.getRegistry(host, port); 418 } else { 419 return LocateRegistry.getRegistry(host, port, socketFactory); 420 } 421 } catch (RemoteException e) { 422 throw (NamingException)wrapRemoteException(e).fillInStackTrace(); 423 } 424 } 425 426 /** 427 * Attempts to install a security manager if none is currently in 428 * place. 429 */ 430 private static void installSecurityMgr() { 431 432 try { 433 System.setSecurityManager(new SecurityManager()); 434 } catch (Exception e) { 435 } 436 } 437 438 /** 439 * Encodes an object prior to binding it in the registry. First, 440 * NamingManager.getStateToBind() is invoked. If the resulting 441 * object is Remote, it is returned. If it is a Reference or 442 * Referenceable, the reference is wrapped in a Remote object. 443 * Otherwise, an exception is thrown. 444 * 445 * @param name The object's name relative to this context. 446 */ 447 private Remote encodeObject(Object obj, Name name) 448 throws NamingException, RemoteException 449 { 450 obj = NamingManager.getStateToBind(obj, name, this, environment); 451 452 if (obj instanceof Remote) { 453 return (Remote)obj; 454 } 455 if (obj instanceof Reference) { 456 return (new ReferenceWrapper((Reference)obj)); 457 } 458 if (obj instanceof Referenceable) { 459 return (new ReferenceWrapper(((Referenceable)obj).getReference())); 460 } 461 throw (new IllegalArgumentException( 462 "RegistryContext: " + 463 "object to bind must be Remote, Reference, or Referenceable")); 464 } 465 466 /** 467 * Decodes an object that has been retrieved from the registry. 468 * First, if the object is a RemoteReference, the Reference is 469 * unwrapped. Then, NamingManager.getObjectInstance() is invoked. 470 * 471 * @param name The object's name relative to this context. 472 */ 473 private Object decodeObject(Remote r, Name name) throws NamingException { 474 try { 475 Object obj = (r instanceof RemoteReference) 476 ? ((RemoteReference)r).getReference() 477 : (Object)r; 478 479 /* 480 * Classes may only be loaded from an arbitrary URL codebase when 481 * the system property com.sun.jndi.rmi.object.trustURLCodebase 482 * has been set to "true". 483 */ 484 485 // Use reference if possible 486 Reference ref = null; 487 if (obj instanceof Reference) { 488 ref = (Reference) obj; 489 } else if (obj instanceof Referenceable) { 490 ref = ((Referenceable)(obj)).getReference(); 491 } 492 493 if (ref != null && ref.getFactoryClassLocation() != null && 494 !trustURLCodebase) { 495 throw new ConfigurationException( 496 "The object factory is untrusted. Set the system property" + 497 " 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'."); 498 } 499 return NamingManager.getObjectInstance(obj, name, this, 500 environment); 501 } catch (NamingException e) { 502 throw e; 503 } catch (RemoteException e) { 504 throw (NamingException) 505 wrapRemoteException(e).fillInStackTrace(); 506 } catch (Exception e) { 507 NamingException ne = new NamingException(); 508 ne.setRootCause(e); 509 throw ne; 510 } 511 } 512 513} 514 515 516/** 517 * A name parser for case-sensitive atomic names. 518 */ 519class AtomicNameParser implements NameParser { 520 private static final Properties syntax = new Properties(); 521 522 public Name parse(String name) throws NamingException { 523 return (new CompoundName(name, syntax)); 524 } 525} 526 527 528/** 529 * An enumeration of name / class-name pairs. 530 */ 531class NameClassPairEnumeration implements NamingEnumeration<NameClassPair> { 532 private final String[] names; 533 private int nextName; // index into "names" 534 535 NameClassPairEnumeration(String[] names) { 536 this.names = names; 537 nextName = 0; 538 } 539 540 public boolean hasMore() { 541 return (nextName < names.length); 542 } 543 544 public NameClassPair next() throws NamingException { 545 if (!hasMore()) { 546 throw (new java.util.NoSuchElementException()); 547 } 548 // Convert name to a one-element composite name, so embedded 549 // meta-characters are properly escaped. 550 String name = names[nextName++]; 551 Name cname = (new CompositeName()).add(name); 552 NameClassPair ncp = new NameClassPair(cname.toString(), 553 "java.lang.Object"); 554 ncp.setNameInNamespace(name); 555 return ncp; 556 } 557 558 public boolean hasMoreElements() { 559 return hasMore(); 560 } 561 562 public NameClassPair nextElement() { 563 try { 564 return next(); 565 } catch (NamingException e) { // should never happen 566 throw (new java.util.NoSuchElementException( 567 "javax.naming.NamingException was thrown")); 568 } 569 } 570 571 public void close() { 572 nextName = names.length; 573 } 574} 575 576 577/** 578 * An enumeration of Bindings. 579 * 580 * The actual registry lookups are performed when next() is called. It would 581 * be nicer to defer this until the object (or its class name) is actually 582 * requested. The problem with that approach is that Binding.getObject() 583 * cannot throw NamingException. 584 */ 585class BindingEnumeration implements NamingEnumeration<Binding> { 586 private RegistryContext ctx; 587 private final String[] names; 588 private int nextName; // index into "names" 589 590 BindingEnumeration(RegistryContext ctx, String[] names) { 591 // Clone ctx in case someone closes it before we're through. 592 this.ctx = new RegistryContext(ctx); 593 this.names = names; 594 nextName = 0; 595 } 596 597 @SuppressWarnings("deprecation") 598 protected void finalize() { 599 ctx.close(); 600 } 601 602 public boolean hasMore() { 603 if (nextName >= names.length) { 604 ctx.close(); 605 } 606 return (nextName < names.length); 607 } 608 609 public Binding next() throws NamingException { 610 if (!hasMore()) { 611 throw (new java.util.NoSuchElementException()); 612 } 613 // Convert name to a one-element composite name, so embedded 614 // meta-characters are properly escaped. 615 String name = names[nextName++]; 616 Name cname = (new CompositeName()).add(name); 617 618 Object obj = ctx.lookup(cname); 619 String cnameStr = cname.toString(); 620 Binding binding = new Binding(cnameStr, obj); 621 binding.setNameInNamespace(cnameStr); 622 return binding; 623 } 624 625 public boolean hasMoreElements() { 626 return hasMore(); 627 } 628 629 public Binding nextElement() { 630 try { 631 return next(); 632 } catch (NamingException e) { 633 throw (new java.util.NoSuchElementException( 634 "javax.naming.NamingException was thrown")); 635 } 636 } 637 638 @SuppressWarnings("deprecation") 639 public void close () { 640 finalize(); 641 } 642} 643