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