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