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