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