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