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