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