1 /* 2 * Copyright (c) 1999, 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 com.sun.jndi.ldap; 27 28 import javax.naming.*; 29 import javax.naming.directory.*; 30 import javax.naming.spi.DirectoryManager; 31 import javax.naming.spi.DirStateFactory; 32 33 import java.io.IOException; 34 import java.io.ByteArrayInputStream; 35 import java.io.ByteArrayOutputStream; 36 import java.io.ObjectInputStream; 37 import java.io.ObjectOutputStream; 38 import java.io.ObjectStreamClass; 39 import java.io.InputStream; 40 41 import java.util.Hashtable; 42 import java.util.Vector; 43 import java.util.StringTokenizer; 44 45 import sun.misc.BASE64Encoder; 46 import sun.misc.BASE64Decoder; 47 48 import java.lang.reflect.Proxy; 49 import java.lang.reflect.Modifier; 50 51 /** 52 * Class containing static methods and constants for dealing with 53 * encoding/decoding JNDI References and Serialized Objects 54 * in LDAP. 55 * @author Vincent Ryan 56 * @author Rosanna Lee 57 */ 58 final class Obj { 59 60 private Obj () {}; // Make sure no one can create one 61 62 // package private; used by Connection 63 static VersionHelper helper = VersionHelper.getVersionHelper(); 64 65 // LDAP attributes used to support Java objects. 66 static final String[] JAVA_ATTRIBUTES = { 67 "objectClass", 68 "javaSerializedData", 69 "javaClassName", 70 "javaFactory", 71 "javaCodeBase", 72 "javaReferenceAddress", 73 "javaClassNames", 74 "javaRemoteLocation" // Deprecated 75 }; 76 77 static final int OBJECT_CLASS = 0; 78 static final int SERIALIZED_DATA = 1; 79 static final int CLASSNAME = 2; 80 static final int FACTORY = 3; 81 static final int CODEBASE = 4; 82 static final int REF_ADDR = 5; 83 static final int TYPENAME = 6; 84 /** 85 * @deprecated 86 */ 87 @Deprecated 88 private static final int REMOTE_LOC = 7; 89 90 // LDAP object classes to support Java objects 91 static final String[] JAVA_OBJECT_CLASSES = { 92 "javaContainer", 93 "javaObject", 94 "javaNamingReference", 95 "javaSerializedObject", 96 "javaMarshalledObject", 97 }; 98 99 static final String[] JAVA_OBJECT_CLASSES_LOWER = { 100 "javacontainer", 101 "javaobject", 102 "javanamingreference", 103 "javaserializedobject", 104 "javamarshalledobject", 105 }; 106 107 static final int STRUCTURAL = 0; // structural object class 108 static final int BASE_OBJECT = 1; // auxiliary java object class 109 static final int REF_OBJECT = 2; // auxiliary reference object class 110 static final int SER_OBJECT = 3; // auxiliary serialized object class 111 static final int MAR_OBJECT = 4; // auxiliary marshalled object class 112 113 /** 114 * Encode an object in LDAP attributes. 115 * Supports binding Referenceable or Reference, Serializable, 116 * and DirContext. 117 * 118 * If the object supports the Referenceable interface then encode 119 * the reference to the object. See encodeReference() for details. 120 *<p> 121 * If the object is serializable, it is stored as follows: 122 * javaClassName 123 * value: Object.getClass(); 124 * javaSerializedData 125 * value: serialized form of Object (in binary form). 126 * javaTypeName 127 * value: getTypeNames(Object.getClass()); 128 */ 129 private static Attributes encodeObject(char separator, 130 Object obj, Attributes attrs, 131 Attribute objectClass, boolean cloned) 132 throws NamingException { 133 boolean structural = 134 (objectClass.size() == 0 || 135 (objectClass.size() == 1 && objectClass.contains("top"))); 136 137 if (structural) { 138 objectClass.add(JAVA_OBJECT_CLASSES[STRUCTURAL]); 139 } 140 141 // References 142 if (obj instanceof Referenceable) { 143 objectClass.add(JAVA_OBJECT_CLASSES[BASE_OBJECT]); 144 objectClass.add(JAVA_OBJECT_CLASSES[REF_OBJECT]); 145 if (!cloned) { 146 attrs = (Attributes)attrs.clone(); 147 } 148 attrs.put(objectClass); 149 return (encodeReference(separator, 150 ((Referenceable)obj).getReference(), 151 attrs, obj)); 152 153 } else if (obj instanceof Reference) { 154 objectClass.add(JAVA_OBJECT_CLASSES[BASE_OBJECT]); 155 objectClass.add(JAVA_OBJECT_CLASSES[REF_OBJECT]); 156 if (!cloned) { 157 attrs = (Attributes)attrs.clone(); 158 } 159 attrs.put(objectClass); 160 return (encodeReference(separator, (Reference)obj, attrs, null)); 161 162 // Serializable Object 163 } else if (obj instanceof java.io.Serializable) { 164 objectClass.add(JAVA_OBJECT_CLASSES[BASE_OBJECT]); 165 if (!(objectClass.contains(JAVA_OBJECT_CLASSES[MAR_OBJECT]) || 166 objectClass.contains(JAVA_OBJECT_CLASSES_LOWER[MAR_OBJECT]))) { 167 objectClass.add(JAVA_OBJECT_CLASSES[SER_OBJECT]); 168 } 169 if (!cloned) { 170 attrs = (Attributes)attrs.clone(); 171 } 172 attrs.put(objectClass); 173 attrs.put(new BasicAttribute(JAVA_ATTRIBUTES[SERIALIZED_DATA], 174 serializeObject(obj))); 175 if (attrs.get(JAVA_ATTRIBUTES[CLASSNAME]) == null) { 176 attrs.put(JAVA_ATTRIBUTES[CLASSNAME], 177 obj.getClass().getName()); 178 } 179 if (attrs.get(JAVA_ATTRIBUTES[TYPENAME]) == null) { 180 Attribute tAttr = 181 LdapCtxFactory.createTypeNameAttr(obj.getClass()); 182 if (tAttr != null) { 183 attrs.put(tAttr); 184 } 185 } 186 // DirContext Object 187 } else if (obj instanceof DirContext) { 188 // do nothing 189 } else { 190 throw new IllegalArgumentException( 191 "can only bind Referenceable, Serializable, DirContext"); 192 } 193 // System.err.println(attrs); 194 return attrs; 195 } 196 197 /** 198 * Each value in javaCodebase contains a list of space-separated 199 * URLs. Each value is independent; we can pick any of the values 200 * so we just use the first one. 201 * @return an array of URL strings for the codebase 202 */ 203 private static String[] getCodebases(Attribute codebaseAttr) throws 204 NamingException { 205 if (codebaseAttr == null) { 206 return null; 207 } else { 208 StringTokenizer parser = 209 new StringTokenizer((String)codebaseAttr.get()); 210 Vector<String> vec = new Vector<>(10); 211 while (parser.hasMoreTokens()) { 212 vec.addElement(parser.nextToken()); 213 } 214 String[] answer = new String[vec.size()]; 215 for (int i = 0; i < answer.length; i++) { 216 answer[i] = vec.elementAt(i); 217 } 218 return answer; 219 } 220 } 221 222 /* 223 * Decode an object from LDAP attribute(s). 224 * The object may be a Reference, or a Serialized object. 225 * 226 * See encodeObject() and encodeReference() for details on formats 227 * expected. 228 */ 229 static Object decodeObject(Attributes attrs) 230 throws NamingException { 231 232 Attribute attr; 233 234 // Get codebase, which is used in all 3 cases. 235 String[] codebases = getCodebases(attrs.get(JAVA_ATTRIBUTES[CODEBASE])); 236 try { 237 if ((attr = attrs.get(JAVA_ATTRIBUTES[SERIALIZED_DATA])) != null) { 238 ClassLoader cl = helper.getURLClassLoader(codebases); 239 return deserializeObject((byte[])attr.get(), cl); 240 } else if ((attr = attrs.get(JAVA_ATTRIBUTES[REMOTE_LOC])) != null) { 241 // For backward compatibility only 242 return decodeRmiObject( 243 (String)attrs.get(JAVA_ATTRIBUTES[CLASSNAME]).get(), 244 (String)attr.get(), codebases); 245 } 246 247 attr = attrs.get(JAVA_ATTRIBUTES[OBJECT_CLASS]); 248 if (attr != null && 249 (attr.contains(JAVA_OBJECT_CLASSES[REF_OBJECT]) || 250 attr.contains(JAVA_OBJECT_CLASSES_LOWER[REF_OBJECT]))) { 251 return decodeReference(attrs, codebases); 252 } 253 return null; 254 } catch (IOException e) { 255 NamingException ne = new NamingException(); 256 ne.setRootCause(e); 257 throw ne; 258 } 259 } 260 261 /** 262 * Convert a Reference object into several LDAP attributes. 263 * 264 * A Reference is stored as into the following attributes: 265 * javaClassName 266 * value: Reference.getClassName(); 267 * javaFactory 268 * value: Reference.getFactoryClassName(); 269 * javaCodeBase 270 * value: Reference.getFactoryClassLocation(); 271 * javaReferenceAddress 272 * value: #0#typeA#valA 273 * value: #1#typeB#valB 274 * value: #2#typeC##[serialized RefAddr C] 275 * value: #3#typeD#valD 276 * 277 * where 278 * - the first character denotes the separator 279 * - the number following the first separator denotes the position 280 * of the RefAddr within the Reference 281 * - "typeA" is RefAddr.getType() 282 * - ## denotes that the Base64-encoded form of the non-StringRefAddr 283 * is to follow; otherwise the value that follows is 284 * StringRefAddr.getContents() 285 * 286 * The default separator is the hash character (#). 287 * May provide property for this in future. 288 */ 289 290 private static Attributes encodeReference(char separator, 291 Reference ref, Attributes attrs, Object orig) 292 throws NamingException { 293 294 if (ref == null) 295 return attrs; 296 297 String s; 298 299 if ((s = ref.getClassName()) != null) { 300 attrs.put(new BasicAttribute(JAVA_ATTRIBUTES[CLASSNAME], s)); 301 } 302 303 if ((s = ref.getFactoryClassName()) != null) { 304 attrs.put(new BasicAttribute(JAVA_ATTRIBUTES[FACTORY], s)); 305 } 306 307 if ((s = ref.getFactoryClassLocation()) != null) { 308 attrs.put(new BasicAttribute(JAVA_ATTRIBUTES[CODEBASE], s)); 309 } 310 311 // Get original object's types if caller has not explicitly 312 // specified other type names 313 if (orig != null && attrs.get(JAVA_ATTRIBUTES[TYPENAME]) != null) { 314 Attribute tAttr = 315 LdapCtxFactory.createTypeNameAttr(orig.getClass()); 316 if (tAttr != null) { 317 attrs.put(tAttr); 318 } 319 } 320 321 int count = ref.size(); 322 323 if (count > 0) { 324 325 Attribute refAttr = new BasicAttribute(JAVA_ATTRIBUTES[REF_ADDR]); 326 RefAddr refAddr; 327 BASE64Encoder encoder = null; 328 329 for (int i = 0; i < count; i++) { 330 refAddr = ref.get(i); 331 332 if (refAddr instanceof StringRefAddr) { 333 refAttr.add(""+ separator + i + 334 separator + refAddr.getType() + 335 separator + refAddr.getContent()); 336 } else { 337 if (encoder == null) 338 encoder = new BASE64Encoder(); 339 340 refAttr.add(""+ separator + i + 341 separator + refAddr.getType() + 342 separator + separator + 343 encoder.encodeBuffer(serializeObject(refAddr))); 344 } 345 } 346 attrs.put(refAttr); 347 } 348 return attrs; 349 } 350 351 /* 352 * A RMI object is stored in the directory as 353 * javaClassName 354 * value: Object.getClass(); 355 * javaRemoteLocation 356 * value: URL of RMI object (accessed through the RMI Registry) 357 * javaCodebase: 358 * value: URL of codebase of where to find classes for object 359 * 360 * Return the RMI Location URL itself. This will be turned into 361 * an RMI object when getObjectInstance() is called on it. 362 * %%% Ignore codebase for now. Depend on RMI registry to send code.-RL 363 * @deprecated For backward compatibility only 364 */ 365 private static Object decodeRmiObject(String className, 366 String rmiName, String[] codebases) throws NamingException { 367 return new Reference(className, new StringRefAddr("URL", rmiName)); 368 } 369 370 /* 371 * Restore a Reference object from several LDAP attributes 372 */ 373 private static Reference decodeReference(Attributes attrs, 374 String[] codebases) throws NamingException, IOException { 375 376 Attribute attr; 377 String className; 378 String factory = null; 379 380 if ((attr = attrs.get(JAVA_ATTRIBUTES[CLASSNAME])) != null) { 381 className = (String)attr.get(); 382 } else { 383 throw new InvalidAttributesException(JAVA_ATTRIBUTES[CLASSNAME] + 384 " attribute is required"); 385 } 386 387 if ((attr = attrs.get(JAVA_ATTRIBUTES[FACTORY])) != null) { 388 factory = (String)attr.get(); 389 } 390 391 Reference ref = new Reference(className, factory, 392 (codebases != null? codebases[0] : null)); 393 394 /* 395 * string encoding of a RefAddr is either: 396 * 397 * #posn#<type>#<address> 398 * or 399 * #posn#<type>##<base64-encoded address> 400 */ 401 if ((attr = attrs.get(JAVA_ATTRIBUTES[REF_ADDR])) != null) { 402 403 String val, posnStr, type; 404 char separator; 405 int start, sep, posn; 406 BASE64Decoder decoder = null; 407 408 ClassLoader cl = helper.getURLClassLoader(codebases); 409 410 /* 411 * Temporary Vector for decoded RefAddr addresses - used to ensure 412 * unordered addresses are correctly re-ordered. 413 */ 414 Vector<RefAddr> refAddrList = new Vector<>(); 415 refAddrList.setSize(attr.size()); 416 417 for (NamingEnumeration<?> vals = attr.getAll(); vals.hasMore(); ) { 418 419 val = (String)vals.next(); 420 421 if (val.length() == 0) { 422 throw new InvalidAttributeValueException( 423 "malformed " + JAVA_ATTRIBUTES[REF_ADDR] + " attribute - "+ 424 "empty attribute value"); 425 } 426 // first character denotes encoding separator 427 separator = val.charAt(0); 428 start = 1; // skip over separator 429 430 // extract position within Reference 431 if ((sep = val.indexOf(separator, start)) < 0) { 432 throw new InvalidAttributeValueException( 433 "malformed " + JAVA_ATTRIBUTES[REF_ADDR] + " attribute - " + 434 "separator '" + separator + "'" + "not found"); 435 } 436 if ((posnStr = val.substring(start, sep)) == null) { 437 throw new InvalidAttributeValueException( 438 "malformed " + JAVA_ATTRIBUTES[REF_ADDR] + " attribute - " + 439 "empty RefAddr position"); 440 } 441 try { 442 posn = Integer.parseInt(posnStr); 443 } catch (NumberFormatException nfe) { 444 throw new InvalidAttributeValueException( 445 "malformed " + JAVA_ATTRIBUTES[REF_ADDR] + " attribute - " + 446 "RefAddr position not an integer"); 447 } 448 start = sep + 1; // skip over position and trailing separator 449 450 // extract type 451 if ((sep = val.indexOf(separator, start)) < 0) { 452 throw new InvalidAttributeValueException( 453 "malformed " + JAVA_ATTRIBUTES[REF_ADDR] + " attribute - " + 454 "RefAddr type not found"); 455 } 456 if ((type = val.substring(start, sep)) == null) { 457 throw new InvalidAttributeValueException( 458 "malformed " + JAVA_ATTRIBUTES[REF_ADDR] + " attribute - " + 459 "empty RefAddr type"); 460 } 461 start = sep + 1; // skip over type and trailing separator 462 463 // extract content 464 if (start == val.length()) { 465 // Empty content 466 refAddrList.setElementAt(new StringRefAddr(type, null), posn); 467 } else if (val.charAt(start) == separator) { 468 // Double separators indicate a non-StringRefAddr 469 // Content is a Base64-encoded serialized RefAddr 470 471 ++start; // skip over consecutive separator 472 // %%% RL: exception if empty after double separator 473 474 if (decoder == null) 475 decoder = new BASE64Decoder(); 476 477 RefAddr ra = (RefAddr) 478 deserializeObject( 479 decoder.decodeBuffer(val.substring(start)), 480 cl); 481 482 refAddrList.setElementAt(ra, posn); 483 } else { 484 // Single separator indicates a StringRefAddr 485 refAddrList.setElementAt(new StringRefAddr(type, 486 val.substring(start)), posn); 487 } 488 } 489 490 // Copy to real reference 491 for (int i = 0; i < refAddrList.size(); i++) { 492 ref.add(refAddrList.elementAt(i)); 493 } 494 } 495 496 return (ref); 497 } 498 499 /* 500 * Serialize an object into a byte array 501 */ 502 private static byte[] serializeObject(Object obj) throws NamingException { 503 504 try { 505 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 506 try (ObjectOutputStream serial = new ObjectOutputStream(bytes)) { 507 serial.writeObject(obj); 508 } 509 510 return (bytes.toByteArray()); 511 512 } catch (IOException e) { 513 NamingException ne = new NamingException(); 514 ne.setRootCause(e); 515 throw ne; 516 } 517 } 518 519 /* 520 * Deserializes a byte array into an object. 521 */ 522 private static Object deserializeObject(byte[] obj, ClassLoader cl) 523 throws NamingException { 524 525 try { 526 // Create ObjectInputStream for deserialization 527 ByteArrayInputStream bytes = new ByteArrayInputStream(obj); 528 try (ObjectInputStream deserial = cl == null ? 529 new ObjectInputStream(bytes) : 530 new LoaderInputStream(bytes, cl)) { 531 return deserial.readObject(); 532 } catch (ClassNotFoundException e) { 533 NamingException ne = new NamingException(); 534 ne.setRootCause(e); 535 throw ne; 536 } 537 } catch (IOException e) { 538 NamingException ne = new NamingException(); 539 ne.setRootCause(e); 540 throw ne; 541 } 542 } 543 544 /** 545 * Returns the attributes to bind given an object and its attributes. 546 */ 547 static Attributes determineBindAttrs( 548 char separator, Object obj, Attributes attrs, boolean cloned, 549 Name name, Context ctx, Hashtable<?,?> env) 550 throws NamingException { 551 552 // Call state factories to convert object and attrs 553 DirStateFactory.Result res = 554 DirectoryManager.getStateToBind(obj, name, ctx, env, attrs); 555 obj = res.getObject(); 556 attrs = res.getAttributes(); 557 558 // We're only storing attributes; no further processing required 559 if (obj == null) { 560 return attrs; 561 } 562 563 //if object to be bound is a DirContext extract its attributes 564 if ((attrs == null) && (obj instanceof DirContext)) { 565 cloned = true; 566 attrs = ((DirContext)obj).getAttributes(""); 567 } 568 569 boolean ocNeedsCloning = false; 570 571 // Create "objectClass" attribute 572 Attribute objectClass; 573 if (attrs == null || attrs.size() == 0) { 574 attrs = new BasicAttributes(LdapClient.caseIgnore); 575 cloned = true; 576 577 // No objectclasses supplied, use "top" to start 578 objectClass = new BasicAttribute("objectClass", "top"); 579 580 } else { 581 // Get existing objectclass attribute 582 objectClass = attrs.get("objectClass"); 583 if (objectClass == null && !attrs.isCaseIgnored()) { 584 // %%% workaround 585 objectClass = attrs.get("objectclass"); 586 } 587 588 // No objectclasses supplied, use "top" to start 589 if (objectClass == null) { 590 objectClass = new BasicAttribute("objectClass", "top"); 591 } else if (ocNeedsCloning || !cloned) { 592 objectClass = (Attribute)objectClass.clone(); 593 } 594 } 595 596 // convert the supplied object into LDAP attributes 597 attrs = encodeObject(separator, obj, attrs, objectClass, cloned); 598 599 // System.err.println("Determined: " + attrs); 600 return attrs; 601 } 602 603 /** 604 * An ObjectInputStream that uses a class loader to find classes. 605 */ 606 private static final class LoaderInputStream extends ObjectInputStream { 607 private ClassLoader classLoader; 608 609 LoaderInputStream(InputStream in, ClassLoader cl) throws IOException { 610 super(in); 611 classLoader = cl; 612 } 613 614 protected Class<?> resolveClass(ObjectStreamClass desc) throws 615 IOException, ClassNotFoundException { 616 try { 617 // %%% Should use Class.forName(desc.getName(), false, classLoader); 618 // except we can't because that is only available on JDK1.2 619 return classLoader.loadClass(desc.getName()); 620 } catch (ClassNotFoundException e) { 621 return super.resolveClass(desc); 622 } 623 } 624 625 protected Class<?> resolveProxyClass(String[] interfaces) throws 626 IOException, ClassNotFoundException { 627 ClassLoader nonPublicLoader = null; 628 boolean hasNonPublicInterface = false; 629 630 // define proxy in class loader of non-public interface(s), if any 631 Class<?>[] classObjs = new Class<?>[interfaces.length]; 632 for (int i = 0; i < interfaces.length; i++) { 633 Class<?> cl = Class.forName(interfaces[i], false, classLoader); 634 if ((cl.getModifiers() & Modifier.PUBLIC) == 0) { 635 if (hasNonPublicInterface) { 636 if (nonPublicLoader != cl.getClassLoader()) { 637 throw new IllegalAccessError( 638 "conflicting non-public interface class loaders"); 639 } 640 } else { 641 nonPublicLoader = cl.getClassLoader(); 642 hasNonPublicInterface = true; 643 } 644 } 645 classObjs[i] = cl; 646 } 647 try { 648 return Proxy.getProxyClass(hasNonPublicInterface ? 649 nonPublicLoader : classLoader, classObjs); 650 } catch (IllegalArgumentException e) { 651 throw new ClassNotFoundException(null, e); 652 } 653 } 654 655 } 656 }