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