1 /*
2 * Copyright 1999-2005 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package com.sun.jndi.ldap;
27
28 import javax.naming;
29 import javax.naming.directory;
30 import javax.naming.spi;
31 import javax.naming.event;
32 import javax.naming.ldap;
33 import javax.naming.ldap.LdapName;
34 import javax.naming.ldap.Rdn;
35
36 import java.util.Vector;
37 import java.util.Hashtable;
38 import java.util.List;
39 import java.util.StringTokenizer;
40 import java.util.Enumeration;
41
42 import java.io.IOException;
43 import java.io.OutputStream;
44
45 import com.sun.jndi.toolkit.ctx;
46 import com.sun.jndi.toolkit.dir.HierMemDirCtx;
47 import com.sun.jndi.toolkit.dir.SearchFilter;
48 import com.sun.jndi.ldap.ext.StartTlsResponseImpl;
49
50 /**
51 * The LDAP context implementation.
52 *
53 * Implementation is not thread-safe. Caller must sync as per JNDI spec.
54 * Members that are used directly or indirectly by internal worker threads
55 * (Connection, EventQueue, NamingEventNotifier) must be thread-safe.
56 * Connection - calls LdapClient.processUnsolicited(), which in turn calls
57 * LdapCtx.convertControls() and LdapCtx.fireUnsolicited().
58 * convertControls() - no sync; reads envprops and 'this'
59 * fireUnsolicited() - sync on eventSupport for all references to 'unsolicited'
60 * (even those in other methods); don't sync on LdapCtx in case caller
61 * is already sync'ing on it - this would prevent Unsol events from firing
62 * and the Connection thread to block (thus preventing any other data
63 * from being read from the connection)
64 * References to 'eventSupport' need not be sync'ed because these
65 * methods can only be called after eventSupport has been set first
66 * (via addNamingListener()).
67 * EventQueue - no direct or indirect calls to LdapCtx
68 * NamingEventNotifier - calls newInstance() to get instance for run() to use;
69 * no sync needed for methods invoked on new instance;
70 *
71 * LdapAttribute links to LdapCtx in order to process getAttributeDefinition()
72 * and getAttributeSyntaxDefinition() calls. It invokes LdapCtx.getSchema(),
73 * which uses schemaTrees (a Hashtable - already sync). Potential conflict
74 * of duplicating construction of tree for same subschemasubentry
75 * but no inconsistency problems.
76 *
77 * NamingEnumerations link to LdapCtx for the following:
78 * 1. increment/decrement enum count so that ctx doesn't close the
79 * underlying connection
80 * 2. LdapClient handle to get next batch of results
81 * 3. Sets LdapCtx's response controls
82 * 4. Process return code
83 * 5. For narrowing response controls (using ctx's factories)
84 * Since processing of NamingEnumeration by client is treated the same as method
85 * invocation on LdapCtx, caller is responsible for locking.
86 *
87 * @author Vincent Ryan
88 * @author Rosanna Lee
89 */
90
91 final public class LdapCtx extends ComponentDirContext
92 implements EventDirContext, LdapContext {
93
94 /*
95 * Used to store arguments to the search method.
96 */
97 final static class SearchArgs {
98 Name name;
99 String filter;
100 SearchControls cons;
101 String[] reqAttrs; // those attributes originally requested
102
103 SearchArgs(Name name, String filter, SearchControls cons, String[] ra) {
104 this.name = name;
105 this.filter = filter;
106 this.cons = cons;
107 this.reqAttrs = ra;
108 }
109 }
110
111 private static final boolean debug = false;
112
113 private static final boolean HARD_CLOSE = true;
114 private static final boolean SOFT_CLOSE = false;
115
116 // ----------------- Constants -----------------
117
118 public static final int DEFAULT_PORT = 389;
119 public static final int DEFAULT_SSL_PORT = 636;
120 public static final String DEFAULT_HOST = "localhost";
121
122 private static final boolean DEFAULT_DELETE_RDN = true;
123 private static final boolean DEFAULT_TYPES_ONLY = false;
124 private static final int DEFAULT_DEREF_ALIASES = 3; // always deref
125 private static final int DEFAULT_LDAP_VERSION = LdapClient.LDAP_VERSION3_VERSION2;
126 private static final int DEFAULT_BATCH_SIZE = 1;
127 private static final int DEFAULT_REFERRAL_MODE = LdapClient.LDAP_REF_IGNORE;
128 private static final char DEFAULT_REF_SEPARATOR = '#';
129
130 // Used by LdapPoolManager
131 static final String DEFAULT_SSL_FACTORY =
132 "javax.net.ssl.SSLSocketFactory"; // use Sun's SSL
133 private static final int DEFAULT_REFERRAL_LIMIT = 10;
134 private static final String STARTTLS_REQ_OID = "1.3.6.1.4.1.1466.20037";
135
136 // schema operational and user attributes
137 private static final String[] SCHEMA_ATTRIBUTES =
138 { "objectClasses", "attributeTypes", "matchingRules", "ldapSyntaxes" };
139
140 // --------------- Environment property names ----------
141
142 // LDAP protocol version: "2", "3"
143 private static final String VERSION = "java.naming.ldap.version";
144
145 // Binary-valued attributes. Space separated string of attribute names.
146 private static final String BINARY_ATTRIBUTES =
147 "java.naming.ldap.attributes.binary";
148
149 // Delete old RDN during modifyDN: "true", "false"
150 private static final String DELETE_RDN = "java.naming.ldap.deleteRDN";
151
152 // De-reference aliases: "never", "searching", "finding", "always"
153 private static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";
154
155 // Return only attribute types (no values)
156 private static final String TYPES_ONLY = "java.naming.ldap.typesOnly";
157
158 // Separator character for encoding Reference's RefAddrs; default is '#'
159 private static final String REF_SEPARATOR = "java.naming.ldap.ref.separator";
160
161 // Socket factory
162 private static final String SOCKET_FACTORY = "java.naming.ldap.factory.socket";
163
164 // Bind Controls (used by LdapReferralException)
165 static final String BIND_CONTROLS = "java.naming.ldap.control.connect";
166
167 private static final String REFERRAL_LIMIT =
168 "java.naming.ldap.referral.limit";
169
170 // trace BER (java.io.OutputStream)
171 private static final String TRACE_BER = "com.sun.jndi.ldap.trace.ber";
172
173 // Get around Netscape Schema Bugs
174 private static final String NETSCAPE_SCHEMA_BUG =
175 "com.sun.jndi.ldap.netscape.schemaBugs";
176 // deprecated
177 private static final String OLD_NETSCAPE_SCHEMA_BUG =
178 "com.sun.naming.netscape.schemaBugs"; // for backward compatability
179
180 // Timeout for socket connect
181 private static final String CONNECT_TIMEOUT =
182 "com.sun.jndi.ldap.connect.timeout";
183
184 // Timeout for reading responses
185 private static final String READ_TIMEOUT =
186 "com.sun.jndi.ldap.read.timeout";
187
188 // Environment property for connection pooling
189 private static final String ENABLE_POOL = "com.sun.jndi.ldap.connect.pool";
190
191 // Environment property for the domain name (derived from this context's DN)
192 private static final String DOMAIN_NAME = "com.sun.jndi.ldap.domainname";
193
194 // ----------------- Fields that don't change -----------------------
195 private static final NameParser parser = new LdapNameParser();
196
197 // controls that Provider needs
198 private static final ControlFactory myResponseControlFactory =
199 new DefaultResponseControlFactory();
200 private static final Control manageReferralControl =
201 new ManageReferralControl(false);
202
203 private static final HierMemDirCtx EMPTY_SCHEMA = new HierMemDirCtx();
204 static {
205 EMPTY_SCHEMA.setReadOnly(
206 new SchemaViolationException("Cannot update schema object"));
207 }
208
209 // ------------ Package private instance variables ----------------
210 // Cannot be private; used by enums
211
212 // ------- Inherited by derived context instances
213
214 int port_number; // port number of server
215 String hostname = null; // host name of server (no brackets
216 // for IPv6 literals)
217 LdapClient clnt = null; // connection handle
218 Hashtable envprops = null; // environment properties of context
219 int handleReferrals = DEFAULT_REFERRAL_MODE; // how referral is handled
220 boolean hasLdapsScheme = false; // true if the context was created
221 // using an LDAPS URL.
222
223 // ------- Not inherited by derived context instances
224
225 String currentDN; // DN of this context
226 Name currentParsedDN; // DN of this context
227 Vector respCtls = null; // Response controls read
228 Control[] reqCtls = null; // Controls to be sent with each request
229
230
231 // ------------- Private instance variables ------------------------
232
233 // ------- Inherited by derived context instances
234
235 private OutputStream trace = null; // output stream for BER debug output
236 private boolean netscapeSchemaBug = false; // workaround
237 private Control[] bindCtls = null; // Controls to be sent with LDAP "bind"
238 private int referralHopLimit = DEFAULT_REFERRAL_LIMIT; // max referral
239 private Hashtable schemaTrees = null; // schema root of this context
240 private int batchSize = DEFAULT_BATCH_SIZE; // batch size for search results
241 private boolean deleteRDN = DEFAULT_DELETE_RDN; // delete the old RDN when modifying DN
242 private boolean typesOnly = DEFAULT_TYPES_ONLY; // return attribute types (no values)
243 private int derefAliases = DEFAULT_DEREF_ALIASES;// de-reference alias entries during searching
244 private char addrEncodingSeparator = DEFAULT_REF_SEPARATOR; // encoding RefAddr
245
246 private Hashtable binaryAttrs = null; // attr values returned as byte[]
247 private int connectTimeout = -1; // no timeout value
248 private int readTimeout = -1; // no timeout value
249 private boolean useSsl = false; // true if SSL protocol is active
250 private boolean useDefaultPortNumber = false; // no port number was supplied
251
252 // ------- Not inherited by derived context instances
253
254 // True if this context was created by another LdapCtx.
255 private boolean parentIsLdapCtx = false; // see composeName()
256
257 private int hopCount = 1; // current referral hop count
258 private String url = null; // URL of context; see getURL()
259 private EventSupport eventSupport; // Event support helper for this ctx
260 private boolean unsolicited = false; // if there unsolicited listeners
261 private boolean sharable = true; // can share connection with other ctx
262
263 // -------------- Constructors -----------------------------------
264
265 public LdapCtx(String dn, String host, int port_number, Hashtable props,
266 boolean useSsl) throws NamingException {
267
268 this.useSsl = this.hasLdapsScheme = useSsl;
269
270 if (props != null) {
271 envprops = (Hashtable) props.clone();
272
273 // SSL env prop overrides the useSsl argument
274 if ("ssl".equals(envprops.get(Context.SECURITY_PROTOCOL))) {
275 this.useSsl = true;
276 }
277
278 // %%% These are only examined when the context is created
279 // %%% because they are only for debugging or workaround purposes.
280 trace = (OutputStream)envprops.get(TRACE_BER);
281
282 if (props.get(NETSCAPE_SCHEMA_BUG) != null ||
283 props.get(OLD_NETSCAPE_SCHEMA_BUG) != null) {
284 netscapeSchemaBug = true;
285 }
286 }
287
288 currentDN = (dn != null) ? dn : "";
289 currentParsedDN = parser.parse(currentDN);
290
291 hostname = (host != null && host.length() > 0) ? host : DEFAULT_HOST;
292 if (hostname.charAt(0) == '[') {
293 hostname = hostname.substring(1, hostname.length() - 1);
294 }
295
296 if (port_number > 0) {
297 this.port_number = port_number;
298 } else {
299 this.port_number = this.useSsl ? DEFAULT_SSL_PORT : DEFAULT_PORT;
300 this.useDefaultPortNumber = true;
301 }
302
303 schemaTrees = new Hashtable(11, 0.75f);
304 initEnv();
305 connect(false);
306 }
307
308 LdapCtx(LdapCtx existing, String newDN) throws NamingException {
309 useSsl = existing.useSsl;
310 hasLdapsScheme = existing.hasLdapsScheme;
311 useDefaultPortNumber = existing.useDefaultPortNumber;
312
313 hostname = existing.hostname;
314 port_number = existing.port_number;
315 currentDN = newDN;
316 if (existing.currentDN == currentDN) {
317 currentParsedDN = existing.currentParsedDN;
318 } else {
319 currentParsedDN = parser.parse(currentDN);
320 }
321
322 envprops = existing.envprops;
323 schemaTrees = existing.schemaTrees;
324
325 clnt = existing.clnt;
326 clnt.incRefCount();
327
328 parentIsLdapCtx = ((newDN == null || newDN.equals(existing.currentDN))
329 ? existing.parentIsLdapCtx
330 : true);
331
332 // inherit these debugging/workaround flags
333 trace = existing.trace;
334 netscapeSchemaBug = existing.netscapeSchemaBug;
335
336 initEnv();
337 }
338
339 public LdapContext newInstance(Control[] reqCtls) throws NamingException {
340
341 LdapContext clone = new LdapCtx(this, currentDN);
342
343 // Connection controls are inherited from environment
344
345 // Set clone's request controls
346 // setRequestControls() will clone reqCtls
347 clone.setRequestControls(reqCtls);
348 return clone;
349 }
350
351 // --------------- Namespace Updates ---------------------
352 // -- bind/rebind/unbind
353 // -- rename
354 // -- createSubcontext/destroySubcontext
355
356 protected void c_bind(Name name, Object obj, Continuation cont)
357 throws NamingException {
358 c_bind(name, obj, null, cont);
359 }
360
361 /*
362 * attrs == null
363 * if obj is DirContext, attrs = obj.getAttributes()
364 * if attrs == null && obj == null
365 * disallow (cannot determine objectclass to use)
366 * if obj == null
367 * just create entry using attrs
368 * else
369 * objAttrs = create attributes for representing obj
370 * attrs += objAttrs
371 * create entry using attrs
372 */
373 protected void c_bind(Name name, Object obj, Attributes attrs,
374 Continuation cont)
375 throws NamingException {
376
377 cont.setError(this, name);
378
379 Attributes inputAttrs = attrs; // Attributes supplied by caller
380 try {
381 ensureOpen();
382
383 if (obj == null) {
384 if (attrs == null) {
385 throw new IllegalArgumentException(
386 "cannot bind null object with no attributes");
387 }
388 } else {
389 attrs = Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs,
390 false, name, this, envprops); // not cloned
391 }
392
393 String newDN = fullyQualifiedName(name);
394 attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);
395 LdapEntry entry = new LdapEntry(newDN, attrs);
396
397 LdapResult answer = clnt.add(entry, reqCtls);
398 respCtls = answer.resControls; // retrieve response controls
399
400 if (answer.status != LdapClient.LDAP_SUCCESS) {
401 processReturnCode(answer, name);
402 }
403
404 } catch (LdapReferralException e) {
405 if (handleReferrals == LdapClient.LDAP_REF_THROW)
406 throw cont.fillInException(e);
407
408 // process the referrals sequentially
409 while (true) {
410
411 LdapReferralContext refCtx =
412 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
413
414 // repeat the original operation at the new context
415 try {
416
417 refCtx.bind(name, obj, inputAttrs);
418 return;
419
420 } catch (LdapReferralException re) {
421 e = re;
422 continue;
423
424 } finally {
425 // Make sure we close referral context
426 refCtx.close();
427 }
428 }
429
430 } catch (IOException e) {
431 NamingException e2 = new CommunicationException(e.getMessage());
432 e2.setRootCause(e);
433 throw cont.fillInException(e2);
434
435 } catch (NamingException e) {
436 throw cont.fillInException(e);
437 }
438 }
439
440 protected void c_rebind(Name name, Object obj, Continuation cont)
441 throws NamingException {
442 c_rebind(name, obj, null, cont);
443 }
444
445
446 /*
447 * attrs == null
448 * if obj is DirContext, attrs = obj.getAttributes().
449 * if attrs == null
450 * leave any existing attributes alone
451 * (set attrs = {objectclass=top} if object doesn't exist)
452 * else
453 * replace all existing attributes with attrs
454 * if obj == null
455 * just create entry using attrs
456 * else
457 * objAttrs = create attributes for representing obj
458 * attrs += objAttrs
459 * create entry using attrs
460 */
461 protected void c_rebind(Name name, Object obj, Attributes attrs,
462 Continuation cont) throws NamingException {
463
464 cont.setError(this, name);
465
466 Attributes inputAttrs = attrs;
467
468 try {
469 Attributes origAttrs = null;
470
471 // Check if name is bound
472 try {
473 origAttrs = c_getAttributes(name, null, cont);
474 } catch (NameNotFoundException e) {}
475
476 // Name not bound, just add it
477 if (origAttrs == null) {
478 c_bind(name, obj, attrs, cont);
479 return;
480 }
481
482 // there's an object there already, need to figure out
483 // what to do about its attributes
484
485 if (attrs == null && obj instanceof DirContext) {
486 attrs = ((DirContext)obj).getAttributes("");
487 }
488 Attributes keepAttrs = (Attributes)origAttrs.clone();
489
490 if (attrs == null) {
491 // we're not changing any attrs, leave old attributes alone
492
493 // Remove Java-related object classes from objectclass attribute
494 Attribute origObjectClass =
495 origAttrs.get(Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS]);
496
497 if (origObjectClass != null) {
498 // clone so that keepAttrs is not affected
499 origObjectClass = (Attribute)origObjectClass.clone();
500 for (int i = 0; i < Obj.JAVA_OBJECT_CLASSES.length; i++) {
501 origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES_LOWER[i]);
502 origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES[i]);
503 }
504 // update;
505 origAttrs.put(origObjectClass);
506 }
507
508 // remove all Java-related attributes except objectclass
509 for (int i = 1; i < Obj.JAVA_ATTRIBUTES.length; i++) {
510 origAttrs.remove(Obj.JAVA_ATTRIBUTES[i]);
511 }
512
513 attrs = origAttrs;
514 }
515 if (obj != null) {
516 attrs =
517 Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs,
518 inputAttrs != attrs, name, this, envprops);
519 }
520
521 String newDN = fullyQualifiedName(name);
522 // remove entry
523 LdapResult answer = clnt.delete(newDN, reqCtls);
524 respCtls = answer.resControls; // retrieve response controls
525
526 if (answer.status != LdapClient.LDAP_SUCCESS) {
527 processReturnCode(answer, name);
528 return;
529 }
530
531 Exception addEx = null;
532 try {
533 attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);
534
535 // add it back using updated attrs
536 LdapEntry entry = new LdapEntry(newDN, attrs);
537 answer = clnt.add(entry, reqCtls);
538 if (answer.resControls != null) {
539 respCtls = appendVector(respCtls, answer.resControls);
540 }
541 } catch (NamingException ae) {
542 addEx = ae;
543 } catch (IOException ae) {
544 addEx = ae;
545 }
546
547 if ((addEx != null && !(addEx instanceof LdapReferralException)) ||
548 answer.status != LdapClient.LDAP_SUCCESS) {
549 // Attempt to restore old entry
550 LdapResult answer2 =
551 clnt.add(new LdapEntry(newDN, keepAttrs), reqCtls);
552 if (answer2.resControls != null) {
553 respCtls = appendVector(respCtls, answer2.resControls);
554 }
555
556 if (addEx == null) {
557 processReturnCode(answer, name);
558 }
559 }
560
561 // Rethrow exception
562 if (addEx instanceof NamingException) {
563 throw (NamingException)addEx;
564 } else if (addEx instanceof IOException) {
565 throw (IOException)addEx;
566 }
567
568 } catch (LdapReferralException e) {
569 if (handleReferrals == LdapClient.LDAP_REF_THROW)
570 throw cont.fillInException(e);
571
572 // process the referrals sequentially
573 while (true) {
574
575 LdapReferralContext refCtx =
576 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
577
578 // repeat the original operation at the new context
579 try {
580
581 refCtx.rebind(name, obj, inputAttrs);
582 return;
583
584 } catch (LdapReferralException re) {
585 e = re;
586 continue;
587
588 } finally {
589 // Make sure we close referral context
590 refCtx.close();
591 }
592 }
593
594 } catch (IOException e) {
595 NamingException e2 = new CommunicationException(e.getMessage());
596 e2.setRootCause(e);
597 throw cont.fillInException(e2);
598
599 } catch (NamingException e) {
600 throw cont.fillInException(e);
601 }
602 }
603
604 protected void c_unbind(Name name, Continuation cont)
605 throws NamingException {
606 cont.setError(this, name);
607
608 try {
609 ensureOpen();
610
611 String fname = fullyQualifiedName(name);
612 LdapResult answer = clnt.delete(fname, reqCtls);
613 respCtls = answer.resControls; // retrieve response controls
614
615 adjustDeleteStatus(fname, answer);
616
617 if (answer.status != LdapClient.LDAP_SUCCESS) {
618 processReturnCode(answer, name);
619 }
620
621 } catch (LdapReferralException e) {
622 if (handleReferrals == LdapClient.LDAP_REF_THROW)
623 throw cont.fillInException(e);
624
625 // process the referrals sequentially
626 while (true) {
627
628 LdapReferralContext refCtx =
629 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
630
631 // repeat the original operation at the new context
632 try {
633
634 refCtx.unbind(name);
635 return;
636
637 } catch (LdapReferralException re) {
638 e = re;
639 continue;
640
641 } finally {
642 // Make sure we close referral context
643 refCtx.close();
644 }
645 }
646
647 } catch (IOException e) {
648 NamingException e2 = new CommunicationException(e.getMessage());
649 e2.setRootCause(e);
650 throw cont.fillInException(e2);
651
652 } catch (NamingException e) {
653 throw cont.fillInException(e);
654 }
655 }
656
657 protected void c_rename(Name oldName, Name newName, Continuation cont)
658 throws NamingException
659 {
660 Name oldParsed, newParsed;
661 Name oldParent, newParent;
662 String newRDN = null;
663 String newSuperior = null;
664
665 // assert (oldName instanceOf CompositeName);
666
667 cont.setError(this, oldName);
668
669 try {
670 ensureOpen();
671
672 // permit oldName to be empty (for processing referral contexts)
673 if (oldName.isEmpty()) {
674 oldParent = parser.parse("");
675 } else {
676 oldParsed = parser.parse(oldName.get(0)); // extract DN & parse
677 oldParent = oldParsed.getPrefix(oldParsed.size() - 1);
678 }
679
680 if (newName instanceof CompositeName) {
681 newParsed = parser.parse(newName.get(0)); // extract DN & parse
682 } else {
683 newParsed = newName; // CompoundName/LdapName is already parsed
684 }
685 newParent = newParsed.getPrefix(newParsed.size() - 1);
686
687 if(!oldParent.equals(newParent)) {
688 if (!clnt.isLdapv3) {
689 throw new InvalidNameException(
690 "LDAPv2 doesn't support changing " +
691 "the parent as a result of a rename");
692 } else {
693 newSuperior = fullyQualifiedName(newParent.toString());
694 }
695 }
696
697 newRDN = newParsed.get(newParsed.size() - 1);
698
699 LdapResult answer = clnt.moddn(fullyQualifiedName(oldName),
700 newRDN,
701 deleteRDN,
702 newSuperior,
703 reqCtls);
704 respCtls = answer.resControls; // retrieve response controls
705
706 if (answer.status != LdapClient.LDAP_SUCCESS) {
707 processReturnCode(answer, oldName);
708 }
709
710 } catch (LdapReferralException e) {
711
712 // Record the new RDN (for use after the referral is followed).
713 e.setNewRdn(newRDN);
714
715 // Cannot continue when a referral has been received and a
716 // newSuperior name was supplied (because the newSuperior is
717 // relative to a naming context BEFORE the referral is followed).
718 if (newSuperior != null) {
719 PartialResultException pre = new PartialResultException(
720 "Cannot continue referral processing when newSuperior is " +
721 "nonempty: " + newSuperior);
722 pre.setRootCause(cont.fillInException(e));
723 throw cont.fillInException(pre);
724 }
725
726 if (handleReferrals == LdapClient.LDAP_REF_THROW)
727 throw cont.fillInException(e);
728
729 // process the referrals sequentially
730 while (true) {
731
732 LdapReferralContext refCtx =
733 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
734
735 // repeat the original operation at the new context
736 try {
737
738 refCtx.rename(oldName, newName);
739 return;
740
741 } catch (LdapReferralException re) {
742 e = re;
743 continue;
744
745 } finally {
746 // Make sure we close referral context
747 refCtx.close();
748 }
749 }
750
751 } catch (IOException e) {
752 NamingException e2 = new CommunicationException(e.getMessage());
753 e2.setRootCause(e);
754 throw cont.fillInException(e2);
755
756 } catch (NamingException e) {
757 throw cont.fillInException(e);
758 }
759 }
760
761 protected Context c_createSubcontext(Name name, Continuation cont)
762 throws NamingException {
763 return c_createSubcontext(name, null, cont);
764 }
765
766 protected DirContext c_createSubcontext(Name name, Attributes attrs,
767 Continuation cont)
768 throws NamingException {
769 cont.setError(this, name);
770
771 Attributes inputAttrs = attrs;
772 try {
773 ensureOpen();
774 if (attrs == null) {
775 // add structural objectclass; name needs to have "cn"
776 Attribute oc = new BasicAttribute(
777 Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS],
778 Obj.JAVA_OBJECT_CLASSES[Obj.STRUCTURAL]);
779 oc.add("top");
780 attrs = new BasicAttributes(true); // case ignore
781 attrs.put(oc);
782 }
783 String newDN = fullyQualifiedName(name);
784 attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);
785
786 LdapEntry entry = new LdapEntry(newDN, attrs);
787
788 LdapResult answer = clnt.add(entry, reqCtls);
789 respCtls = answer.resControls; // retrieve response controls
790
791 if (answer.status != LdapClient.LDAP_SUCCESS) {
792 processReturnCode(answer, name);
793 return null;
794 }
795
796 // creation successful, get back live object
797 return new LdapCtx(this, newDN);
798
799 } catch (LdapReferralException e) {
800 if (handleReferrals == LdapClient.LDAP_REF_THROW)
801 throw cont.fillInException(e);
802
803 // process the referrals sequentially
804 while (true) {
805
806 LdapReferralContext refCtx =
807 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
808
809 // repeat the original operation at the new context
810 try {
811
812 return refCtx.createSubcontext(name, inputAttrs);
813
814 } catch (LdapReferralException re) {
815 e = re;
816 continue;
817
818 } finally {
819 // Make sure we close referral context
820 refCtx.close();
821 }
822 }
823
824 } catch (IOException e) {
825 NamingException e2 = new CommunicationException(e.getMessage());
826 e2.setRootCause(e);
827 throw cont.fillInException(e2);
828
829 } catch (NamingException e) {
830 throw cont.fillInException(e);
831 }
832 }
833
834 protected void c_destroySubcontext(Name name, Continuation cont)
835 throws NamingException {
836 cont.setError(this, name);
837
838 try {
839 ensureOpen();
840
841 String fname = fullyQualifiedName(name);
842 LdapResult answer = clnt.delete(fname, reqCtls);
843 respCtls = answer.resControls; // retrieve response controls
844
845 adjustDeleteStatus(fname, answer);
846
847 if (answer.status != LdapClient.LDAP_SUCCESS) {
848 processReturnCode(answer, name);
849 }
850
851 } catch (LdapReferralException e) {
852 if (handleReferrals == LdapClient.LDAP_REF_THROW)
853 throw cont.fillInException(e);
854
855 // process the referrals sequentially
856 while (true) {
857
858 LdapReferralContext refCtx =
859 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
860
861 // repeat the original operation at the new context
862 try {
863
864 refCtx.destroySubcontext(name);
865 return;
866 } catch (LdapReferralException re) {
867 e = re;
868 continue;
869 } finally {
870 // Make sure we close referral context
871 refCtx.close();
872 }
873 }
874 } catch (IOException e) {
875 NamingException e2 = new CommunicationException(e.getMessage());
876 e2.setRootCause(e);
877 throw cont.fillInException(e2);
878 } catch (NamingException e) {
879 throw cont.fillInException(e);
880 }
881 }
882
883 /**
884 * Adds attributes from RDN to attrs if not already present.
885 * Note that if attrs already contains an attribute by the same name,
886 * or if the distinguished name is empty, then leave attrs unchanged.
887 *
888 * @param dn The non-null DN of the entry to add
889 * @param attrs The non-null attributes of entry to add
890 * @param directUpdate Whether attrs can be updated directly
891 * @returns Non-null attributes with attributes from the RDN added
892 */
893 private static Attributes addRdnAttributes(String dn, Attributes attrs,
894 boolean directUpdate) throws NamingException {
895
896 // Handle the empty name
897 if (dn.equals("")) {
898 return attrs;
899 }
900
901 // Parse string name into list of RDNs
902 //List<Rdn> rdnList = (new LdapName(dn)).rdns();
903 List rdnList = (new LdapName(dn)).getRdns();
904
905 // Get leaf RDN
906 //Rdn rdn = rdnList.get(rdnList.size() - 1);
907 Rdn rdn = (Rdn) rdnList.get(rdnList.size() - 1);
908 Attributes nameAttrs = rdn.toAttributes();
909
910 // Add attributes of RDN to attrs if not already there
911 NamingEnumeration enum_ = nameAttrs.getAll();
912 Attribute nameAttr;
913 while (enum_.hasMore()) {
914 nameAttr = (Attribute) enum_.next();
915
916 // If attrs already has the attribute, don't change or add to it
917 if (attrs.get(nameAttr.getID()) == null) {
918
919 /**
920 * When attrs.isCaseIgnored() is false, attrs.get() will
921 * return null when the case mis-matches for otherwise
922 * equal attrIDs.
923 * As the attrIDs' case is irrelevant for LDAP, ignore
924 * the case of attrIDs even when attrs.isCaseIgnored() is
925 * false. This is done by explicitly comparing the elements in
926 * the enumeration of IDs with their case ignored.
927 */
928 if (!attrs.isCaseIgnored() &&
929 containsIgnoreCase(attrs.getIDs(), nameAttr.getID())) {
930 continue;
931 }
932
933 if (!directUpdate) {
934 attrs = (Attributes)attrs.clone();
935 directUpdate = true;
936 }
937 attrs.put(nameAttr);
938 }
939 }
940
941 return attrs;
942 }
943
944
945 private static boolean containsIgnoreCase(NamingEnumeration enumStr,
946 String str) throws NamingException {
947 String strEntry;
948
949 while (enumStr.hasMore()) {
950 strEntry = (String) enumStr.next();
951 if (strEntry.equalsIgnoreCase(str)) {
952 return true;
953 }
954 }
955 return false;
956 }
957
958
959 private void adjustDeleteStatus(String fname, LdapResult answer) {
960 if (answer.status == LdapClient.LDAP_NO_SUCH_OBJECT &&
961 answer.matchedDN != null) {
962 try {
963 // %%% RL: are there any implications for referrals?
964
965 Name orig = parser.parse(fname);
966 Name matched = parser.parse(answer.matchedDN);
967 if ((orig.size() - matched.size()) == 1)
968 answer.status = LdapClient.LDAP_SUCCESS;
969 } catch (NamingException e) {}
970 }
971 }
972
973 /*
974 * Append the the second Vector onto the first Vector
975 * (v2 must be non-null)
976 */
977 private static Vector appendVector(Vector v1, Vector v2) {
978 if (v1 == null) {
979 v1 = v2;
980 } else {
981 for (int i = 0; i < v2.size(); i++) {
982 v1.addElement(v2.elementAt(i));
983 }
984 }
985 return v1;
986 }
987
988 // ------------- Lookups and Browsing -------------------------
989 // lookup/lookupLink
990 // list/listBindings
991
992 protected Object c_lookupLink(Name name, Continuation cont)
993 throws NamingException {
994 return c_lookup(name, cont);
995 }
996
997 protected Object c_lookup(Name name, Continuation cont)
998 throws NamingException {
999 cont.setError(this, name);
1000 Object obj = null;
1001 Attributes attrs;
1002
1003 try {
1004 SearchControls cons = new SearchControls();
1005 cons.setSearchScope(SearchControls.OBJECT_SCOPE);
1006 cons.setReturningAttributes(null); // ask for all attributes
1007 cons.setReturningObjFlag(true); // need values to construct obj
1008
1009 LdapResult answer = doSearchOnce(name, "(objectClass=*)", cons, true);
1010 respCtls = answer.resControls; // retrieve response controls
1011
1012 // should get back 1 SearchResponse and 1 SearchResult
1013
1014 if (answer.status != LdapClient.LDAP_SUCCESS) {
1015 processReturnCode(answer, name);
1016 }
1017
1018 if (answer.entries == null || answer.entries.size() != 1) {
1019 // found it but got no attributes
1020 attrs = new BasicAttributes(LdapClient.caseIgnore);
1021 } else {
1022 LdapEntry entry = (LdapEntry)answer.entries.elementAt(0);
1023 attrs = entry.attributes;
1024
1025 Vector entryCtls = entry.respCtls; // retrieve entry controls
1026 if (entryCtls != null) {
1027 appendVector(respCtls, entryCtls); // concatenate controls
1028 }
1029 }
1030
1031 if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) {
1032 // serialized object or object reference
1033 obj = Obj.decodeObject(attrs);
1034 }
1035 if (obj == null) {
1036 obj = new LdapCtx(this, fullyQualifiedName(name));
1037 }
1038 } catch (LdapReferralException e) {
1039 if (handleReferrals == LdapClient.LDAP_REF_THROW)
1040 throw cont.fillInException(e);
1041
1042 // process the referrals sequentially
1043 while (true) {
1044
1045 LdapReferralContext refCtx =
1046 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1047 // repeat the original operation at the new context
1048 try {
1049
1050 return refCtx.lookup(name);
1051
1052 } catch (LdapReferralException re) {
1053 e = re;
1054 continue;
1055
1056 } finally {
1057 // Make sure we close referral context
1058 refCtx.close();
1059 }
1060 }
1061
1062 } catch (NamingException e) {
1063 throw cont.fillInException(e);
1064 }
1065
1066 try {
1067 return DirectoryManager.getObjectInstance(obj, name,
1068 this, envprops, attrs);
1069
1070 } catch (NamingException e) {
1071 throw cont.fillInException(e);
1072
1073 } catch (Exception e) {
1074 NamingException e2 = new NamingException(
1075 "problem generating object using object factory");
1076 e2.setRootCause(e);
1077 throw cont.fillInException(e2);
1078 }
1079 }
1080
1081 protected NamingEnumeration c_list(Name name, Continuation cont)
1082 throws NamingException {
1083 SearchControls cons = new SearchControls();
1084 String[] classAttrs = new String[2];
1085
1086 classAttrs[0] = Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS];
1087 classAttrs[1] = Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME];
1088 cons.setReturningAttributes(classAttrs);
1089
1090 // set this flag to override the typesOnly flag
1091 cons.setReturningObjFlag(true);
1092
1093 cont.setError(this, name);
1094
1095 LdapResult answer = null;
1096
1097 try {
1098 answer = doSearch(name, "(objectClass=*)", cons, true, true);
1099
1100 // list result may contain continuation references
1101 if ((answer.status != LdapClient.LDAP_SUCCESS) ||
1102 (answer.referrals != null)) {
1103 processReturnCode(answer, name);
1104 }
1105
1106 return new LdapNamingEnumeration(this, answer, name, cont);
1107
1108 } catch (LdapReferralException e) {
1109 if (handleReferrals == LdapClient.LDAP_REF_THROW)
1110 throw cont.fillInException(e);
1111
1112 // process the referrals sequentially
1113 while (true) {
1114
1115 LdapReferralContext refCtx =
1116 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1117
1118 // repeat the original operation at the new context
1119 try {
1120
1121 return refCtx.list(name);
1122
1123 } catch (LdapReferralException re) {
1124 e = re;
1125 continue;
1126
1127 } finally {
1128 // Make sure we close referral context
1129 refCtx.close();
1130 }
1131 }
1132
1133 } catch (LimitExceededException e) {
1134 LdapNamingEnumeration res =
1135 new LdapNamingEnumeration(this, answer, name, cont);
1136
1137 res.setNamingException(
1138 (LimitExceededException)cont.fillInException(e));
1139 return res;
1140
1141 } catch (PartialResultException e) {
1142 LdapNamingEnumeration res =
1143 new LdapNamingEnumeration(this, answer, name, cont);
1144
1145 res.setNamingException(
1146 (PartialResultException)cont.fillInException(e));
1147 return res;
1148
1149 } catch (NamingException e) {
1150 throw cont.fillInException(e);
1151 }
1152 }
1153
1154 protected NamingEnumeration c_listBindings(Name name, Continuation cont)
1155 throws NamingException {
1156
1157 SearchControls cons = new SearchControls();
1158 cons.setReturningAttributes(null); // ask for all attributes
1159 cons.setReturningObjFlag(true); // need values to construct obj
1160
1161 cont.setError(this, name);
1162
1163 LdapResult answer = null;
1164
1165 try {
1166 answer = doSearch(name, "(objectClass=*)", cons, true, true);
1167
1168 // listBindings result may contain continuation references
1169 if ((answer.status != LdapClient.LDAP_SUCCESS) ||
1170 (answer.referrals != null)) {
1171 processReturnCode(answer, name);
1172 }
1173
1174 return new LdapBindingEnumeration(this, answer, name, cont);
1175
1176 } catch (LdapReferralException e) {
1177 if (handleReferrals == LdapClient.LDAP_REF_THROW)
1178 throw cont.fillInException(e);
1179
1180 // process the referrals sequentially
1181 while (true) {
1182
1183 LdapReferralContext refCtx =
1184 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1185
1186 // repeat the original operation at the new context
1187 try {
1188
1189 return refCtx.listBindings(name);
1190
1191 } catch (LdapReferralException re) {
1192 e = re;
1193 continue;
1194
1195 } finally {
1196 // Make sure we close referral context
1197 refCtx.close();
1198 }
1199 }
1200 } catch (LimitExceededException e) {
1201 LdapBindingEnumeration res =
1202 new LdapBindingEnumeration(this, answer, name, cont);
1203
1204 res.setNamingException(
1205 (LimitExceededException)cont.fillInException(e));
1206 return res;
1207
1208 } catch (PartialResultException e) {
1209 LdapBindingEnumeration res =
1210 new LdapBindingEnumeration(this, answer, name, cont);
1211
1212 res.setNamingException(
1213 (PartialResultException)cont.fillInException(e));
1214 return res;
1215
1216 } catch (NamingException e) {
1217 throw cont.fillInException(e);
1218 }
1219 }
1220
1221 // --------------- Name-related Methods -----------------------
1222 // -- getNameParser/getNameInNamespace/composeName
1223
1224 protected NameParser c_getNameParser(Name name, Continuation cont)
1225 throws NamingException
1226 {
1227 // ignore name, always return same parser
1228 cont.setSuccess();
1229 return parser;
1230 }
1231
1232 public String getNameInNamespace() {
1233 return currentDN;
1234 }
1235
1236 public Name composeName(Name name, Name prefix)
1237 throws NamingException
1238 {
1239 Name result;
1240
1241 // Handle compound names. A pair of LdapNames is an easy case.
1242 if ((name instanceof LdapName) && (prefix instanceof LdapName)) {
1243 result = (Name)(prefix.clone());
1244 result.addAll(name);
1245 return new CompositeName().add(result.toString());
1246 }
1247 if (!(name instanceof CompositeName)) {
1248 name = new CompositeName().add(name.toString());
1249 }
1250 if (!(prefix instanceof CompositeName)) {
1251 prefix = new CompositeName().add(prefix.toString());
1252 }
1253
1254 int prefixLast = prefix.size() - 1;
1255
1256 if (name.isEmpty() || prefix.isEmpty() ||
1257 name.get(0).equals("") || prefix.get(prefixLast).equals("")) {
1258 return super.composeName(name, prefix);
1259 }
1260
1261 result = (Name)(prefix.clone());
1262 result.addAll(name);
1263
1264 if (parentIsLdapCtx) {
1265 String ldapComp = concatNames(result.get(prefixLast + 1),
1266 result.get(prefixLast));
1267 result.remove(prefixLast + 1);
1268 result.remove(prefixLast);
1269 result.add(prefixLast, ldapComp);
1270 }
1271 return result;
1272 }
1273
1274 private String fullyQualifiedName(Name rel) {
1275 return rel.isEmpty()
1276 ? currentDN
1277 : fullyQualifiedName(rel.get(0));
1278 }
1279
1280 private String fullyQualifiedName(String rel) {
1281 return (concatNames(rel, currentDN));
1282 }
1283
1284 // used by LdapSearchEnumeration
1285 private static String concatNames(String lesser, String greater) {
1286 if (lesser == null || lesser.equals("")) {
1287 return greater;
1288 } else if (greater == null || greater.equals("")) {
1289 return lesser;
1290 } else {
1291 return (lesser + "," + greater);
1292 }
1293 }
1294
1295 // --------------- Reading and Updating Attributes
1296 // getAttributes/modifyAttributes
1297
1298 protected Attributes c_getAttributes(Name name, String[] attrIds,
1299 Continuation cont)
1300 throws NamingException {
1301 cont.setError(this, name);
1302
1303 SearchControls cons = new SearchControls();
1304 cons.setSearchScope(SearchControls.OBJECT_SCOPE);
1305 cons.setReturningAttributes(attrIds);
1306
1307 try {
1308 LdapResult answer =
1309 doSearchOnce(name, "(objectClass=*)", cons, true);
1310 respCtls = answer.resControls; // retrieve response controls
1311
1312 if (answer.status != LdapClient.LDAP_SUCCESS) {
1313 processReturnCode(answer, name);
1314 }
1315
1316 if (answer.entries == null || answer.entries.size() != 1) {
1317 return new BasicAttributes(LdapClient.caseIgnore);
1318 }
1319
1320 // get attributes from result
1321 LdapEntry entry = (LdapEntry) answer.entries.elementAt(0);
1322
1323 Vector entryCtls = entry.respCtls; // retrieve entry controls
1324 if (entryCtls != null) {
1325 appendVector(respCtls, entryCtls); // concatenate controls
1326 }
1327
1328 // do this so attributes can find their schema
1329 setParents(entry.attributes, (Name) name.clone());
1330
1331 return (entry.attributes);
1332
1333 } catch (LdapReferralException e) {
1334 if (handleReferrals == LdapClient.LDAP_REF_THROW)
1335 throw cont.fillInException(e);
1336
1337 // process the referrals sequentially
1338 while (true) {
1339
1340 LdapReferralContext refCtx =
1341 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1342
1343 // repeat the original operation at the new context
1344 try {
1345
1346 return refCtx.getAttributes(name, attrIds);
1347
1348 } catch (LdapReferralException re) {
1349 e = re;
1350 continue;
1351
1352 } finally {
1353 // Make sure we close referral context
1354 refCtx.close();
1355 }
1356 }
1357
1358 } catch (NamingException e) {
1359 throw cont.fillInException(e);
1360 }
1361 }
1362
1363 protected void c_modifyAttributes(Name name, int mod_op, Attributes attrs,
1364 Continuation cont)
1365 throws NamingException {
1366
1367 cont.setError(this, name);
1368
1369 try {
1370 ensureOpen();
1371
1372 if (attrs == null || attrs.size() == 0) {
1373 return; // nothing to do
1374 }
1375 String newDN = fullyQualifiedName(name);
1376 int jmod_op = convertToLdapModCode(mod_op);
1377
1378 // construct mod list
1379 int[] jmods = new int[attrs.size()];
1380 Attribute[] jattrs = new Attribute[attrs.size()];
1381
1382 NamingEnumeration ae = attrs.getAll();
1383 for(int i = 0; i < jmods.length && ae.hasMore(); i++) {
1384 jmods[i] = jmod_op;
1385 jattrs[i] = (Attribute)ae.next();
1386 }
1387
1388 LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls);
1389 respCtls = answer.resControls; // retrieve response controls
1390
1391 if (answer.status != LdapClient.LDAP_SUCCESS) {
1392 processReturnCode(answer, name);
1393 return;
1394 }
1395
1396 } catch (LdapReferralException e) {
1397 if (handleReferrals == LdapClient.LDAP_REF_THROW)
1398 throw cont.fillInException(e);
1399
1400 // process the referrals sequentially
1401 while (true) {
1402
1403 LdapReferralContext refCtx =
1404 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1405
1406 // repeat the original operation at the new context
1407 try {
1408
1409 refCtx.modifyAttributes(name, mod_op, attrs);
1410 return;
1411
1412 } catch (LdapReferralException re) {
1413 e = re;
1414 continue;
1415
1416 } finally {
1417 // Make sure we close referral context
1418 refCtx.close();
1419 }
1420 }
1421
1422 } catch (IOException e) {
1423 NamingException e2 = new CommunicationException(e.getMessage());
1424 e2.setRootCause(e);
1425 throw cont.fillInException(e2);
1426
1427 } catch (NamingException e) {
1428 throw cont.fillInException(e);
1429 }
1430 }
1431
1432 protected void c_modifyAttributes(Name name, ModificationItem[] mods,
1433 Continuation cont)
1434 throws NamingException {
1435 cont.setError(this, name);
1436
1437 try {
1438 ensureOpen();
1439
1440 if (mods == null || mods.length == 0) {
1441 return; // nothing to do
1442 }
1443 String newDN = fullyQualifiedName(name);
1444
1445 // construct mod list
1446 int[] jmods = new int[mods.length];
1447 Attribute[] jattrs = new Attribute[mods.length];
1448 ModificationItem mod;
1449 for (int i = 0; i < jmods.length; i++) {
1450 mod = mods[i];
1451 jmods[i] = convertToLdapModCode(mod.getModificationOp());
1452 jattrs[i] = mod.getAttribute();
1453 }
1454
1455 LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls);
1456 respCtls = answer.resControls; // retrieve response controls
1457
1458 if (answer.status != LdapClient.LDAP_SUCCESS) {
1459 processReturnCode(answer, name);
1460 }
1461
1462 } catch (LdapReferralException e) {
1463 if (handleReferrals == LdapClient.LDAP_REF_THROW)
1464 throw cont.fillInException(e);
1465
1466 // process the referrals sequentially
1467 while (true) {
1468
1469 LdapReferralContext refCtx =
1470 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1471
1472 // repeat the original operation at the new context
1473 try {
1474
1475 refCtx.modifyAttributes(name, mods);
1476 return;
1477
1478 } catch (LdapReferralException re) {
1479 e = re;
1480 continue;
1481
1482 } finally {
1483 // Make sure we close referral context
1484 refCtx.close();
1485 }
1486 }
1487
1488 } catch (IOException e) {
1489 NamingException e2 = new CommunicationException(e.getMessage());
1490 e2.setRootCause(e);
1491 throw cont.fillInException(e2);
1492
1493 } catch (NamingException e) {
1494 throw cont.fillInException(e);
1495 }
1496 }
1497
1498 private static int convertToLdapModCode(int mod_op) {
1499 switch (mod_op) {
1500 case DirContext.ADD_ATTRIBUTE:
1501 return(LdapClient.ADD);
1502
1503 case DirContext.REPLACE_ATTRIBUTE:
1504 return (LdapClient.REPLACE);
1505
1506 case DirContext.REMOVE_ATTRIBUTE:
1507 return (LdapClient.DELETE);
1508
1509 default:
1510 throw new IllegalArgumentException("Invalid modification code");
1511 }
1512 }
1513
1514 // ------------------- Schema -----------------------
1515
1516 protected DirContext c_getSchema(Name name, Continuation cont)
1517 throws NamingException {
1518 cont.setError(this, name);
1519 try {
1520 return getSchemaTree(name);
1521
1522 } catch (NamingException e) {
1523 throw cont.fillInException(e);
1524 }
1525 }
1526
1527 protected DirContext c_getSchemaClassDefinition(Name name,
1528 Continuation cont)
1529 throws NamingException {
1530 cont.setError(this, name);
1531
1532 try {
1533 // retrieve the objectClass attribute from LDAP
1534 Attribute objectClassAttr = c_getAttributes(name,
1535 new String[]{"objectclass"}, cont).get("objectclass");
1536 if (objectClassAttr == null || objectClassAttr.size() == 0) {
1537 return EMPTY_SCHEMA;
1538 }
1539
1540 // retrieve the root of the ObjectClass schema tree
1541 Context ocSchema = (Context) c_getSchema(name, cont).lookup(
1542 LdapSchemaParser.OBJECTCLASS_DEFINITION_NAME);
1543
1544 // create a context to hold the schema objects representing the object
1545 // classes
1546 HierMemDirCtx objectClassCtx = new HierMemDirCtx();
1547 DirContext objectClassDef;
1548 String objectClassName;
1549 for (Enumeration objectClasses = objectClassAttr.getAll();
1550 objectClasses.hasMoreElements(); ) {
1551 objectClassName = (String)objectClasses.nextElement();
1552 // %%% Should we fail if not found, or just continue?
1553 objectClassDef = (DirContext)ocSchema.lookup(objectClassName);
1554 objectClassCtx.bind(objectClassName, objectClassDef);
1555 }
1556
1557 // Make context read-only
1558 objectClassCtx.setReadOnly(
1559 new SchemaViolationException("Cannot update schema object"));
1560 return (DirContext)objectClassCtx;
1561
1562 } catch (NamingException e) {
1563 throw cont.fillInException(e);
1564 }
1565 }
1566
1567 /*
1568 * getSchemaTree first looks to see if we have already built a
1569 * schema tree for the given entry. If not, it builds a new one and
1570 * stores it in our private hash table
1571 */
1572 private DirContext getSchemaTree(Name name) throws NamingException {
1573 String subschemasubentry = getSchemaEntry(name, true);
1574
1575 DirContext schemaTree = (DirContext)schemaTrees.get(subschemasubentry);
1576
1577 if(schemaTree==null) {
1578 if(debug){System.err.println("LdapCtx: building new schema tree " + this);}
1579 schemaTree = buildSchemaTree(subschemasubentry);
1580 schemaTrees.put(subschemasubentry, schemaTree);
1581 }
1582
1583 return schemaTree;
1584 }
1585
1586 /*
1587 * buildSchemaTree builds the schema tree corresponding to the
1588 * given subschemasubentree
1589 */
1590 private DirContext buildSchemaTree(String subschemasubentry)
1591 throws NamingException {
1592
1593 // get the schema entry itself
1594 // DO ask for return object here because we need it to
1595 // create context. Since asking for all attrs, we won't
1596 // be transmitting any specific attrIDs (like Java-specific ones).
1597 SearchControls constraints = new
1598 SearchControls(SearchControls.OBJECT_SCOPE,
1599 0, 0, /* count and time limits */
1600 SCHEMA_ATTRIBUTES /* return schema attrs */,
1601 true /* return obj */,
1602 false /*deref link */ );
1603
1604 Name sse = (new CompositeName()).add(subschemasubentry);
1605 NamingEnumeration results =
1606 searchAux(sse, "(objectClass=subschema)", constraints,
1607 false, true, new Continuation());
1608
1609 if(!results.hasMore()) {
1610 throw new OperationNotSupportedException(
1611 "Cannot get read subschemasubentry: " + subschemasubentry);
1612 }
1613 SearchResult result = (SearchResult)results.next();
1614 results.close();
1615
1616 Object obj = result.getObject();
1617 if(!(obj instanceof LdapCtx)) {
1618 throw new NamingException(
1619 "Cannot get schema object as DirContext: " + subschemasubentry);
1620 }
1621
1622 return LdapSchemaCtx.createSchemaTree(envprops, subschemasubentry,
1623 (LdapCtx)obj /* schema entry */,
1624 result.getAttributes() /* schema attributes */,
1625 netscapeSchemaBug);
1626 }
1627
1628 /*
1629 * getSchemaEntree returns the DN of the subschemasubentree for the
1630 * given entree. It first looks to see if the given entry has
1631 * a subschema different from that of the root DIT (by looking for
1632 * a "subschemasubentry" attribute). If it doesn't find one, it returns
1633 * the one for the root of the DIT (by looking for the root's
1634 * "subschemasubentry" attribute).
1635 *
1636 * This function is called regardless of the server's version, since
1637 * an administrator may have setup the server to support client schema
1638 * queries. If this function trys a serarch on a v2 server that
1639 * doesn't support schema, one of these two things will happen:
1640 * 1) It will get an exception when querying the root DSE
1641 * 2) It will not find a subschemasubentry on the root DSE
1642 * If either of these things occur and the server is not v3, we
1643 * throw OperationNotSupported.
1644 *
1645 * the relative flag tells whether the given name is relative to this
1646 * context.
1647 */
1648 private String getSchemaEntry(Name name, boolean relative)
1649 throws NamingException {
1650
1651 // Asks for operational attribute "subschemasubentry"
1652 SearchControls constraints = new SearchControls(SearchControls.OBJECT_SCOPE,
1653 0, 0, /* count and time limits */
1654 new String[]{"subschemasubentry"} /* attr to return */,
1655 false /* returning obj */,
1656 false /* deref link */);
1657
1658 NamingEnumeration results;
1659 try {
1660 results = searchAux(name, "objectclass=*", constraints, relative,
1661 true, new Continuation());
1662
1663 } catch (NamingException ne) {
1664 if (!clnt.isLdapv3 && currentDN.length() == 0 && name.isEmpty()) {
1665 // we got an error looking for a root entry on an ldapv2
1666 // server. The server must not support schema.
1667 throw new OperationNotSupportedException(
1668 "Cannot get schema information from server");
1669 } else {
1670 throw ne;
1671 }
1672 }
1673
1674 if (!results.hasMoreElements()) {
1675 throw new ConfigurationException(
1676 "Requesting schema of nonexistent entry: " + name);
1677 }
1678
1679 SearchResult result = (SearchResult) results.next();
1680 results.close();
1681
1682 Attribute schemaEntryAttr =
1683 result.getAttributes().get("subschemasubentry");
1684 //System.err.println("schema entry attrs: " + schemaEntryAttr);
1685
1686 if (schemaEntryAttr == null || schemaEntryAttr.size() < 0) {
1687 if (currentDN.length() == 0 && name.isEmpty()) {
1688 // the server doesn't have a subschemasubentry in its root DSE.
1689 // therefore, it doesn't support schema.
1690 throw new OperationNotSupportedException(
1691 "Cannot read subschemasubentry of root DSE");
1692 } else {
1693 return getSchemaEntry(new CompositeName(), false);
1694 }
1695 }
1696
1697 return (String)(schemaEntryAttr.get()); // return schema entry name
1698 }
1699
1700 // package-private; used by search enum.
1701 // Set attributes to point to this context in case some one
1702 // asked for their schema
1703 void setParents(Attributes attrs, Name name) throws NamingException {
1704 NamingEnumeration ae = attrs.getAll();
1705 while(ae.hasMore()) {
1706 ((LdapAttribute) ae.next()).setParent(this, name);
1707 }
1708 }
1709
1710 /*
1711 * Returns the URL associated with this context; used by LdapAttribute
1712 * after deserialization to get pointer to this context.
1713 */
1714 String getURL() {
1715 if (url == null) {
1716 url = LdapURL.toUrlString(hostname, port_number, currentDN,
1717 hasLdapsScheme);
1718 }
1719
1720 return url;
1721 }
1722
1723 // --------------------- Searches -----------------------------
1724 protected NamingEnumeration c_search(Name name,
1725 Attributes matchingAttributes,
1726 Continuation cont)
1727 throws NamingException {
1728 return c_search(name, matchingAttributes, null, cont);
1729 }
1730
1731 protected NamingEnumeration c_search(Name name,
1732 Attributes matchingAttributes,
1733 String[] attributesToReturn,
1734 Continuation cont)
1735 throws NamingException {
1736 SearchControls cons = new SearchControls();
1737 cons.setReturningAttributes(attributesToReturn);
1738 String filter;
1739 try {
1740 filter = SearchFilter.format(matchingAttributes);
1741 } catch (NamingException e) {
1742 cont.setError(this, name);
1743 throw cont.fillInException(e);
1744 }
1745 return c_search(name, filter, cons, cont);
1746 }
1747
1748 protected NamingEnumeration c_search(Name name,
1749 String filter,
1750 SearchControls cons,
1751 Continuation cont)
1752 throws NamingException {
1753 return searchAux(name, filter, cloneSearchControls(cons), true, true,
1754 cont);
1755 }
1756
1757 protected NamingEnumeration c_search(Name name,
1758 String filterExpr,
1759 Object[] filterArgs,
1760 SearchControls cons,
1761 Continuation cont)
1762 throws NamingException {
1763 String strfilter;
1764 try {
1765 strfilter = SearchFilter.format(filterExpr, filterArgs);
1766 } catch (NamingException e) {
1767 cont.setError(this, name);
1768 throw cont.fillInException(e);
1769 }
1770 return c_search(name, strfilter, cons, cont);
1771 }
1772
1773 // Used by NamingNotifier
1774 NamingEnumeration searchAux(Name name,
1775 String filter,
1776 SearchControls cons,
1777 boolean relative,
1778 boolean waitForReply, Continuation cont) throws NamingException {
1779
1780 LdapResult answer = null;
1781 String[] tokens = new String[2]; // stores ldap compare op. values
1782 String[] reqAttrs; // remember what was asked
1783
1784 if (cons == null) {
1785 cons = new SearchControls();
1786 }
1787 reqAttrs = cons.getReturningAttributes();
1788
1789 // if objects are requested then request the Java attributes too
1790 // so that the objects can be constructed
1791 if (cons.getReturningObjFlag()) {
1792 if (reqAttrs != null) {
1793
1794 // check for presence of "*" (user attributes wildcard)
1795 boolean hasWildcard = false;
1796 for (int i = reqAttrs.length - 1; i >= 0; i--) {
1797 if (reqAttrs[i].equals("*")) {
1798 hasWildcard = true;
1799 break;
1800 }
1801 }
1802 if (! hasWildcard) {
1803 String[] totalAttrs =
1804 new String[reqAttrs.length +Obj.JAVA_ATTRIBUTES.length];
1805 System.arraycopy(reqAttrs, 0, totalAttrs, 0,
1806 reqAttrs.length);
1807 System.arraycopy(Obj.JAVA_ATTRIBUTES, 0, totalAttrs,
1808 reqAttrs.length, Obj.JAVA_ATTRIBUTES.length);
1809
1810 cons.setReturningAttributes(totalAttrs);
1811 }
1812 }
1813 }
1814
1815 LdapCtx.SearchArgs args =
1816 new LdapCtx.SearchArgs(name, filter, cons, reqAttrs);
1817
1818 cont.setError(this, name);
1819 try {
1820 // see if this can be done as a compare, otherwise do a search
1821 if (searchToCompare(filter, cons, tokens)){
1822 //System.err.println("compare triggered");
1823 answer = compare(name, tokens[0], tokens[1]);
1824 if (! (answer.compareToSearchResult(fullyQualifiedName(name)))){
1825 processReturnCode(answer, name);
1826 }
1827 } else {
1828 answer = doSearch(name, filter, cons, relative, waitForReply);
1829 // search result may contain referrals
1830 processReturnCode(answer, name);
1831 }
1832 return new LdapSearchEnumeration(this, answer,
1833 fullyQualifiedName(name), args, cont);
1834
1835 } catch (LdapReferralException e) {
1836 if (handleReferrals == LdapClient.LDAP_REF_THROW)
1837 throw cont.fillInException(e);
1838
1839 // process the referrals sequentially
1840 while (true) {
1841
1842 LdapReferralContext refCtx =
1843 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
1844
1845 // repeat the original operation at the new context
1846 try {
1847
1848 return refCtx.search(name, filter, cons);
1849
1850 } catch (LdapReferralException re) {
1851 e = re;
1852 continue;
1853
1854 } finally {
1855 // Make sure we close referral context
1856 refCtx.close();
1857 }
1858 }
1859
1860 } catch (LimitExceededException e) {
1861 LdapSearchEnumeration res =
1862 new LdapSearchEnumeration(this, answer, fullyQualifiedName(name),
1863 args, cont);
1864 res.setNamingException(e);
1865 return res;
1866
1867 } catch (PartialResultException e) {
1868 LdapSearchEnumeration res =
1869 new LdapSearchEnumeration(this, answer, fullyQualifiedName(name),
1870 args, cont);
1871
1872 res.setNamingException(e);
1873 return res;
1874
1875 } catch (IOException e) {
1876 NamingException e2 = new CommunicationException(e.getMessage());
1877 e2.setRootCause(e);
1878 throw cont.fillInException(e2);
1879
1880 } catch (NamingException e) {
1881 throw cont.fillInException(e);
1882 }
1883 }
1884
1885
1886 LdapResult getSearchReply(LdapClient eClnt, LdapResult res)
1887 throws NamingException {
1888 // ensureOpen() won't work here because
1889 // session was associated with previous connection
1890
1891 // %%% RL: we can actually allow the enumeration to continue
1892 // using the old handle but other weird things might happen
1893 // when we hit a referral
1894 if (clnt != eClnt) {
1895 throw new CommunicationException(
1896 "Context's connection changed; unable to continue enumeration");
1897 }
1898
1899 try {
1900 return eClnt.getSearchReply(batchSize, res, binaryAttrs);
1901 } catch (IOException e) {
1902 NamingException e2 = new CommunicationException(e.getMessage());
1903 e2.setRootCause(e);
1904 throw e2;
1905 }
1906 }
1907
1908 // Perform a search. Expect 1 SearchResultEntry and the SearchResultDone.
1909 private LdapResult doSearchOnce(Name name, String filter,
1910 SearchControls cons, boolean relative) throws NamingException {
1911
1912 int savedBatchSize = batchSize;
1913 batchSize = 2; // 2 protocol elements
1914
1915 LdapResult answer = doSearch(name, filter, cons, relative, true);
1916
1917 batchSize = savedBatchSize;
1918 return answer;
1919 }
1920
1921 private LdapResult doSearch(Name name, String filter, SearchControls cons,
1922 boolean relative, boolean waitFirstReply) throws NamingException {
1923 ensureOpen();
1924 try {
1925 int scope;
1926
1927 switch (cons.getSearchScope()) {
1928 case SearchControls.OBJECT_SCOPE:
1929 scope = LdapClient.SCOPE_BASE_OBJECT;
1930 break;
1931 default:
1932 case SearchControls.ONELEVEL_SCOPE:
1933 scope = LdapClient.SCOPE_ONE_LEVEL;
1934 break;
1935 case SearchControls.SUBTREE_SCOPE:
1936 scope = LdapClient.SCOPE_SUBTREE;
1937 break;
1938 }
1939
1940 // If cons.getReturningObjFlag() then caller should already
1941 // have make sure to request the appropriate attrs
1942
1943 String[] retattrs = cons.getReturningAttributes();
1944 if (retattrs != null && retattrs.length == 0) {
1945 // Ldap treats null and empty array the same
1946 // need to replace with single element array
1947 retattrs = new String[1];
1948 retattrs[0] = "1.1";
1949 }
1950
1951 String nm = (relative
1952 ? fullyQualifiedName(name)
1953 : (name.isEmpty()
1954 ? ""
1955 : name.get(0)));
1956
1957 // JNDI unit is milliseconds, LDAP unit is seconds.
1958 // Zero means no limit.
1959 int msecLimit = cons.getTimeLimit();
1960 int secLimit = 0;
1961
1962 if (msecLimit > 0) {
1963 secLimit = (msecLimit / 1000) + 1;
1964 }
1965
1966 LdapResult answer =
1967 clnt.search(nm,
1968 scope,
1969 derefAliases,
1970 (int)cons.getCountLimit(),
1971 secLimit,
1972 cons.getReturningObjFlag() ? false : typesOnly,
1973 retattrs,
1974 filter,
1975 batchSize,
1976 reqCtls,
1977 binaryAttrs,
1978 waitFirstReply);
1979 respCtls = answer.resControls; // retrieve response controls
1980 return answer;
1981
1982 } catch (IOException e) {
1983 NamingException e2 = new CommunicationException(e.getMessage());
1984 e2.setRootCause(e);
1985 throw e2;
1986 }
1987 }
1988
1989
1990 /*
1991 * Certain simple JNDI searches are automatically converted to
1992 * LDAP compare operations by the LDAP service provider. A search
1993 * is converted to a compare iff:
1994 *
1995 * - the scope is set to OBJECT_SCOPE
1996 * - the filter string contains a simple assertion: "<type>=<value>"
1997 * - the returning attributes list is present but empty
1998 */
1999
2000 // returns true if a search can be caried out as a compare, and sets
2001 // tokens[0] and tokens[1] to the type and value respectively.
2002 // e.g. filter "cn=Jon Ruiz" becomes, type "cn" and value "Jon Ruiz"
2003 // This function uses the documents JNDI Compare example as a model
2004 // for when to turn a search into a compare.
2005
2006 private static boolean searchToCompare(
2007 String filter,
2008 SearchControls cons,
2009 String tokens[]) {
2010
2011 // if scope is not object-scope, it's really a search
2012 if (cons.getSearchScope() != SearchControls.OBJECT_SCOPE) {
2013 return false;
2014 }
2015
2016 // if attributes are to be returned, it's really a search
2017 String[] attrs = cons.getReturningAttributes();
2018 if (attrs == null || attrs.length != 0) {
2019 return false;
2020 }
2021
2022 // if the filter not a simple assertion, it's really a search
2023 if (! filterToAssertion(filter, tokens)) {
2024 return false;
2025 }
2026
2027 // it can be converted to a compare
2028 return true;
2029 }
2030
2031 // If the supplied filter is a simple assertion i.e. "<type>=<value>"
2032 // (enclosing parentheses are permitted) then
2033 // filterToAssertion will return true and pass the type and value as
2034 // the first and second elements of tokens respectively.
2035 // precondition: tokens[] must be initialized and be at least of size 2.
2036
2037 private static boolean filterToAssertion(String filter, String tokens[]) {
2038
2039 // find the left and right half of the assertion
2040 StringTokenizer assertionTokenizer = new StringTokenizer(filter, "=");
2041
2042 if (assertionTokenizer.countTokens() != 2) {
2043 return false;
2044 }
2045
2046 tokens[0] = assertionTokenizer.nextToken();
2047 tokens[1] = assertionTokenizer.nextToken();
2048
2049 // make sure the value does not contain a wildcard
2050 if (tokens[1].indexOf('*') != -1) {
2051 return false;
2052 }
2053
2054 // test for enclosing parenthesis
2055 boolean hasParens = false;
2056 int len = tokens[1].length();
2057
2058 if ((tokens[0].charAt(0) == '(') &&
2059 (tokens[1].charAt(len - 1) == ')')) {
2060 hasParens = true;
2061
2062 } else if ((tokens[0].charAt(0) == '(') ||
2063 (tokens[1].charAt(len - 1) == ')')) {
2064 return false; // unbalanced
2065 }
2066
2067 // make sure the left and right half are not expresions themselves
2068 StringTokenizer illegalCharsTokenizer =
2069 new StringTokenizer(tokens[0], "()&|!=~><*", true);
2070
2071 if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {
2072 return false;
2073 }
2074
2075 illegalCharsTokenizer =
2076 new StringTokenizer(tokens[1], "()&|!=~><*", true);
2077
2078 if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {
2079 return false;
2080 }
2081
2082 // strip off enclosing parenthesis, if present
2083 if (hasParens) {
2084 tokens[0] = tokens[0].substring(1);
2085 tokens[1] = tokens[1].substring(0, len - 1);
2086 }
2087
2088 return true;
2089 }
2090
2091 private LdapResult compare(Name name, String type, String value)
2092 throws IOException, NamingException {
2093
2094 ensureOpen();
2095 String nm = fullyQualifiedName(name);
2096
2097 LdapResult answer = clnt.compare(nm, type, value, reqCtls);
2098 respCtls = answer.resControls; // retrieve response controls
2099
2100 return answer;
2101 }
2102
2103 private static SearchControls cloneSearchControls(SearchControls cons) {
2104 if (cons == null) {
2105 return null;
2106 }
2107 String[] retAttrs = cons.getReturningAttributes();
2108 if (retAttrs != null) {
2109 String[] attrs = new String[retAttrs.length];
2110 System.arraycopy(retAttrs, 0, attrs, 0, retAttrs.length);
2111 retAttrs = attrs;
2112 }
2113 return new SearchControls(cons.getSearchScope(),
2114 cons.getCountLimit(),
2115 cons.getTimeLimit(),
2116 retAttrs,
2117 cons.getReturningObjFlag(),
2118 cons.getDerefLinkFlag());
2119 }
2120
2121 // -------------- Environment Properties ------------------
2122
2123 /**
2124 * Override with noncloning version.
2125 */
2126 protected Hashtable p_getEnvironment() {
2127 return envprops;
2128 }
2129
2130 public Hashtable getEnvironment() throws NamingException {
2131 return (envprops == null
2132 ? new Hashtable(5, 0.75f)
2133 : (Hashtable)envprops.clone());
2134 }
2135
2136 public Object removeFromEnvironment(String propName)
2137 throws NamingException {
2138
2139 // not there; just return
2140 if (envprops == null || envprops.get(propName) == null) {
2141 return null;
2142 }
2143
2144 if (propName.equals(REF_SEPARATOR)) {
2145 addrEncodingSeparator = DEFAULT_REF_SEPARATOR;
2146 } else if (propName.equals(TYPES_ONLY)) {
2147 typesOnly = DEFAULT_TYPES_ONLY;
2148 } else if (propName.equals(DELETE_RDN)) {
2149 deleteRDN = DEFAULT_DELETE_RDN;
2150 } else if (propName.equals(DEREF_ALIASES)) {
2151 derefAliases = DEFAULT_DEREF_ALIASES;
2152 } else if (propName.equals(Context.BATCHSIZE)) {
2153 batchSize = DEFAULT_BATCH_SIZE;
2154 } else if (propName.equals(REFERRAL_LIMIT)) {
2155 referralHopLimit = DEFAULT_REFERRAL_LIMIT;
2156 } else if (propName.equals(Context.REFERRAL)) {
2157 setReferralMode(null, true);
2158 } else if (propName.equals(BINARY_ATTRIBUTES)) {
2159 setBinaryAttributes(null);
2160 } else if (propName.equals(CONNECT_TIMEOUT)) {
2161 connectTimeout = -1;
2162 } else if (propName.equals(READ_TIMEOUT)) {
2163 readTimeout = -1;
2164
2165 // The following properties affect the connection
2166
2167 } else if (propName.equals(Context.SECURITY_PROTOCOL)) {
2168 closeConnection(SOFT_CLOSE);
2169 // De-activate SSL and reset the context's url and port number
2170 if (useSsl && !hasLdapsScheme) {
2171 useSsl = false;
2172 url = null;
2173 if (useDefaultPortNumber) {
2174 port_number = DEFAULT_PORT;
2175 }
2176 }
2177 } else if (propName.equals(VERSION) ||
2178 propName.equals(SOCKET_FACTORY)) {
2179 closeConnection(SOFT_CLOSE);
2180 } else if(propName.equals(Context.SECURITY_AUTHENTICATION) ||
2181 propName.equals(Context.SECURITY_PRINCIPAL) ||
2182 propName.equals(Context.SECURITY_CREDENTIALS)) {
2183 sharable = false;
2184 }
2185
2186 // Update environment; reconnection will use new props
2187 envprops = (Hashtable)envprops.clone();
2188 return envprops.remove(propName);
2189 }
2190
2191 public Object addToEnvironment(String propName, Object propVal)
2192 throws NamingException {
2193
2194 // If adding null, call remove
2195 if (propVal == null) {
2196 return removeFromEnvironment(propName);
2197 }
2198
2199 if (propName.equals(REF_SEPARATOR)) {
2200 setRefSeparator((String)propVal);
2201 } else if (propName.equals(TYPES_ONLY)) {
2202 setTypesOnly((String)propVal);
2203 } else if (propName.equals(DELETE_RDN)) {
2204 setDeleteRDN((String)propVal);
2205 } else if (propName.equals(DEREF_ALIASES)) {
2206 setDerefAliases((String)propVal);
2207 } else if (propName.equals(Context.BATCHSIZE)) {
2208 setBatchSize((String)propVal);
2209 } else if (propName.equals(REFERRAL_LIMIT)) {
2210 setReferralLimit((String)propVal);
2211 } else if (propName.equals(Context.REFERRAL)) {
2212 setReferralMode((String)propVal, true);
2213 } else if (propName.equals(BINARY_ATTRIBUTES)) {
2214 setBinaryAttributes((String)propVal);
2215 } else if (propName.equals(CONNECT_TIMEOUT)) {
2216 setConnectTimeout((String)propVal);
2217 } else if (propName.equals(READ_TIMEOUT)) {
2218 setReadTimeout((String)propVal);
2219 // The following properties affect the connection
2220
2221 } else if (propName.equals(Context.SECURITY_PROTOCOL)) {
2222 closeConnection(SOFT_CLOSE);
2223 // Activate SSL and reset the context's url and port number
2224 if ("ssl".equals(propVal)) {
2225 useSsl = true;
2226 url = null;
2227 if (useDefaultPortNumber) {
2228 port_number = DEFAULT_SSL_PORT;
2229 }
2230 }
2231 } else if (propName.equals(VERSION) ||
2232 propName.equals(SOCKET_FACTORY)) {
2233 closeConnection(SOFT_CLOSE);
2234 } else if (propName.equals(Context.SECURITY_AUTHENTICATION) ||
2235 propName.equals(Context.SECURITY_PRINCIPAL) ||
2236 propName.equals(Context.SECURITY_CREDENTIALS)) {
2237 sharable = false;
2238 }
2239
2240 // Update environment; reconnection will use new props
2241 envprops = (envprops == null
2242 ? new Hashtable(5, 0.75f)
2243 : (Hashtable)envprops.clone());
2244 return envprops.put(propName, propVal);
2245 }
2246
2247 /**
2248 * Sets the URL that created the context in the java.naming.provider.url
2249 * property.
2250 */
2251 void setProviderUrl(String providerUrl) { // called by LdapCtxFactory
2252 if (envprops != null) {
2253 envprops.put(Context.PROVIDER_URL, providerUrl);
2254 }
2255 }
2256
2257 /**
2258 * Sets the domain name for the context in the com.sun.jndi.ldap.domainname
2259 * property.
2260 * Used for hostname verification by Start TLS
2261 */
2262 void setDomainName(String domainName) { // called by LdapCtxFactory
2263 if (envprops != null) {
2264 envprops.put(DOMAIN_NAME, domainName);
2265 }
2266 }
2267
2268 private void initEnv() throws NamingException {
2269 if (envprops == null) {
2270 // Make sure that referrals are to their default
2271 setReferralMode(null, false);
2272 return;
2273 }
2274
2275 // Set batch size
2276 setBatchSize((String)envprops.get(Context.BATCHSIZE));
2277
2278 // Set separator used for encoding RefAddr
2279 setRefSeparator((String)envprops.get(REF_SEPARATOR));
2280
2281 // Set whether RDN is removed when renaming object
2282 setDeleteRDN((String)envprops.get(DELETE_RDN));
2283
2284 // Set whether types are returned only
2285 setTypesOnly((String)envprops.get(TYPES_ONLY));
2286
2287 // Set how aliases are dereferenced
2288 setDerefAliases((String)envprops.get(DEREF_ALIASES));
2289
2290 // Set the limit on referral chains
2291 setReferralLimit((String)envprops.get(REFERRAL_LIMIT));
2292
2293 setBinaryAttributes((String)envprops.get(BINARY_ATTRIBUTES));
2294
2295 bindCtls = cloneControls((Control[]) envprops.get(BIND_CONTROLS));
2296
2297 // set referral handling
2298 setReferralMode((String)envprops.get(Context.REFERRAL), false);
2299
2300 // Set the connect timeout
2301 setConnectTimeout((String)envprops.get(CONNECT_TIMEOUT));
2302
2303 // Set the read timeout
2304 setReadTimeout((String)envprops.get(READ_TIMEOUT));
2305
2306 // When connection is created, it will use these and other
2307 // properties from the environment
2308 }
2309
2310 private void setDeleteRDN(String deleteRDNProp) {
2311 if ((deleteRDNProp != null) &&
2312 (deleteRDNProp.equalsIgnoreCase("false"))) {
2313 deleteRDN = false;
2314 } else {
2315 deleteRDN = DEFAULT_DELETE_RDN;
2316 }
2317 }
2318
2319 private void setTypesOnly(String typesOnlyProp) {
2320 if ((typesOnlyProp != null) &&
2321 (typesOnlyProp.equalsIgnoreCase("true"))) {
2322 typesOnly = true;
2323 } else {
2324 typesOnly = DEFAULT_TYPES_ONLY;
2325 }
2326 }
2327
2328 /**
2329 * Sets the batch size of this context;
2330 */
2331 private void setBatchSize(String batchSizeProp) {
2332 // set batchsize
2333 if (batchSizeProp != null) {
2334 batchSize = Integer.parseInt(batchSizeProp);
2335 } else {
2336 batchSize = DEFAULT_BATCH_SIZE;
2337 }
2338 }
2339
2340 /**
2341 * Sets the referral mode of this context to 'follow', 'throw' or 'ignore'.
2342 * If referral mode is 'ignore' then activate the manageReferral control.
2343 */
2344 private void setReferralMode(String ref, boolean update) {
2345 // First determine the referral mode
2346 if (ref != null) {
2347 if (ref.equals("follow")) {
2348 handleReferrals = LdapClient.LDAP_REF_FOLLOW;
2349 } else if (ref.equals("throw")) {
2350 handleReferrals = LdapClient.LDAP_REF_THROW;
2351 } else if (ref.equals("ignore")) {
2352 handleReferrals = LdapClient.LDAP_REF_IGNORE;
2353 } else {
2354 throw new IllegalArgumentException(
2355 "Illegal value for " + Context.REFERRAL + " property.");
2356 }
2357 } else {
2358 handleReferrals = DEFAULT_REFERRAL_MODE;
2359 }
2360
2361 if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
2362 // If ignoring referrals, add manageReferralControl
2363 reqCtls = addControl(reqCtls, manageReferralControl);
2364
2365 } else if (update) {
2366
2367 // If we're update an existing context, remove the control
2368 reqCtls = removeControl(reqCtls, manageReferralControl);
2369
2370 } // else, leave alone; need not update
2371 }
2372
2373 /**
2374 * Set whether aliases are derefereced during resolution and searches.
2375 */
2376 private void setDerefAliases(String deref) {
2377 if (deref != null) {
2378 if (deref.equals("never")) {
2379 derefAliases = 0; // never de-reference aliases
2380 } else if (deref.equals("searching")) {
2381 derefAliases = 1; // de-reference aliases during searching
2382 } else if (deref.equals("finding")) {
2383 derefAliases = 2; // de-reference during name resolution
2384 } else if (deref.equals("always")) {
2385 derefAliases = 3; // always de-reference aliases
2386 } else {
2387 throw new IllegalArgumentException("Illegal value for " +
2388 DEREF_ALIASES + " property.");
2389 }
2390 } else {
2391 derefAliases = DEFAULT_DEREF_ALIASES;
2392 }
2393 }
2394
2395 private void setRefSeparator(String sepStr) throws NamingException {
2396 if (sepStr != null && sepStr.length() > 0) {
2397 addrEncodingSeparator = sepStr.charAt(0);
2398 } else {
2399 addrEncodingSeparator = DEFAULT_REF_SEPARATOR;
2400 }
2401 }
2402
2403 /**
2404 * Sets the limit on referral chains
2405 */
2406 private void setReferralLimit(String referralLimitProp) {
2407 // set referral limit
2408 if (referralLimitProp != null) {
2409 referralHopLimit = Integer.parseInt(referralLimitProp);
2410
2411 // a zero setting indicates no limit
2412 if (referralHopLimit == 0)
2413 referralHopLimit = Integer.MAX_VALUE;
2414 } else {
2415 referralHopLimit = DEFAULT_REFERRAL_LIMIT;
2416 }
2417 }
2418
2419 // For counting referral hops
2420 void setHopCount(int hopCount) {
2421 this.hopCount = hopCount;
2422 }
2423
2424 /**
2425 * Sets the connect timeout value
2426 */
2427 private void setConnectTimeout(String connectTimeoutProp) {
2428 if (connectTimeoutProp != null) {
2429 connectTimeout = Integer.parseInt(connectTimeoutProp);
2430 } else {
2431 connectTimeout = -1;
2432 }
2433 }
2434
2435 /**
2436 * Sets the read timeout value
2437 */
2438 private void setReadTimeout(String readTimeoutProp) {
2439 if (readTimeoutProp != null) {
2440 readTimeout = Integer.parseInt(readTimeoutProp);
2441 } else {
2442 readTimeout = -1;
2443 }
2444 }
2445
2446 /*
2447 * Extract URLs from a string. The format of the string is:
2448 *
2449 * <urlstring > ::= "Referral:" <ldapurls>
2450 * <ldapurls> ::= <separator> <ldapurl> | <ldapurls>
2451 * <separator> ::= ASCII linefeed character (0x0a)
2452 * <ldapurl> ::= LDAP URL format (RFC 1959)
2453 */
2454 private static Vector extractURLs(String refString) {
2455
2456 int separator = 0;
2457 int urlCount = 0;
2458
2459 // count the number of URLs
2460 while ((separator = refString.indexOf('\n', separator)) >= 0) {
2461 separator++;
2462 urlCount++;
2463 }
2464
2465 Vector referrals = new Vector(urlCount);
2466 int iURL;
2467 int i = 0;
2468
2469 separator = refString.indexOf('\n');
2470 iURL = separator + 1;
2471 while ((separator = refString.indexOf('\n', iURL)) >= 0) {
2472 referrals.addElement(refString.substring(iURL, separator));
2473 iURL = separator + 1;
2474 }
2475 referrals.addElement(refString.substring(iURL));
2476
2477 return referrals;
2478 }
2479
2480 /*
2481 * Argument is a space-separated list of attribute IDs
2482 * Converts attribute IDs to lowercase before adding to built-in list.
2483 */
2484 private void setBinaryAttributes(String attrIds) {
2485 if (attrIds == null) {
2486 binaryAttrs = null;
2487 } else {
2488 binaryAttrs = new Hashtable(11, 0.75f);
2489 StringTokenizer tokens =
2490 new StringTokenizer(attrIds.toLowerCase(), " ");
2491
2492 while (tokens.hasMoreTokens()) {
2493 binaryAttrs.put(tokens.nextToken(), Boolean.TRUE);
2494 }
2495 }
2496 }
2497
2498 // ----------------- Connection ---------------------
2499
2500 protected void finalize() {
2501 try {
2502 close();
2503 } catch (NamingException e) {
2504 // ignore failures
2505 }
2506 }
2507
2508 synchronized public void close() throws NamingException {
2509 if (debug) {
2510 System.err.println("LdapCtx: close() called " + this);
2511 (new Throwable()).printStackTrace();
2512 }
2513
2514 // Event (normal and unsolicited)
2515 if (eventSupport != null) {
2516 eventSupport.cleanup(); // idempotent
2517 removeUnsolicited();
2518 }
2519
2520 // Enumerations that are keeping the connection alive
2521 if (enumCount > 0) {
2522 if (debug)
2523 System.err.println("LdapCtx: close deferred");
2524 closeRequested = true;
2525 return;
2526 }
2527 closeConnection(SOFT_CLOSE);
2528
2529 // %%%: RL: There is no need to set these to null, as they're just
2530 // variables whose contents and references will automatically
2531 // be cleaned up when they're no longer referenced.
2532 // Also, setting these to null creates problems for the attribute
2533 // schema-related methods, which need these to work.
2534 /*
2535 schemaTrees = null;
2536 envprops = null;
2537 */
2538 }
2539
2540 public void reconnect(Control[] connCtls) throws NamingException {
2541 // Update environment
2542 envprops = (envprops == null
2543 ? new Hashtable(5, 0.75f)
2544 : (Hashtable)envprops.clone());
2545
2546 if (connCtls == null) {
2547 envprops.remove(BIND_CONTROLS);
2548 bindCtls = null;
2549 } else {
2550 envprops.put(BIND_CONTROLS, bindCtls = cloneControls(connCtls));
2551 }
2552
2553 sharable = false; // can't share with existing contexts
2554 ensureOpen(); // open or reauthenticated
2555 }
2556
2557 private void ensureOpen() throws NamingException {
2558 ensureOpen(false);
2559 }
2560
2561 private void ensureOpen(boolean startTLS) throws NamingException {
2562
2563 try {
2564 if (clnt == null) {
2565 if (debug) {
2566 System.err.println("LdapCtx: Reconnecting " + this);
2567 }
2568
2569 // reset the cache before a new connection is established
2570 schemaTrees = new Hashtable(11, 0.75f);
2571 connect(startTLS);
2572
2573 } else if (!sharable || startTLS) {
2574
2575 synchronized (clnt) {
2576 if (!clnt.isLdapv3
2577 || clnt.referenceCount > 1
2578 || clnt.usingSaslStreams()) {
2579 closeConnection(SOFT_CLOSE);
2580 }
2581 }
2582 // reset the cache before a new connection is established
2583 schemaTrees = new Hashtable(11, 0.75f);
2584 connect(startTLS);
2585 }
2586
2587 } finally {
2588 sharable = true; // connection is now either new or single-use
2589 // OK for others to start sharing again
2590 }
2591 }
2592
2593 private void connect(boolean startTLS) throws NamingException {
2594 if (debug) { System.err.println("LdapCtx: Connecting " + this); }
2595
2596 String user = null; // authenticating user
2597 Object passwd = null; // password for authenticating user
2598 String secProtocol = null; // security protocol (e.g. "ssl")
2599 String socketFactory = null; // socket factory
2600 String authMechanism = null; // authentication mechanism
2601 String ver = null;
2602 int ldapVersion; // LDAP protocol version
2603 boolean usePool = false; // enable connection pooling
2604
2605 if (envprops != null) {
2606 user = (String)envprops.get(Context.SECURITY_PRINCIPAL);
2607 passwd = envprops.get(Context.SECURITY_CREDENTIALS);
2608 ver = (String)envprops.get(VERSION);
2609 secProtocol =
2610 useSsl ? "ssl" : (String)envprops.get(Context.SECURITY_PROTOCOL);
2611 socketFactory = (String)envprops.get(SOCKET_FACTORY);
2612 authMechanism =
2613 (String)envprops.get(Context.SECURITY_AUTHENTICATION);
2614
2615 usePool = "true".equalsIgnoreCase((String)envprops.get(ENABLE_POOL));
2616 }
2617
2618 if (socketFactory == null) {
2619 socketFactory =
2620 "ssl".equals(secProtocol) ? DEFAULT_SSL_FACTORY : null;
2621 }
2622
2623 if (authMechanism == null) {
2624 authMechanism = (user == null) ? "none" : "simple";
2625 }
2626
2627 try {
2628 boolean initial = (clnt == null);
2629
2630 if (initial) {
2631 ldapVersion = (ver != null) ? Integer.parseInt(ver) :
2632 DEFAULT_LDAP_VERSION;
2633
2634 clnt = LdapClient.getInstance(
2635 usePool, // Whether to use connection pooling
2636
2637 // Required for LdapClient constructor
2638 hostname,
2639 port_number,
2640 socketFactory,
2641 connectTimeout,
2642 readTimeout,
2643 trace,
2644
2645 // Required for basic client identity
2646 ldapVersion,
2647 authMechanism,
2648 bindCtls,
2649 secProtocol,
2650
2651 // Required for simple client identity
2652 user,
2653 passwd,
2654
2655 // Required for SASL client identity
2656 envprops);
2657
2658
2659 /**
2660 * Pooled connections are preauthenticated;
2661 * newly created ones are not.
2662 */
2663 if (clnt.authenticateCalled()) {
2664 return;
2665 }
2666
2667 } else if (sharable && startTLS) {
2668 return; // no authentication required
2669
2670 } else {
2671 // reauthenticating over existing connection;
2672 // only v3 supports this
2673 ldapVersion = LdapClient.LDAP_VERSION3;
2674 }
2675
2676 LdapResult answer = clnt.authenticate(initial,
2677 user, passwd, ldapVersion, authMechanism, bindCtls, envprops);
2678
2679 respCtls = answer.resControls; // retrieve (bind) response controls
2680
2681 if (answer.status != LdapClient.LDAP_SUCCESS) {
2682 if (initial) {
2683 closeConnection(HARD_CLOSE); // hard close
2684 }
2685 processReturnCode(answer);
2686 }
2687
2688 } catch (LdapReferralException e) {
2689 if (handleReferrals == LdapClient.LDAP_REF_THROW)
2690 throw e;
2691
2692 String referral;
2693 LdapURL url;
2694 NamingException saved_ex = null;
2695
2696 // Process the referrals sequentially (top level) and
2697 // recursively (per referral)
2698 while (true) {
2699
2700 if ((referral = e.getNextReferral()) == null) {
2701 // No more referrals to follow
2702
2703 if (saved_ex != null) {
2704 throw (NamingException)(saved_ex.fillInStackTrace());
2705 } else {
2706 // No saved exception, something must have gone wrong
2707 throw new NamingException(
2708 "Internal error processing referral during connection");
2709 }
2710 }
2711
2712 // Use host/port number from referral
2713 url = new LdapURL(referral);
2714 hostname = url.getHost();
2715 if ((hostname != null) && (hostname.charAt(0) == '[')) {
2716 hostname = hostname.substring(1, hostname.length() - 1);
2717 }
2718 port_number = url.getPort();
2719
2720 // Try to connect again using new host/port number
2721 try {
2722 connect(startTLS);
2723 break;
2724
2725 } catch (NamingException ne) {
2726 saved_ex = ne;
2727 continue; // follow another referral
2728 }
2729 }
2730 }
2731 }
2732
2733 private void closeConnection(boolean hardclose) {
2734 removeUnsolicited(); // idempotent
2735
2736 if (clnt != null) {
2737 if (debug) {
2738 System.err.println("LdapCtx: calling clnt.close() " + this);
2739 }
2740 clnt.close(reqCtls, hardclose);
2741 clnt = null;
2742 }
2743 }
2744
2745 // Used by Enum classes to track whether it still needs context
2746 private int enumCount = 0;
2747 private boolean closeRequested = false;
2748
2749 synchronized void incEnumCount() {
2750 ++enumCount;
2751 if (debug) System.err.println("LdapCtx: " + this + " enum inc: " + enumCount);
2752 }
2753
2754 synchronized void decEnumCount() {
2755 --enumCount;
2756 if (debug) System.err.println("LdapCtx: " + this + " enum dec: " + enumCount);
2757
2758 if (enumCount == 0 && closeRequested) {
2759 try {
2760 close();
2761 } catch (NamingException e) {
2762 // ignore failures
2763 }
2764 }
2765 }
2766
2767
2768 // ------------ Return code and Error messages -----------------------
2769
2770 protected void processReturnCode(LdapResult answer) throws NamingException {
2771 processReturnCode(answer, null, this, null, envprops, null);
2772 }
2773
2774 void processReturnCode(LdapResult answer, Name remainName)
2775 throws NamingException {
2776 processReturnCode(answer,
2777 (new CompositeName()).add(currentDN),
2778 this,
2779 remainName,
2780 envprops,
2781 fullyQualifiedName(remainName));
2782 }
2783
2784 protected void processReturnCode(LdapResult res, Name resolvedName,
2785 Object resolvedObj, Name remainName, Hashtable envprops, String fullDN)
2786 throws NamingException {
2787
2788 String msg = LdapClient.getErrorMessage(res.status, res.errorMessage);
2789 NamingException e;
2790 LdapReferralException r = null;
2791
2792 switch (res.status) {
2793
2794 case LdapClient.LDAP_SUCCESS:
2795
2796 // handle Search continuation references
2797 if (res.referrals != null) {
2798
2799 msg = "Unprocessed Continuation Reference(s)";
2800
2801 if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
2802 e = new PartialResultException(msg);
2803 break;
2804 }
2805
2806 // handle multiple sets of URLs
2807 int contRefCount = res.referrals.size();
2808 LdapReferralException head = null;
2809 LdapReferralException ptr = null;
2810
2811 msg = "Continuation Reference";
2812
2813 // make a chain of LdapReferralExceptions
2814 for (int i = 0; i < contRefCount; i++) {
2815
2816 r = new LdapReferralException(resolvedName, resolvedObj,
2817 remainName, msg, envprops, fullDN, handleReferrals,
2818 reqCtls);
2819 r.setReferralInfo((Vector)res.referrals.elementAt(i), true);
2820
2821 if (hopCount > 1) {
2822 r.setHopCount(hopCount);
2823 }
2824
2825 if (head == null) {
2826 head = ptr = r;
2827 } else {
2828 ptr.nextReferralEx = r; // append ex. to end of chain
2829 ptr = r;
2830 }
2831 }
2832 res.referrals = null; // reset
2833
2834 if (res.refEx == null) {
2835 res.refEx = head;
2836
2837 } else {
2838 ptr = res.refEx;
2839
2840 while (ptr.nextReferralEx != null) {
2841 ptr = ptr.nextReferralEx;
2842 }
2843 ptr.nextReferralEx = head;
2844 }
2845
2846 // check the hop limit
2847 if (hopCount > referralHopLimit) {
2848 NamingException lee =
2849 new LimitExceededException("Referral limit exceeded");
2850 lee.setRootCause(r);
2851 throw lee;
2852 }
2853 }
2854 return;
2855
2856 case LdapClient.LDAP_REFERRAL:
2857
2858 if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
2859 e = new PartialResultException(msg);
2860 break;
2861 }
2862
2863 r = new LdapReferralException(resolvedName, resolvedObj, remainName,
2864 msg, envprops, fullDN, handleReferrals, reqCtls);
2865 // only one set of URLs is present
2866 r.setReferralInfo((Vector)res.referrals.elementAt(0), false);
2867
2868 if (hopCount > 1) {
2869 r.setHopCount(hopCount);
2870 }
2871
2872 // check the hop limit
2873 if (hopCount > referralHopLimit) {
2874 NamingException lee =
2875 new LimitExceededException("Referral limit exceeded");
2876 lee.setRootCause(r);
2877 e = lee;
2878
2879 } else {
2880 e = r;
2881 }
2882 break;
2883
2884 /*
2885 * Handle SLAPD-style referrals.
2886 *
2887 * Referrals received during name resolution should be followed
2888 * until one succeeds - the target entry is located. An exception
2889 * is thrown now to handle these.
2890 *
2891 * Referrals received during a search operation point to unexplored
2892 * parts of the directory and each should be followed. An exception
2893 * is thrown later (during results enumeration) to handle these.
2894 */
2895
2896 case LdapClient.LDAP_PARTIAL_RESULTS:
2897
2898 if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
2899 e = new PartialResultException(msg);
2900 break;
2901 }
2902
2903 // extract SLAPD-style referrals from errorMessage
2904 if ((res.errorMessage != null) && (!res.errorMessage.equals(""))) {
2905 res.referrals = extractURLs(res.errorMessage);
2906 } else {
2907 e = new PartialResultException(msg);
2908 break;
2909 }
2910
2911 // build exception
2912 r = new LdapReferralException(resolvedName,
2913 resolvedObj,
2914 remainName,
2915 msg,
2916 envprops,
2917 fullDN,
2918 handleReferrals,
2919 reqCtls);
2920
2921 if (hopCount > 1) {
2922 r.setHopCount(hopCount);
2923 }
2924 /*
2925 * %%%
2926 * SLAPD-style referrals received during name resolution
2927 * cannot be distinguished from those received during a
2928 * search operation. Since both must be handled differently
2929 * the following rule is applied:
2930 *
2931 * If 1 referral and 0 entries is received then
2932 * assume name resolution has not yet completed.
2933 */
2934 if (((res.entries == null) || (res.entries.size() == 0)) &&
2935 (res.referrals.size() == 1)) {
2936
2937 r.setReferralInfo((Vector)res.referrals, false);
2938
2939 // check the hop limit
2940 if (hopCount > referralHopLimit) {
2941 NamingException lee =
2942 new LimitExceededException("Referral limit exceeded");
2943 lee.setRootCause(r);
2944 e = lee;
2945
2946 } else {
2947 e = r;
2948 }
2949
2950 } else {
2951 r.setReferralInfo(res.referrals, true);
2952 res.refEx = r;
2953 return;
2954 }
2955 break;
2956
2957 case LdapClient.LDAP_INVALID_DN_SYNTAX:
2958 case LdapClient.LDAP_NAMING_VIOLATION:
2959
2960 if (remainName != null) {
2961 e = new
2962 InvalidNameException(remainName.toString() + ": " + msg);
2963 } else {
2964 e = new InvalidNameException(msg);
2965 }
2966 break;
2967
2968 default:
2969 e = mapErrorCode(res.status, res.errorMessage);
2970 break;
2971 }
2972 e.setResolvedName(resolvedName);
2973 e.setResolvedObj(resolvedObj);
2974 e.setRemainingName(remainName);
2975 throw e;
2976 }
2977
2978 /**
2979 * Maps an LDAP error code to an appropriate NamingException.
2980 * %%% public; used by controls
2981 *
2982 * @param errorCode numeric LDAP error code
2983 * @param errorMessage textual description of the LDAP error. May be null.
2984 *
2985 * @return A NamingException or null if the error code indicates success.
2986 */
2987 public static NamingException mapErrorCode(int errorCode,
2988 String errorMessage) {
2989
2990 if (errorCode == LdapClient.LDAP_SUCCESS)
2991 return null;
2992
2993 NamingException e = null;
2994 String message = LdapClient.getErrorMessage(errorCode, errorMessage);
2995
2996 switch (errorCode) {
2997
2998 case LdapClient.LDAP_ALIAS_DEREFERENCING_PROBLEM:
2999 e = new NamingException(message);
3000 break;
3001
3002 case LdapClient.LDAP_ALIAS_PROBLEM:
3003 e = new NamingException(message);
3004 break;
3005
3006 case LdapClient.LDAP_ATTRIBUTE_OR_VALUE_EXISTS:
3007 e = new AttributeInUseException(message);
3008 break;
3009
3010 case LdapClient.LDAP_AUTH_METHOD_NOT_SUPPORTED:
3011 case LdapClient.LDAP_CONFIDENTIALITY_REQUIRED:
3012 case LdapClient.LDAP_STRONG_AUTH_REQUIRED:
3013 case LdapClient.LDAP_INAPPROPRIATE_AUTHENTICATION:
3014 e = new AuthenticationNotSupportedException(message);
3015 break;
3016
3017 case LdapClient.LDAP_ENTRY_ALREADY_EXISTS:
3018 e = new NameAlreadyBoundException(message);
3019 break;
3020
3021 case LdapClient.LDAP_INVALID_CREDENTIALS:
3022 case LdapClient.LDAP_SASL_BIND_IN_PROGRESS:
3023 e = new AuthenticationException(message);
3024 break;
3025
3026 case LdapClient.LDAP_INAPPROPRIATE_MATCHING:
3027 e = new InvalidSearchFilterException(message);
3028 break;
3029
3030 case LdapClient.LDAP_INSUFFICIENT_ACCESS_RIGHTS:
3031 e = new NoPermissionException(message);
3032 break;
3033
3034 case LdapClient.LDAP_INVALID_ATTRIBUTE_SYNTAX:
3035 case LdapClient.LDAP_CONSTRAINT_VIOLATION:
3036 e = new InvalidAttributeValueException(message);
3037 break;
3038
3039 case LdapClient.LDAP_LOOP_DETECT:
3040 e = new NamingException(message);
3041 break;
3042
3043 case LdapClient.LDAP_NO_SUCH_ATTRIBUTE:
3044 e = new NoSuchAttributeException(message);
3045 break;
3046
3047 case LdapClient.LDAP_NO_SUCH_OBJECT:
3048 e = new NameNotFoundException(message);
3049 break;
3050
3051 case LdapClient.LDAP_OBJECT_CLASS_MODS_PROHIBITED:
3052 case LdapClient.LDAP_OBJECT_CLASS_VIOLATION:
3053 case LdapClient.LDAP_NOT_ALLOWED_ON_RDN:
3054 e = new SchemaViolationException(message);
3055 break;
3056
3057 case LdapClient.LDAP_NOT_ALLOWED_ON_NON_LEAF:
3058 e = new ContextNotEmptyException(message);
3059 break;
3060
3061 case LdapClient.LDAP_OPERATIONS_ERROR:
3062 // %%% need new exception ?
3063 e = new NamingException(message);
3064 break;
3065
3066 case LdapClient.LDAP_OTHER:
3067 e = new NamingException(message);
3068 break;
3069
3070 case LdapClient.LDAP_PROTOCOL_ERROR:
3071 e = new CommunicationException(message);
3072 break;
3073
3074 case LdapClient.LDAP_SIZE_LIMIT_EXCEEDED:
3075 e = new SizeLimitExceededException(message);
3076 break;
3077
3078 case LdapClient.LDAP_TIME_LIMIT_EXCEEDED:
3079 e = new TimeLimitExceededException(message);
3080 break;
3081
3082 case LdapClient.LDAP_UNAVAILABLE_CRITICAL_EXTENSION:
3083 e = new OperationNotSupportedException(message);
3084 break;
3085
3086 case LdapClient.LDAP_UNAVAILABLE:
3087 case LdapClient.LDAP_BUSY:
3088 e = new ServiceUnavailableException(message);
3089 break;
3090
3091 case LdapClient.LDAP_UNDEFINED_ATTRIBUTE_TYPE:
3092 e = new InvalidAttributeIdentifierException(message);
3093 break;
3094
3095 case LdapClient.LDAP_UNWILLING_TO_PERFORM:
3096 e = new OperationNotSupportedException(message);
3097 break;
3098
3099 case LdapClient.LDAP_COMPARE_FALSE:
3100 case LdapClient.LDAP_COMPARE_TRUE:
3101 case LdapClient.LDAP_IS_LEAF:
3102 // these are really not exceptions and this code probably
3103 // never gets executed
3104 e = new NamingException(message);
3105 break;
3106
3107 case LdapClient.LDAP_ADMIN_LIMIT_EXCEEDED:
3108 e = new LimitExceededException(message);
3109 break;
3110
3111 case LdapClient.LDAP_REFERRAL:
3112 e = new NamingException(message);
3113 break;
3114
3115 case LdapClient.LDAP_PARTIAL_RESULTS:
3116 e = new NamingException(message);
3117 break;
3118
3119 case LdapClient.LDAP_INVALID_DN_SYNTAX:
3120 case LdapClient.LDAP_NAMING_VIOLATION:
3121 e = new InvalidNameException(message);
3122 break;
3123
3124 default:
3125 e = new NamingException(message);
3126 break;
3127 }
3128
3129 return e;
3130 }
3131
3132 // ----------------- Extensions and Controls -------------------
3133
3134 public ExtendedResponse extendedOperation(ExtendedRequest request)
3135 throws NamingException {
3136
3137 boolean startTLS = (request.getID().equals(STARTTLS_REQ_OID));
3138 ensureOpen(startTLS);
3139
3140 try {
3141
3142 LdapResult answer =
3143 clnt.extendedOp(request.getID(), request.getEncodedValue(),
3144 reqCtls, startTLS);
3145 respCtls = answer.resControls; // retrieve response controls
3146
3147 if (answer.status != LdapClient.LDAP_SUCCESS) {
3148 processReturnCode(answer, new CompositeName());
3149 }
3150 // %%% verify request.getID() == answer.extensionId
3151
3152 int len = (answer.extensionValue == null) ?
3153 0 :
3154 answer.extensionValue.length;
3155
3156 ExtendedResponse er =
3157 request.createExtendedResponse(answer.extensionId,
3158 answer.extensionValue, 0, len);
3159
3160 if (er instanceof StartTlsResponseImpl) {
3161 // Pass the connection handle to StartTlsResponseImpl
3162 String domainName = (String)
3163 (envprops != null ? envprops.get(DOMAIN_NAME) : null);
3164 ((StartTlsResponseImpl)er).setConnection(clnt.conn, domainName);
3165 }
3166 return er;
3167
3168 } catch (LdapReferralException e) {
3169
3170 if (handleReferrals == LdapClient.LDAP_REF_THROW)
3171 throw e;
3172
3173 // process the referrals sequentially
3174 while (true) {
3175
3176 LdapReferralContext refCtx =
3177 (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
3178
3179 // repeat the original operation at the new context
3180 try {
3181
3182 return refCtx.extendedOperation(request);
3183
3184 } catch (LdapReferralException re) {
3185 e = re;
3186 continue;
3187
3188 } finally {
3189 // Make sure we close referral context
3190 refCtx.close();
3191 }
3192 }
3193
3194 } catch (IOException e) {
3195 NamingException e2 = new CommunicationException(e.getMessage());
3196 e2.setRootCause(e);
3197 throw e2;
3198 }
3199 }
3200
3201 public void setRequestControls(Control[] reqCtls) throws NamingException {
3202 if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
3203 this.reqCtls = addControl(reqCtls, manageReferralControl);
3204 } else {
3205 this.reqCtls = cloneControls(reqCtls);
3206 }
3207 }
3208
3209 public Control[] getRequestControls() throws NamingException {
3210 return cloneControls(reqCtls);
3211 }
3212
3213 public Control[] getConnectControls() throws NamingException {
3214 return cloneControls(bindCtls);
3215 }
3216
3217 public Control[] getResponseControls() throws NamingException {
3218 return (respCtls != null)? convertControls(respCtls) : null;
3219 }
3220
3221 /**
3222 * Narrow controls using own default factory and ControlFactory.
3223 * @param ctls A non-null Vector
3224 */
3225 Control[] convertControls(Vector ctls) throws NamingException {
3226 int count = ctls.size();
3227
3228 if (count == 0) {
3229 return null;
3230 }
3231
3232 Control[] controls = new Control[count];
3233
3234 for (int i = 0; i < count; i++) {
3235 // Try own factory first
3236 controls[i] = myResponseControlFactory.getControlInstance(
3237 (Control)ctls.elementAt(i));
3238
3239 // Try assigned factories if own produced null
3240 if (controls[i] == null) {
3241 controls[i] = ControlFactory.getControlInstance(
3242 (Control)ctls.elementAt(i), this, envprops);
3243 }
3244 }
3245 return controls;
3246 }
3247
3248 private static Control[] addControl(Control[] prevCtls, Control addition) {
3249 if (prevCtls == null) {
3250 return new Control[]{addition};
3251 }
3252
3253 // Find it
3254 int found = findControl(prevCtls, addition);
3255 if (found != -1) {
3256 return prevCtls; // no need to do it again
3257 }
3258
3259 Control[] newCtls = new Control[prevCtls.length+1];
3260 System.arraycopy(prevCtls, 0, newCtls, 0, prevCtls.length);
3261 newCtls[prevCtls.length] = addition;
3262 return newCtls;
3263 }
3264
3265 private static int findControl(Control[] ctls, Control target) {
3266 for (int i = 0; i < ctls.length; i++) {
3267 if (ctls[i] == target) {
3268 return i;
3269 }
3270 }
3271 return -1;
3272 }
3273
3274 private static Control[] removeControl(Control[] prevCtls, Control target) {
3275 if (prevCtls == null) {
3276 return null;
3277 }
3278
3279 // Find it
3280 int found = findControl(prevCtls, target);
3281 if (found == -1) {
3282 return prevCtls; // not there
3283 }
3284
3285 // Remove it
3286 Control[] newCtls = new Control[prevCtls.length-1];
3287 System.arraycopy(prevCtls, 0, newCtls, 0, found);
3288 System.arraycopy(prevCtls, found+1, newCtls, found,
3289 prevCtls.length-found-1);
3290 return newCtls;
3291 }
3292
3293 private static Control[] cloneControls(Control[] ctls) {
3294 if (ctls == null) {
3295 return null;
3296 }
3297 Control[] copiedCtls = new Control[ctls.length];
3298 System.arraycopy(ctls, 0, copiedCtls, 0, ctls.length);
3299 return copiedCtls;
3300 }
3301
3302 // -------------------- Events ------------------------
3303 /*
3304 * Access to eventSupport need not be synchronized even though the
3305 * Connection thread can access it asynchronously. It is
3306 * impossible for a race condition to occur because
3307 * eventSupport.addNamingListener() must have been called before
3308 * the Connection thread can call back to this ctx.
3309 */
3310 public void addNamingListener(Name nm, int scope, NamingListener l)
3311 throws NamingException {
3312 addNamingListener(getTargetName(nm), scope, l);
3313 }
3314
3315 public void addNamingListener(String nm, int scope, NamingListener l)
3316 throws NamingException {
3317 if (eventSupport == null)
3318 eventSupport = new EventSupport(this);
3319 eventSupport.addNamingListener(getTargetName(new CompositeName(nm)),
3320 scope, l);
3321
3322 // If first time asking for unsol
3323 if (l instanceof UnsolicitedNotificationListener && !unsolicited) {
3324 addUnsolicited();
3325 }
3326 }
3327
3328 public void removeNamingListener(NamingListener l) throws NamingException {
3329 if (eventSupport == null)
3330 return; // no activity before, so just return
3331
3332 eventSupport.removeNamingListener(l);
3333
3334 // If removing an Unsol listener and it is the last one, let clnt know
3335 if (l instanceof UnsolicitedNotificationListener &&
3336 !eventSupport.hasUnsolicited()) {
3337 removeUnsolicited();
3338 }
3339 }
3340
3341 public void addNamingListener(String nm, String filter, SearchControls ctls,
3342 NamingListener l) throws NamingException {
3343 if (eventSupport == null)
3344 eventSupport = new EventSupport(this);
3345 eventSupport.addNamingListener(getTargetName(new CompositeName(nm)),
3346 filter, cloneSearchControls(ctls), l);
3347
3348 // If first time asking for unsol
3349 if (l instanceof UnsolicitedNotificationListener && !unsolicited) {
3350 addUnsolicited();
3351 }
3352 }
3353
3354 public void addNamingListener(Name nm, String filter, SearchControls ctls,
3355 NamingListener l) throws NamingException {
3356 addNamingListener(getTargetName(nm), filter, ctls, l);
3357 }
3358
3359 public void addNamingListener(Name nm, String filter, Object[] filterArgs,
3360 SearchControls ctls, NamingListener l) throws NamingException {
3361 addNamingListener(getTargetName(nm), filter, filterArgs, ctls, l);
3362 }
3363
3364 public void addNamingListener(String nm, String filterExpr, Object[] filterArgs,
3365 SearchControls ctls, NamingListener l) throws NamingException {
3366 String strfilter = SearchFilter.format(filterExpr, filterArgs);
3367 addNamingListener(getTargetName(new CompositeName(nm)), strfilter, ctls, l);
3368 }
3369
3370 public boolean targetMustExist() {
3371 return true;
3372 }
3373
3374 /**
3375 * Retrieves the target name for which the listener is registering.
3376 * If nm is a CompositeName, use its first and only component. It
3377 * cannot have more than one components because a target be outside of
3378 * this namespace. If nm is not a CompositeName, then treat it as a
3379 * compound name.
3380 * @param nm The non-null target name.
3381 */
3382 private static String getTargetName(Name nm) throws NamingException {
3383 if (nm instanceof CompositeName) {
3384 if (nm.size() > 1) {
3385 throw new InvalidNameException(
3386 "Target cannot span multiple namespaces: " + nm);
3387 } else if (nm.size() == 0) {
3388 return "";
3389 } else {
3390 return nm.get(0);
3391 }
3392 } else {
3393 // treat as compound name
3394 return nm.toString();
3395 }
3396 }
3397
3398 // ------------------ Unsolicited Notification ---------------
3399 // package private methods for handling unsolicited notification
3400
3401 /**
3402 * Registers this context with the underlying LdapClient.
3403 * When the underlying LdapClient receives an unsolicited notification,
3404 * it will invoke LdapCtx.fireUnsolicited() so that this context
3405 * can (using EventSupport) notified any registered listeners.
3406 * This method is called by EventSupport when an unsolicited listener
3407 * first registers with this context (should be called just once).
3408 * @see #removeUnsolicited
3409 * @see #fireUnsolicited
3410 */
3411 private void addUnsolicited() throws NamingException {
3412 if (debug) {
3413 System.out.println("LdapCtx.addUnsolicited: " + this);
3414 }
3415
3416 // addNamingListener must have created EventSupport already
3417 ensureOpen();
3418 synchronized (eventSupport) {
3419 clnt.addUnsolicited(this);
3420 unsolicited = true;
3421 }
3422 }
3423
3424 /**
3425 * Removes this context from registering interest in unsolicited
3426 * notifications from the underlying LdapClient. This method is called
3427 * under any one of the following conditions:
3428 * <ul>
3429 * <li>All unsolicited listeners have been removed. (see removingNamingListener)
3430 * <li>This context is closed.
3431 * <li>This context's underlying LdapClient changes.
3432 *</ul>
3433 * After this method has been called, this context will not pass
3434 * on any events related to unsolicited notifications to EventSupport and
3435 * and its listeners.
3436 */
3437
3438 private void removeUnsolicited() {
3439 if (debug) {
3440 System.out.println("LdapCtx.removeUnsolicited: " + unsolicited);
3441 }
3442 if (eventSupport == null) {
3443 return;
3444 }
3445
3446 // addNamingListener must have created EventSupport already
3447 synchronized(eventSupport) {
3448 if (unsolicited && clnt != null) {
3449 clnt.removeUnsolicited(this);
3450 }
3451 unsolicited = false;
3452 }
3453 }
3454
3455 /**
3456 * Uses EventSupport to fire an event related to an unsolicited notification.
3457 * Called by LdapClient when LdapClient receives an unsolicited notification.
3458 */
3459 void fireUnsolicited(Object obj) {
3460 if (debug) {
3461 System.out.println("LdapCtx.fireUnsolicited: " + obj);
3462 }
3463 // addNamingListener must have created EventSupport already
3464 synchronized(eventSupport) {
3465 if (unsolicited) {
3466 eventSupport.fireUnsolicited(obj);
3467
3468 if (obj instanceof NamingException) {
3469 unsolicited = false;
3470 // No need to notify clnt because clnt is the
3471 // only one that can fire a NamingException to
3472 // unsol listeners and it will handle its own cleanup
3473 }
3474 }
3475 }
3476 }
3477 }