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