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