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