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