/* * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.jndi.ldap; import javax.naming.*; import javax.naming.directory.*; import javax.naming.spi.*; import javax.naming.event.*; import javax.naming.ldap.*; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; import java.util.Locale; import java.util.Vector; import java.util.Hashtable; import java.util.List; import java.util.StringTokenizer; import java.util.Enumeration; import java.io.IOException; import java.io.OutputStream; import com.sun.jndi.toolkit.ctx.*; import com.sun.jndi.toolkit.dir.HierMemDirCtx; import com.sun.jndi.toolkit.dir.SearchFilter; import com.sun.jndi.ldap.ext.StartTlsResponseImpl; /** * The LDAP context implementation. * * Implementation is not thread-safe. Caller must sync as per JNDI spec. * Members that are used directly or indirectly by internal worker threads * (Connection, EventQueue, NamingEventNotifier) must be thread-safe. * Connection - calls LdapClient.processUnsolicited(), which in turn calls * LdapCtx.convertControls() and LdapCtx.fireUnsolicited(). * convertControls() - no sync; reads envprops and 'this' * fireUnsolicited() - sync on eventSupport for all references to 'unsolicited' * (even those in other methods); don't sync on LdapCtx in case caller * is already sync'ing on it - this would prevent Unsol events from firing * and the Connection thread to block (thus preventing any other data * from being read from the connection) * References to 'eventSupport' need not be sync'ed because these * methods can only be called after eventSupport has been set first * (via addNamingListener()). * EventQueue - no direct or indirect calls to LdapCtx * NamingEventNotifier - calls newInstance() to get instance for run() to use; * no sync needed for methods invoked on new instance; * * LdapAttribute links to LdapCtx in order to process getAttributeDefinition() * and getAttributeSyntaxDefinition() calls. It invokes LdapCtx.getSchema(), * which uses schemaTrees (a Hashtable - already sync). Potential conflict * of duplicating construction of tree for same subschemasubentry * but no inconsistency problems. * * NamingEnumerations link to LdapCtx for the following: * 1. increment/decrement enum count so that ctx doesn't close the * underlying connection * 2. LdapClient handle to get next batch of results * 3. Sets LdapCtx's response controls * 4. Process return code * 5. For narrowing response controls (using ctx's factories) * Since processing of NamingEnumeration by client is treated the same as method * invocation on LdapCtx, caller is responsible for locking. * * @author Vincent Ryan * @author Rosanna Lee */ final public class LdapCtx extends ComponentDirContext implements EventDirContext, LdapContext { /* * Used to store arguments to the search method. */ final static class SearchArgs { Name name; String filter; SearchControls cons; String[] reqAttrs; // those attributes originally requested SearchArgs(Name name, String filter, SearchControls cons, String[] ra) { this.name = name; this.filter = filter; this.cons = cons; this.reqAttrs = ra; } } private static final boolean debug = false; private static final boolean HARD_CLOSE = true; private static final boolean SOFT_CLOSE = false; // ----------------- Constants ----------------- public static final int DEFAULT_PORT = 389; public static final int DEFAULT_SSL_PORT = 636; public static final String DEFAULT_HOST = "localhost"; private static final boolean DEFAULT_DELETE_RDN = true; private static final boolean DEFAULT_TYPES_ONLY = false; private static final int DEFAULT_DEREF_ALIASES = 3; // always deref private static final int DEFAULT_LDAP_VERSION = LdapClient.LDAP_VERSION3_VERSION2; private static final int DEFAULT_BATCH_SIZE = 1; private static final int DEFAULT_REFERRAL_MODE = LdapClient.LDAP_REF_IGNORE; private static final char DEFAULT_REF_SEPARATOR = '#'; // Used by LdapPoolManager static final String DEFAULT_SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"; // use Sun's SSL private static final int DEFAULT_REFERRAL_LIMIT = 10; private static final String STARTTLS_REQ_OID = "1.3.6.1.4.1.1466.20037"; // schema operational and user attributes private static final String[] SCHEMA_ATTRIBUTES = { "objectClasses", "attributeTypes", "matchingRules", "ldapSyntaxes" }; // --------------- Environment property names ---------- // LDAP protocol version: "2", "3" private static final String VERSION = "java.naming.ldap.version"; // Binary-valued attributes. Space separated string of attribute names. private static final String BINARY_ATTRIBUTES = "java.naming.ldap.attributes.binary"; // Delete old RDN during modifyDN: "true", "false" private static final String DELETE_RDN = "java.naming.ldap.deleteRDN"; // De-reference aliases: "never", "searching", "finding", "always" private static final String DEREF_ALIASES = "java.naming.ldap.derefAliases"; // Return only attribute types (no values) private static final String TYPES_ONLY = "java.naming.ldap.typesOnly"; // Separator character for encoding Reference's RefAddrs; default is '#' private static final String REF_SEPARATOR = "java.naming.ldap.ref.separator"; // Socket factory private static final String SOCKET_FACTORY = "java.naming.ldap.factory.socket"; // Bind Controls (used by LdapReferralException) static final String BIND_CONTROLS = "java.naming.ldap.control.connect"; private static final String REFERRAL_LIMIT = "java.naming.ldap.referral.limit"; // trace BER (java.io.OutputStream) private static final String TRACE_BER = "com.sun.jndi.ldap.trace.ber"; // Get around Netscape Schema Bugs private static final String NETSCAPE_SCHEMA_BUG = "com.sun.jndi.ldap.netscape.schemaBugs"; // deprecated private static final String OLD_NETSCAPE_SCHEMA_BUG = "com.sun.naming.netscape.schemaBugs"; // for backward compatibility // Timeout for socket connect private static final String CONNECT_TIMEOUT = "com.sun.jndi.ldap.connect.timeout"; // Timeout for reading responses private static final String READ_TIMEOUT = "com.sun.jndi.ldap.read.timeout"; // Environment property for connection pooling private static final String ENABLE_POOL = "com.sun.jndi.ldap.connect.pool"; // Environment property for the domain name (derived from this context's DN) private static final String DOMAIN_NAME = "com.sun.jndi.ldap.domainname"; // Block until the first search reply is received private static final String WAIT_FOR_REPLY = "com.sun.jndi.ldap.search.waitForReply"; // Size of the queue of unprocessed search replies private static final String REPLY_QUEUE_SIZE = "com.sun.jndi.ldap.search.replyQueueSize"; // ----------------- Fields that don't change ----------------------- private static final NameParser parser = new LdapNameParser(); // controls that Provider needs private static final ControlFactory myResponseControlFactory = new DefaultResponseControlFactory(); private static final Control manageReferralControl = new ManageReferralControl(false); private static final HierMemDirCtx EMPTY_SCHEMA = new HierMemDirCtx(); static { EMPTY_SCHEMA.setReadOnly( new SchemaViolationException("Cannot update schema object")); } // ------------ Package private instance variables ---------------- // Cannot be private; used by enums // ------- Inherited by derived context instances int port_number; // port number of server String hostname = null; // host name of server (no brackets // for IPv6 literals) LdapClient clnt = null; // connection handle private boolean reconnect = false; // indicates that re-connect requested Hashtable envprops = null; // environment properties of context int handleReferrals = DEFAULT_REFERRAL_MODE; // how referral is handled boolean hasLdapsScheme = false; // true if the context was created // using an LDAPS URL. // ------- Not inherited by derived context instances String currentDN; // DN of this context Name currentParsedDN; // DN of this context Vector respCtls = null; // Response controls read Control[] reqCtls = null; // Controls to be sent with each request // ------------- Private instance variables ------------------------ // ------- Inherited by derived context instances private OutputStream trace = null; // output stream for BER debug output private boolean netscapeSchemaBug = false; // workaround private Control[] bindCtls = null; // Controls to be sent with LDAP "bind" private int referralHopLimit = DEFAULT_REFERRAL_LIMIT; // max referral private Hashtable schemaTrees = null; // schema root of this context private int batchSize = DEFAULT_BATCH_SIZE; // batch size for search results private boolean deleteRDN = DEFAULT_DELETE_RDN; // delete the old RDN when modifying DN private boolean typesOnly = DEFAULT_TYPES_ONLY; // return attribute types (no values) private int derefAliases = DEFAULT_DEREF_ALIASES;// de-reference alias entries during searching private char addrEncodingSeparator = DEFAULT_REF_SEPARATOR; // encoding RefAddr private Hashtable binaryAttrs = null; // attr values returned as byte[] private int connectTimeout = -1; // no timeout value private int readTimeout = -1; // no timeout value private boolean waitForReply = true; // wait for search response private int replyQueueSize = -1; // unlimited queue size private boolean useSsl = false; // true if SSL protocol is active private boolean useDefaultPortNumber = false; // no port number was supplied // ------- Not inherited by derived context instances // True if this context was created by another LdapCtx. private boolean parentIsLdapCtx = false; // see composeName() private int hopCount = 1; // current referral hop count private String url = null; // URL of context; see getURL() private EventSupport eventSupport; // Event support helper for this ctx private boolean unsolicited = false; // if there unsolicited listeners private boolean sharable = true; // can share connection with other ctx // -------------- Constructors ----------------------------------- @SuppressWarnings("unchecked") public LdapCtx(String dn, String host, int port_number, Hashtable props, boolean useSsl) throws NamingException { this.useSsl = this.hasLdapsScheme = useSsl; if (props != null) { envprops = (Hashtable) props.clone(); // SSL env prop overrides the useSsl argument if ("ssl".equals(envprops.get(Context.SECURITY_PROTOCOL))) { this.useSsl = true; } // %%% These are only examined when the context is created // %%% because they are only for debugging or workaround purposes. trace = (OutputStream)envprops.get(TRACE_BER); if (props.get(NETSCAPE_SCHEMA_BUG) != null || props.get(OLD_NETSCAPE_SCHEMA_BUG) != null) { netscapeSchemaBug = true; } } currentDN = (dn != null) ? dn : ""; currentParsedDN = parser.parse(currentDN); hostname = (host != null && host.length() > 0) ? host : DEFAULT_HOST; if (hostname.charAt(0) == '[') { hostname = hostname.substring(1, hostname.length() - 1); } if (port_number > 0) { this.port_number = port_number; } else { this.port_number = this.useSsl ? DEFAULT_SSL_PORT : DEFAULT_PORT; this.useDefaultPortNumber = true; } schemaTrees = new Hashtable<>(11, 0.75f); initEnv(); try { connect(false); } catch (NamingException e) { try { close(); } catch (Exception e2) { // Nothing } throw e; } } LdapCtx(LdapCtx existing, String newDN) throws NamingException { useSsl = existing.useSsl; hasLdapsScheme = existing.hasLdapsScheme; useDefaultPortNumber = existing.useDefaultPortNumber; hostname = existing.hostname; port_number = existing.port_number; currentDN = newDN; if (existing.currentDN == currentDN) { currentParsedDN = existing.currentParsedDN; } else { currentParsedDN = parser.parse(currentDN); } envprops = existing.envprops; schemaTrees = existing.schemaTrees; clnt = existing.clnt; clnt.incRefCount(); parentIsLdapCtx = ((newDN == null || newDN.equals(existing.currentDN)) ? existing.parentIsLdapCtx : true); // inherit these debugging/workaround flags trace = existing.trace; netscapeSchemaBug = existing.netscapeSchemaBug; initEnv(); } public LdapContext newInstance(Control[] reqCtls) throws NamingException { LdapContext clone = new LdapCtx(this, currentDN); // Connection controls are inherited from environment // Set clone's request controls // setRequestControls() will clone reqCtls clone.setRequestControls(reqCtls); return clone; } // --------------- Namespace Updates --------------------- // -- bind/rebind/unbind // -- rename // -- createSubcontext/destroySubcontext protected void c_bind(Name name, Object obj, Continuation cont) throws NamingException { c_bind(name, obj, null, cont); } /* * attrs == null * if obj is DirContext, attrs = obj.getAttributes() * if attrs == null && obj == null * disallow (cannot determine objectclass to use) * if obj == null * just create entry using attrs * else * objAttrs = create attributes for representing obj * attrs += objAttrs * create entry using attrs */ protected void c_bind(Name name, Object obj, Attributes attrs, Continuation cont) throws NamingException { cont.setError(this, name); Attributes inputAttrs = attrs; // Attributes supplied by caller try { ensureOpen(); if (obj == null) { if (attrs == null) { throw new IllegalArgumentException( "cannot bind null object with no attributes"); } } else { attrs = Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs, false, name, this, envprops); // not cloned } String newDN = fullyQualifiedName(name); attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs); LdapEntry entry = new LdapEntry(newDN, attrs); LdapResult answer = clnt.add(entry, reqCtls); respCtls = answer.resControls; // retrieve response controls if (answer.status != LdapClient.LDAP_SUCCESS) { processReturnCode(answer, name); } } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { refCtx.bind(name, obj, inputAttrs); return; } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw cont.fillInException(e2); } catch (NamingException e) { throw cont.fillInException(e); } } protected void c_rebind(Name name, Object obj, Continuation cont) throws NamingException { c_rebind(name, obj, null, cont); } /* * attrs == null * if obj is DirContext, attrs = obj.getAttributes(). * if attrs == null * leave any existing attributes alone * (set attrs = {objectclass=top} if object doesn't exist) * else * replace all existing attributes with attrs * if obj == null * just create entry using attrs * else * objAttrs = create attributes for representing obj * attrs += objAttrs * create entry using attrs */ protected void c_rebind(Name name, Object obj, Attributes attrs, Continuation cont) throws NamingException { cont.setError(this, name); Attributes inputAttrs = attrs; try { Attributes origAttrs = null; // Check if name is bound try { origAttrs = c_getAttributes(name, null, cont); } catch (NameNotFoundException e) {} // Name not bound, just add it if (origAttrs == null) { c_bind(name, obj, attrs, cont); return; } // there's an object there already, need to figure out // what to do about its attributes if (attrs == null && obj instanceof DirContext) { attrs = ((DirContext)obj).getAttributes(""); } Attributes keepAttrs = (Attributes)origAttrs.clone(); if (attrs == null) { // we're not changing any attrs, leave old attributes alone // Remove Java-related object classes from objectclass attribute Attribute origObjectClass = origAttrs.get(Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS]); if (origObjectClass != null) { // clone so that keepAttrs is not affected origObjectClass = (Attribute)origObjectClass.clone(); for (int i = 0; i < Obj.JAVA_OBJECT_CLASSES.length; i++) { origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES_LOWER[i]); origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES[i]); } // update; origAttrs.put(origObjectClass); } // remove all Java-related attributes except objectclass for (int i = 1; i < Obj.JAVA_ATTRIBUTES.length; i++) { origAttrs.remove(Obj.JAVA_ATTRIBUTES[i]); } attrs = origAttrs; } if (obj != null) { attrs = Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs, inputAttrs != attrs, name, this, envprops); } String newDN = fullyQualifiedName(name); // remove entry LdapResult answer = clnt.delete(newDN, reqCtls); respCtls = answer.resControls; // retrieve response controls if (answer.status != LdapClient.LDAP_SUCCESS) { processReturnCode(answer, name); return; } Exception addEx = null; try { attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs); // add it back using updated attrs LdapEntry entry = new LdapEntry(newDN, attrs); answer = clnt.add(entry, reqCtls); if (answer.resControls != null) { respCtls = appendVector(respCtls, answer.resControls); } } catch (NamingException | IOException ae) { addEx = ae; } if ((addEx != null && !(addEx instanceof LdapReferralException)) || answer.status != LdapClient.LDAP_SUCCESS) { // Attempt to restore old entry LdapResult answer2 = clnt.add(new LdapEntry(newDN, keepAttrs), reqCtls); if (answer2.resControls != null) { respCtls = appendVector(respCtls, answer2.resControls); } if (addEx == null) { processReturnCode(answer, name); } } // Rethrow exception if (addEx instanceof NamingException) { throw (NamingException)addEx; } else if (addEx instanceof IOException) { throw (IOException)addEx; } } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { refCtx.rebind(name, obj, inputAttrs); return; } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw cont.fillInException(e2); } catch (NamingException e) { throw cont.fillInException(e); } } protected void c_unbind(Name name, Continuation cont) throws NamingException { cont.setError(this, name); try { ensureOpen(); String fname = fullyQualifiedName(name); LdapResult answer = clnt.delete(fname, reqCtls); respCtls = answer.resControls; // retrieve response controls adjustDeleteStatus(fname, answer); if (answer.status != LdapClient.LDAP_SUCCESS) { processReturnCode(answer, name); } } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { refCtx.unbind(name); return; } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw cont.fillInException(e2); } catch (NamingException e) { throw cont.fillInException(e); } } protected void c_rename(Name oldName, Name newName, Continuation cont) throws NamingException { Name oldParsed, newParsed; Name oldParent, newParent; String newRDN = null; String newSuperior = null; // assert (oldName instanceOf CompositeName); cont.setError(this, oldName); try { ensureOpen(); // permit oldName to be empty (for processing referral contexts) if (oldName.isEmpty()) { oldParent = parser.parse(""); } else { oldParsed = parser.parse(oldName.get(0)); // extract DN & parse oldParent = oldParsed.getPrefix(oldParsed.size() - 1); } if (newName instanceof CompositeName) { newParsed = parser.parse(newName.get(0)); // extract DN & parse } else { newParsed = newName; // CompoundName/LdapName is already parsed } newParent = newParsed.getPrefix(newParsed.size() - 1); if(!oldParent.equals(newParent)) { if (!clnt.isLdapv3) { throw new InvalidNameException( "LDAPv2 doesn't support changing " + "the parent as a result of a rename"); } else { newSuperior = fullyQualifiedName(newParent.toString()); } } newRDN = newParsed.get(newParsed.size() - 1); LdapResult answer = clnt.moddn(fullyQualifiedName(oldName), newRDN, deleteRDN, newSuperior, reqCtls); respCtls = answer.resControls; // retrieve response controls if (answer.status != LdapClient.LDAP_SUCCESS) { processReturnCode(answer, oldName); } } catch (LdapReferralException e) { // Record the new RDN (for use after the referral is followed). e.setNewRdn(newRDN); // Cannot continue when a referral has been received and a // newSuperior name was supplied (because the newSuperior is // relative to a naming context BEFORE the referral is followed). if (newSuperior != null) { PartialResultException pre = new PartialResultException( "Cannot continue referral processing when newSuperior is " + "nonempty: " + newSuperior); pre.setRootCause(cont.fillInException(e)); throw cont.fillInException(pre); } if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { refCtx.rename(oldName, newName); return; } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw cont.fillInException(e2); } catch (NamingException e) { throw cont.fillInException(e); } } protected Context c_createSubcontext(Name name, Continuation cont) throws NamingException { return c_createSubcontext(name, null, cont); } protected DirContext c_createSubcontext(Name name, Attributes attrs, Continuation cont) throws NamingException { cont.setError(this, name); Attributes inputAttrs = attrs; try { ensureOpen(); if (attrs == null) { // add structural objectclass; name needs to have "cn" Attribute oc = new BasicAttribute( Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS], Obj.JAVA_OBJECT_CLASSES[Obj.STRUCTURAL]); oc.add("top"); attrs = new BasicAttributes(true); // case ignore attrs.put(oc); } String newDN = fullyQualifiedName(name); attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs); LdapEntry entry = new LdapEntry(newDN, attrs); LdapResult answer = clnt.add(entry, reqCtls); respCtls = answer.resControls; // retrieve response controls if (answer.status != LdapClient.LDAP_SUCCESS) { processReturnCode(answer, name); return null; } // creation successful, get back live object return new LdapCtx(this, newDN); } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { return refCtx.createSubcontext(name, inputAttrs); } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw cont.fillInException(e2); } catch (NamingException e) { throw cont.fillInException(e); } } protected void c_destroySubcontext(Name name, Continuation cont) throws NamingException { cont.setError(this, name); try { ensureOpen(); String fname = fullyQualifiedName(name); LdapResult answer = clnt.delete(fname, reqCtls); respCtls = answer.resControls; // retrieve response controls adjustDeleteStatus(fname, answer); if (answer.status != LdapClient.LDAP_SUCCESS) { processReturnCode(answer, name); } } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { refCtx.destroySubcontext(name); return; } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw cont.fillInException(e2); } catch (NamingException e) { throw cont.fillInException(e); } } /** * Adds attributes from RDN to attrs if not already present. * Note that if attrs already contains an attribute by the same name, * or if the distinguished name is empty, then leave attrs unchanged. * * @param dn The non-null DN of the entry to add * @param attrs The non-null attributes of entry to add * @param directUpdate Whether attrs can be updated directly * @return Non-null attributes with attributes from the RDN added */ private static Attributes addRdnAttributes(String dn, Attributes attrs, boolean directUpdate) throws NamingException { // Handle the empty name if (dn.equals("")) { return attrs; } // Parse string name into list of RDNs List rdnList = (new LdapName(dn)).getRdns(); // Get leaf RDN Rdn rdn = rdnList.get(rdnList.size() - 1); Attributes nameAttrs = rdn.toAttributes(); // Add attributes of RDN to attrs if not already there NamingEnumeration enum_ = nameAttrs.getAll(); Attribute nameAttr; while (enum_.hasMore()) { nameAttr = enum_.next(); // If attrs already has the attribute, don't change or add to it if (attrs.get(nameAttr.getID()) == null) { /** * When attrs.isCaseIgnored() is false, attrs.get() will * return null when the case mis-matches for otherwise * equal attrIDs. * As the attrIDs' case is irrelevant for LDAP, ignore * the case of attrIDs even when attrs.isCaseIgnored() is * false. This is done by explicitly comparing the elements in * the enumeration of IDs with their case ignored. */ if (!attrs.isCaseIgnored() && containsIgnoreCase(attrs.getIDs(), nameAttr.getID())) { continue; } if (!directUpdate) { attrs = (Attributes)attrs.clone(); directUpdate = true; } attrs.put(nameAttr); } } return attrs; } private static boolean containsIgnoreCase(NamingEnumeration enumStr, String str) throws NamingException { String strEntry; while (enumStr.hasMore()) { strEntry = enumStr.next(); if (strEntry.equalsIgnoreCase(str)) { return true; } } return false; } private void adjustDeleteStatus(String fname, LdapResult answer) { if (answer.status == LdapClient.LDAP_NO_SUCH_OBJECT && answer.matchedDN != null) { try { // %%% RL: are there any implications for referrals? Name orig = parser.parse(fname); Name matched = parser.parse(answer.matchedDN); if ((orig.size() - matched.size()) == 1) answer.status = LdapClient.LDAP_SUCCESS; } catch (NamingException e) {} } } /* * Append the second Vector onto the first Vector * (v2 must be non-null) */ private static Vector appendVector(Vector v1, Vector v2) { if (v1 == null) { v1 = v2; } else { for (int i = 0; i < v2.size(); i++) { v1.addElement(v2.elementAt(i)); } } return v1; } // ------------- Lookups and Browsing ------------------------- // lookup/lookupLink // list/listBindings protected Object c_lookupLink(Name name, Continuation cont) throws NamingException { return c_lookup(name, cont); } protected Object c_lookup(Name name, Continuation cont) throws NamingException { cont.setError(this, name); Object obj = null; Attributes attrs; try { SearchControls cons = new SearchControls(); cons.setSearchScope(SearchControls.OBJECT_SCOPE); cons.setReturningAttributes(null); // ask for all attributes cons.setReturningObjFlag(true); // need values to construct obj LdapResult answer = doSearchOnce(name, "(objectClass=*)", cons, true); respCtls = answer.resControls; // retrieve response controls // should get back 1 SearchResponse and 1 SearchResult if (answer.status != LdapClient.LDAP_SUCCESS) { processReturnCode(answer, name); } if (answer.entries == null || answer.entries.size() != 1) { // found it but got no attributes attrs = new BasicAttributes(LdapClient.caseIgnore); } else { LdapEntry entry = answer.entries.elementAt(0); attrs = entry.attributes; Vector entryCtls = entry.respCtls; // retrieve entry controls if (entryCtls != null) { appendVector(respCtls, entryCtls); // concatenate controls } } if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) { // serialized object or object reference obj = Obj.decodeObject(attrs); } if (obj == null) { obj = new LdapCtx(this, fullyQualifiedName(name)); } } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { return refCtx.lookup(name); } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (NamingException e) { throw cont.fillInException(e); } try { return DirectoryManager.getObjectInstance(obj, name, this, envprops, attrs); } catch (NamingException e) { throw cont.fillInException(e); } catch (Exception e) { NamingException e2 = new NamingException( "problem generating object using object factory"); e2.setRootCause(e); throw cont.fillInException(e2); } } protected NamingEnumeration c_list(Name name, Continuation cont) throws NamingException { SearchControls cons = new SearchControls(); String[] classAttrs = new String[2]; classAttrs[0] = Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS]; classAttrs[1] = Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]; cons.setReturningAttributes(classAttrs); // set this flag to override the typesOnly flag cons.setReturningObjFlag(true); cont.setError(this, name); LdapResult answer = null; try { answer = doSearch(name, "(objectClass=*)", cons, true, true); // list result may contain continuation references if ((answer.status != LdapClient.LDAP_SUCCESS) || (answer.referrals != null)) { processReturnCode(answer, name); } return new LdapNamingEnumeration(this, answer, name, cont); } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { return refCtx.list(name); } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (LimitExceededException e) { LdapNamingEnumeration res = new LdapNamingEnumeration(this, answer, name, cont); res.setNamingException( (LimitExceededException)cont.fillInException(e)); return res; } catch (PartialResultException e) { LdapNamingEnumeration res = new LdapNamingEnumeration(this, answer, name, cont); res.setNamingException( (PartialResultException)cont.fillInException(e)); return res; } catch (NamingException e) { throw cont.fillInException(e); } } protected NamingEnumeration c_listBindings(Name name, Continuation cont) throws NamingException { SearchControls cons = new SearchControls(); cons.setReturningAttributes(null); // ask for all attributes cons.setReturningObjFlag(true); // need values to construct obj cont.setError(this, name); LdapResult answer = null; try { answer = doSearch(name, "(objectClass=*)", cons, true, true); // listBindings result may contain continuation references if ((answer.status != LdapClient.LDAP_SUCCESS) || (answer.referrals != null)) { processReturnCode(answer, name); } return new LdapBindingEnumeration(this, answer, name, cont); } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { @SuppressWarnings("unchecked") LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { return refCtx.listBindings(name); } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (LimitExceededException e) { LdapBindingEnumeration res = new LdapBindingEnumeration(this, answer, name, cont); res.setNamingException(cont.fillInException(e)); return res; } catch (PartialResultException e) { LdapBindingEnumeration res = new LdapBindingEnumeration(this, answer, name, cont); res.setNamingException(cont.fillInException(e)); return res; } catch (NamingException e) { throw cont.fillInException(e); } } // --------------- Name-related Methods ----------------------- // -- getNameParser/getNameInNamespace/composeName protected NameParser c_getNameParser(Name name, Continuation cont) throws NamingException { // ignore name, always return same parser cont.setSuccess(); return parser; } public String getNameInNamespace() { return currentDN; } public Name composeName(Name name, Name prefix) throws NamingException { Name result; // Handle compound names. A pair of LdapNames is an easy case. if ((name instanceof LdapName) && (prefix instanceof LdapName)) { result = (Name)(prefix.clone()); result.addAll(name); return new CompositeName().add(result.toString()); } if (!(name instanceof CompositeName)) { name = new CompositeName().add(name.toString()); } if (!(prefix instanceof CompositeName)) { prefix = new CompositeName().add(prefix.toString()); } int prefixLast = prefix.size() - 1; if (name.isEmpty() || prefix.isEmpty() || name.get(0).equals("") || prefix.get(prefixLast).equals("")) { return super.composeName(name, prefix); } result = (Name)(prefix.clone()); result.addAll(name); if (parentIsLdapCtx) { String ldapComp = concatNames(result.get(prefixLast + 1), result.get(prefixLast)); result.remove(prefixLast + 1); result.remove(prefixLast); result.add(prefixLast, ldapComp); } return result; } private String fullyQualifiedName(Name rel) { return rel.isEmpty() ? currentDN : fullyQualifiedName(rel.get(0)); } private String fullyQualifiedName(String rel) { return (concatNames(rel, currentDN)); } // used by LdapSearchEnumeration private static String concatNames(String lesser, String greater) { if (lesser == null || lesser.equals("")) { return greater; } else if (greater == null || greater.equals("")) { return lesser; } else { return (lesser + "," + greater); } } // --------------- Reading and Updating Attributes // getAttributes/modifyAttributes protected Attributes c_getAttributes(Name name, String[] attrIds, Continuation cont) throws NamingException { cont.setError(this, name); SearchControls cons = new SearchControls(); cons.setSearchScope(SearchControls.OBJECT_SCOPE); cons.setReturningAttributes(attrIds); try { LdapResult answer = doSearchOnce(name, "(objectClass=*)", cons, true); respCtls = answer.resControls; // retrieve response controls if (answer.status != LdapClient.LDAP_SUCCESS) { processReturnCode(answer, name); } if (answer.entries == null || answer.entries.size() != 1) { return new BasicAttributes(LdapClient.caseIgnore); } // get attributes from result LdapEntry entry = answer.entries.elementAt(0); Vector entryCtls = entry.respCtls; // retrieve entry controls if (entryCtls != null) { appendVector(respCtls, entryCtls); // concatenate controls } // do this so attributes can find their schema setParents(entry.attributes, (Name) name.clone()); return (entry.attributes); } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { return refCtx.getAttributes(name, attrIds); } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (NamingException e) { throw cont.fillInException(e); } } protected void c_modifyAttributes(Name name, int mod_op, Attributes attrs, Continuation cont) throws NamingException { cont.setError(this, name); try { ensureOpen(); if (attrs == null || attrs.size() == 0) { return; // nothing to do } String newDN = fullyQualifiedName(name); int jmod_op = convertToLdapModCode(mod_op); // construct mod list int[] jmods = new int[attrs.size()]; Attribute[] jattrs = new Attribute[attrs.size()]; NamingEnumeration ae = attrs.getAll(); for(int i = 0; i < jmods.length && ae.hasMore(); i++) { jmods[i] = jmod_op; jattrs[i] = ae.next(); } LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls); respCtls = answer.resControls; // retrieve response controls if (answer.status != LdapClient.LDAP_SUCCESS) { processReturnCode(answer, name); return; } } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { refCtx.modifyAttributes(name, mod_op, attrs); return; } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw cont.fillInException(e2); } catch (NamingException e) { throw cont.fillInException(e); } } protected void c_modifyAttributes(Name name, ModificationItem[] mods, Continuation cont) throws NamingException { cont.setError(this, name); try { ensureOpen(); if (mods == null || mods.length == 0) { return; // nothing to do } String newDN = fullyQualifiedName(name); // construct mod list int[] jmods = new int[mods.length]; Attribute[] jattrs = new Attribute[mods.length]; ModificationItem mod; for (int i = 0; i < jmods.length; i++) { mod = mods[i]; jmods[i] = convertToLdapModCode(mod.getModificationOp()); jattrs[i] = mod.getAttribute(); } LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls); respCtls = answer.resControls; // retrieve response controls if (answer.status != LdapClient.LDAP_SUCCESS) { processReturnCode(answer, name); } } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { refCtx.modifyAttributes(name, mods); return; } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw cont.fillInException(e2); } catch (NamingException e) { throw cont.fillInException(e); } } private static int convertToLdapModCode(int mod_op) { switch (mod_op) { case DirContext.ADD_ATTRIBUTE: return(LdapClient.ADD); case DirContext.REPLACE_ATTRIBUTE: return (LdapClient.REPLACE); case DirContext.REMOVE_ATTRIBUTE: return (LdapClient.DELETE); default: throw new IllegalArgumentException("Invalid modification code"); } } // ------------------- Schema ----------------------- protected DirContext c_getSchema(Name name, Continuation cont) throws NamingException { cont.setError(this, name); try { return getSchemaTree(name); } catch (NamingException e) { throw cont.fillInException(e); } } protected DirContext c_getSchemaClassDefinition(Name name, Continuation cont) throws NamingException { cont.setError(this, name); try { // retrieve the objectClass attribute from LDAP Attribute objectClassAttr = c_getAttributes(name, new String[]{"objectclass"}, cont).get("objectclass"); if (objectClassAttr == null || objectClassAttr.size() == 0) { return EMPTY_SCHEMA; } // retrieve the root of the ObjectClass schema tree Context ocSchema = (Context) c_getSchema(name, cont).lookup( LdapSchemaParser.OBJECTCLASS_DEFINITION_NAME); // create a context to hold the schema objects representing the object // classes HierMemDirCtx objectClassCtx = new HierMemDirCtx(); DirContext objectClassDef; String objectClassName; for (Enumeration objectClasses = objectClassAttr.getAll(); objectClasses.hasMoreElements(); ) { objectClassName = (String)objectClasses.nextElement(); // %%% Should we fail if not found, or just continue? objectClassDef = (DirContext)ocSchema.lookup(objectClassName); objectClassCtx.bind(objectClassName, objectClassDef); } // Make context read-only objectClassCtx.setReadOnly( new SchemaViolationException("Cannot update schema object")); return (DirContext)objectClassCtx; } catch (NamingException e) { throw cont.fillInException(e); } } /* * getSchemaTree first looks to see if we have already built a * schema tree for the given entry. If not, it builds a new one and * stores it in our private hash table */ private DirContext getSchemaTree(Name name) throws NamingException { String subschemasubentry = getSchemaEntry(name, true); DirContext schemaTree = schemaTrees.get(subschemasubentry); if(schemaTree==null) { if(debug){System.err.println("LdapCtx: building new schema tree " + this);} schemaTree = buildSchemaTree(subschemasubentry); schemaTrees.put(subschemasubentry, schemaTree); } return schemaTree; } /* * buildSchemaTree builds the schema tree corresponding to the * given subschemasubentree */ private DirContext buildSchemaTree(String subschemasubentry) throws NamingException { // get the schema entry itself // DO ask for return object here because we need it to // create context. Since asking for all attrs, we won't // be transmitting any specific attrIDs (like Java-specific ones). SearchControls constraints = new SearchControls(SearchControls.OBJECT_SCOPE, 0, 0, /* count and time limits */ SCHEMA_ATTRIBUTES /* return schema attrs */, true /* return obj */, false /*deref link */ ); Name sse = (new CompositeName()).add(subschemasubentry); NamingEnumeration results = searchAux(sse, "(objectClass=subschema)", constraints, false, true, new Continuation()); if(!results.hasMore()) { throw new OperationNotSupportedException( "Cannot get read subschemasubentry: " + subschemasubentry); } SearchResult result = results.next(); results.close(); Object obj = result.getObject(); if(!(obj instanceof LdapCtx)) { throw new NamingException( "Cannot get schema object as DirContext: " + subschemasubentry); } return LdapSchemaCtx.createSchemaTree(envprops, subschemasubentry, (LdapCtx)obj /* schema entry */, result.getAttributes() /* schema attributes */, netscapeSchemaBug); } /* * getSchemaEntree returns the DN of the subschemasubentree for the * given entree. It first looks to see if the given entry has * a subschema different from that of the root DIT (by looking for * a "subschemasubentry" attribute). If it doesn't find one, it returns * the one for the root of the DIT (by looking for the root's * "subschemasubentry" attribute). * * This function is called regardless of the server's version, since * an administrator may have setup the server to support client schema * queries. If this function tries a search on a v2 server that * doesn't support schema, one of these two things will happen: * 1) It will get an exception when querying the root DSE * 2) It will not find a subschemasubentry on the root DSE * If either of these things occur and the server is not v3, we * throw OperationNotSupported. * * the relative flag tells whether the given name is relative to this * context. */ private String getSchemaEntry(Name name, boolean relative) throws NamingException { // Asks for operational attribute "subschemasubentry" SearchControls constraints = new SearchControls(SearchControls.OBJECT_SCOPE, 0, 0, /* count and time limits */ new String[]{"subschemasubentry"} /* attr to return */, false /* returning obj */, false /* deref link */); NamingEnumeration results; try { results = searchAux(name, "objectclass=*", constraints, relative, true, new Continuation()); } catch (NamingException ne) { if (!clnt.isLdapv3 && currentDN.length() == 0 && name.isEmpty()) { // we got an error looking for a root entry on an ldapv2 // server. The server must not support schema. throw new OperationNotSupportedException( "Cannot get schema information from server"); } else { throw ne; } } if (!results.hasMoreElements()) { throw new ConfigurationException( "Requesting schema of nonexistent entry: " + name); } SearchResult result = results.next(); results.close(); Attribute schemaEntryAttr = result.getAttributes().get("subschemasubentry"); //System.err.println("schema entry attrs: " + schemaEntryAttr); if (schemaEntryAttr == null || schemaEntryAttr.size() < 0) { if (currentDN.length() == 0 && name.isEmpty()) { // the server doesn't have a subschemasubentry in its root DSE. // therefore, it doesn't support schema. throw new OperationNotSupportedException( "Cannot read subschemasubentry of root DSE"); } else { return getSchemaEntry(new CompositeName(), false); } } return (String)(schemaEntryAttr.get()); // return schema entry name } // package-private; used by search enum. // Set attributes to point to this context in case some one // asked for their schema void setParents(Attributes attrs, Name name) throws NamingException { NamingEnumeration ae = attrs.getAll(); while(ae.hasMore()) { ((LdapAttribute) ae.next()).setParent(this, name); } } /* * Returns the URL associated with this context; used by LdapAttribute * after deserialization to get pointer to this context. */ String getURL() { if (url == null) { url = LdapURL.toUrlString(hostname, port_number, currentDN, hasLdapsScheme); } return url; } // --------------------- Searches ----------------------------- protected NamingEnumeration c_search(Name name, Attributes matchingAttributes, Continuation cont) throws NamingException { return c_search(name, matchingAttributes, null, cont); } protected NamingEnumeration c_search(Name name, Attributes matchingAttributes, String[] attributesToReturn, Continuation cont) throws NamingException { SearchControls cons = new SearchControls(); cons.setReturningAttributes(attributesToReturn); String filter; try { filter = SearchFilter.format(matchingAttributes); } catch (NamingException e) { cont.setError(this, name); throw cont.fillInException(e); } return c_search(name, filter, cons, cont); } protected NamingEnumeration c_search(Name name, String filter, SearchControls cons, Continuation cont) throws NamingException { return searchAux(name, filter, cloneSearchControls(cons), true, waitForReply, cont); } protected NamingEnumeration c_search(Name name, String filterExpr, Object[] filterArgs, SearchControls cons, Continuation cont) throws NamingException { String strfilter; try { strfilter = SearchFilter.format(filterExpr, filterArgs); } catch (NamingException e) { cont.setError(this, name); throw cont.fillInException(e); } return c_search(name, strfilter, cons, cont); } // Used by NamingNotifier NamingEnumeration searchAux(Name name, String filter, SearchControls cons, boolean relative, boolean waitForReply, Continuation cont) throws NamingException { LdapResult answer = null; String[] tokens = new String[2]; // stores ldap compare op. values String[] reqAttrs; // remember what was asked if (cons == null) { cons = new SearchControls(); } reqAttrs = cons.getReturningAttributes(); // if objects are requested then request the Java attributes too // so that the objects can be constructed if (cons.getReturningObjFlag()) { if (reqAttrs != null) { // check for presence of "*" (user attributes wildcard) boolean hasWildcard = false; for (int i = reqAttrs.length - 1; i >= 0; i--) { if (reqAttrs[i].equals("*")) { hasWildcard = true; break; } } if (! hasWildcard) { String[] totalAttrs = new String[reqAttrs.length +Obj.JAVA_ATTRIBUTES.length]; System.arraycopy(reqAttrs, 0, totalAttrs, 0, reqAttrs.length); System.arraycopy(Obj.JAVA_ATTRIBUTES, 0, totalAttrs, reqAttrs.length, Obj.JAVA_ATTRIBUTES.length); cons.setReturningAttributes(totalAttrs); } } } LdapCtx.SearchArgs args = new LdapCtx.SearchArgs(name, filter, cons, reqAttrs); cont.setError(this, name); try { // see if this can be done as a compare, otherwise do a search if (searchToCompare(filter, cons, tokens)){ //System.err.println("compare triggered"); answer = compare(name, tokens[0], tokens[1]); if (! (answer.compareToSearchResult(fullyQualifiedName(name)))){ processReturnCode(answer, name); } } else { answer = doSearch(name, filter, cons, relative, waitForReply); // search result may contain referrals processReturnCode(answer, name); } return new LdapSearchEnumeration(this, answer, fullyQualifiedName(name), args, cont); } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw cont.fillInException(e); // process the referrals sequentially while (true) { @SuppressWarnings("unchecked") LdapReferralContext refCtx = (LdapReferralContext) e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { return refCtx.search(name, filter, cons); } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (LimitExceededException e) { LdapSearchEnumeration res = new LdapSearchEnumeration(this, answer, fullyQualifiedName(name), args, cont); res.setNamingException(e); return res; } catch (PartialResultException e) { LdapSearchEnumeration res = new LdapSearchEnumeration(this, answer, fullyQualifiedName(name), args, cont); res.setNamingException(e); return res; } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw cont.fillInException(e2); } catch (NamingException e) { throw cont.fillInException(e); } } LdapResult getSearchReply(LdapClient eClnt, LdapResult res) throws NamingException { // ensureOpen() won't work here because // session was associated with previous connection // %%% RL: we can actually allow the enumeration to continue // using the old handle but other weird things might happen // when we hit a referral if (clnt != eClnt) { throw new CommunicationException( "Context's connection changed; unable to continue enumeration"); } try { return eClnt.getSearchReply(batchSize, res, binaryAttrs); } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw e2; } } // Perform a search. Expect 1 SearchResultEntry and the SearchResultDone. private LdapResult doSearchOnce(Name name, String filter, SearchControls cons, boolean relative) throws NamingException { int savedBatchSize = batchSize; batchSize = 2; // 2 protocol elements LdapResult answer = doSearch(name, filter, cons, relative, true); batchSize = savedBatchSize; return answer; } private LdapResult doSearch(Name name, String filter, SearchControls cons, boolean relative, boolean waitForReply) throws NamingException { ensureOpen(); try { int scope; switch (cons.getSearchScope()) { case SearchControls.OBJECT_SCOPE: scope = LdapClient.SCOPE_BASE_OBJECT; break; default: case SearchControls.ONELEVEL_SCOPE: scope = LdapClient.SCOPE_ONE_LEVEL; break; case SearchControls.SUBTREE_SCOPE: scope = LdapClient.SCOPE_SUBTREE; break; } // If cons.getReturningObjFlag() then caller should already // have make sure to request the appropriate attrs String[] retattrs = cons.getReturningAttributes(); if (retattrs != null && retattrs.length == 0) { // Ldap treats null and empty array the same // need to replace with single element array retattrs = new String[1]; retattrs[0] = "1.1"; } String nm = (relative ? fullyQualifiedName(name) : (name.isEmpty() ? "" : name.get(0))); // JNDI unit is milliseconds, LDAP unit is seconds. // Zero means no limit. int msecLimit = cons.getTimeLimit(); int secLimit = 0; if (msecLimit > 0) { secLimit = (msecLimit / 1000) + 1; } LdapResult answer = clnt.search(nm, scope, derefAliases, (int)cons.getCountLimit(), secLimit, cons.getReturningObjFlag() ? false : typesOnly, retattrs, filter, batchSize, reqCtls, binaryAttrs, waitForReply, replyQueueSize); respCtls = answer.resControls; // retrieve response controls return answer; } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw e2; } } /* * Certain simple JNDI searches are automatically converted to * LDAP compare operations by the LDAP service provider. A search * is converted to a compare iff: * * - the scope is set to OBJECT_SCOPE * - the filter string contains a simple assertion: "=" * - the returning attributes list is present but empty */ // returns true if a search can be carried out as a compare, and sets // tokens[0] and tokens[1] to the type and value respectively. // e.g. filter "cn=Jon Ruiz" becomes, type "cn" and value "Jon Ruiz" // This function uses the documents JNDI Compare example as a model // for when to turn a search into a compare. private static boolean searchToCompare( String filter, SearchControls cons, String tokens[]) { // if scope is not object-scope, it's really a search if (cons.getSearchScope() != SearchControls.OBJECT_SCOPE) { return false; } // if attributes are to be returned, it's really a search String[] attrs = cons.getReturningAttributes(); if (attrs == null || attrs.length != 0) { return false; } // if the filter not a simple assertion, it's really a search if (! filterToAssertion(filter, tokens)) { return false; } // it can be converted to a compare return true; } // If the supplied filter is a simple assertion i.e. "=" // (enclosing parentheses are permitted) then // filterToAssertion will return true and pass the type and value as // the first and second elements of tokens respectively. // precondition: tokens[] must be initialized and be at least of size 2. private static boolean filterToAssertion(String filter, String tokens[]) { // find the left and right half of the assertion StringTokenizer assertionTokenizer = new StringTokenizer(filter, "="); if (assertionTokenizer.countTokens() != 2) { return false; } tokens[0] = assertionTokenizer.nextToken(); tokens[1] = assertionTokenizer.nextToken(); // make sure the value does not contain a wildcard if (tokens[1].indexOf('*') != -1) { return false; } // test for enclosing parenthesis boolean hasParens = false; int len = tokens[1].length(); if ((tokens[0].charAt(0) == '(') && (tokens[1].charAt(len - 1) == ')')) { hasParens = true; } else if ((tokens[0].charAt(0) == '(') || (tokens[1].charAt(len - 1) == ')')) { return false; // unbalanced } // make sure the left and right half are not expressions themselves StringTokenizer illegalCharsTokenizer = new StringTokenizer(tokens[0], "()&|!=~><*", true); if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) { return false; } illegalCharsTokenizer = new StringTokenizer(tokens[1], "()&|!=~><*", true); if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) { return false; } // strip off enclosing parenthesis, if present if (hasParens) { tokens[0] = tokens[0].substring(1); tokens[1] = tokens[1].substring(0, len - 1); } return true; } private LdapResult compare(Name name, String type, String value) throws IOException, NamingException { ensureOpen(); String nm = fullyQualifiedName(name); LdapResult answer = clnt.compare(nm, type, value, reqCtls); respCtls = answer.resControls; // retrieve response controls return answer; } private static SearchControls cloneSearchControls(SearchControls cons) { if (cons == null) { return null; } String[] retAttrs = cons.getReturningAttributes(); if (retAttrs != null) { String[] attrs = new String[retAttrs.length]; System.arraycopy(retAttrs, 0, attrs, 0, retAttrs.length); retAttrs = attrs; } return new SearchControls(cons.getSearchScope(), cons.getCountLimit(), cons.getTimeLimit(), retAttrs, cons.getReturningObjFlag(), cons.getDerefLinkFlag()); } // -------------- Environment Properties ------------------ /** * Override with noncloning version. */ protected Hashtable p_getEnvironment() { return envprops; } @SuppressWarnings("unchecked") // clone() public Hashtable getEnvironment() throws NamingException { return (envprops == null ? new Hashtable(5, 0.75f) : (Hashtable)envprops.clone()); } @SuppressWarnings("unchecked") // clone() public Object removeFromEnvironment(String propName) throws NamingException { // not there; just return if (envprops == null || envprops.get(propName) == null) { return null; } switch (propName) { case REF_SEPARATOR: addrEncodingSeparator = DEFAULT_REF_SEPARATOR; break; case TYPES_ONLY: typesOnly = DEFAULT_TYPES_ONLY; break; case DELETE_RDN: deleteRDN = DEFAULT_DELETE_RDN; break; case DEREF_ALIASES: derefAliases = DEFAULT_DEREF_ALIASES; break; case Context.BATCHSIZE: batchSize = DEFAULT_BATCH_SIZE; break; case REFERRAL_LIMIT: referralHopLimit = DEFAULT_REFERRAL_LIMIT; break; case Context.REFERRAL: setReferralMode(null, true); break; case BINARY_ATTRIBUTES: setBinaryAttributes(null); break; case CONNECT_TIMEOUT: connectTimeout = -1; break; case READ_TIMEOUT: readTimeout = -1; break; case WAIT_FOR_REPLY: waitForReply = true; break; case REPLY_QUEUE_SIZE: replyQueueSize = -1; break; // The following properties affect the connection case Context.SECURITY_PROTOCOL: closeConnection(SOFT_CLOSE); // De-activate SSL and reset the context's url and port number if (useSsl && !hasLdapsScheme) { useSsl = false; url = null; if (useDefaultPortNumber) { port_number = DEFAULT_PORT; } } break; case VERSION: case SOCKET_FACTORY: closeConnection(SOFT_CLOSE); break; case Context.SECURITY_AUTHENTICATION: case Context.SECURITY_PRINCIPAL: case Context.SECURITY_CREDENTIALS: sharable = false; break; } // Update environment; reconnection will use new props envprops = (Hashtable)envprops.clone(); return envprops.remove(propName); } @SuppressWarnings("unchecked") // clone() public Object addToEnvironment(String propName, Object propVal) throws NamingException { // If adding null, call remove if (propVal == null) { return removeFromEnvironment(propName); } switch (propName) { case REF_SEPARATOR: setRefSeparator((String)propVal); break; case TYPES_ONLY: setTypesOnly((String)propVal); break; case DELETE_RDN: setDeleteRDN((String)propVal); break; case DEREF_ALIASES: setDerefAliases((String)propVal); break; case Context.BATCHSIZE: setBatchSize((String)propVal); break; case REFERRAL_LIMIT: setReferralLimit((String)propVal); break; case Context.REFERRAL: setReferralMode((String)propVal, true); break; case BINARY_ATTRIBUTES: setBinaryAttributes((String)propVal); break; case CONNECT_TIMEOUT: setConnectTimeout((String)propVal); break; case READ_TIMEOUT: setReadTimeout((String)propVal); break; case WAIT_FOR_REPLY: setWaitForReply((String)propVal); break; case REPLY_QUEUE_SIZE: setReplyQueueSize((String)propVal); break; // The following properties affect the connection case Context.SECURITY_PROTOCOL: closeConnection(SOFT_CLOSE); // Activate SSL and reset the context's url and port number if ("ssl".equals(propVal)) { useSsl = true; url = null; if (useDefaultPortNumber) { port_number = DEFAULT_SSL_PORT; } } break; case VERSION: case SOCKET_FACTORY: closeConnection(SOFT_CLOSE); break; case Context.SECURITY_AUTHENTICATION: case Context.SECURITY_PRINCIPAL: case Context.SECURITY_CREDENTIALS: sharable = false; break; } // Update environment; reconnection will use new props envprops = (envprops == null ? new Hashtable(5, 0.75f) : (Hashtable)envprops.clone()); return envprops.put(propName, propVal); } /** * Sets the URL that created the context in the java.naming.provider.url * property. */ void setProviderUrl(String providerUrl) { // called by LdapCtxFactory if (envprops != null) { envprops.put(Context.PROVIDER_URL, providerUrl); } } /** * Sets the domain name for the context in the com.sun.jndi.ldap.domainname * property. * Used for hostname verification by Start TLS */ void setDomainName(String domainName) { // called by LdapCtxFactory if (envprops != null) { envprops.put(DOMAIN_NAME, domainName); } } private void initEnv() throws NamingException { if (envprops == null) { // Make sure that referrals are to their default setReferralMode(null, false); return; } // Set batch size setBatchSize((String)envprops.get(Context.BATCHSIZE)); // Set separator used for encoding RefAddr setRefSeparator((String)envprops.get(REF_SEPARATOR)); // Set whether RDN is removed when renaming object setDeleteRDN((String)envprops.get(DELETE_RDN)); // Set whether types are returned only setTypesOnly((String)envprops.get(TYPES_ONLY)); // Set how aliases are dereferenced setDerefAliases((String)envprops.get(DEREF_ALIASES)); // Set the limit on referral chains setReferralLimit((String)envprops.get(REFERRAL_LIMIT)); setBinaryAttributes((String)envprops.get(BINARY_ATTRIBUTES)); bindCtls = cloneControls((Control[]) envprops.get(BIND_CONTROLS)); // set referral handling setReferralMode((String)envprops.get(Context.REFERRAL), false); // Set the connect timeout setConnectTimeout((String)envprops.get(CONNECT_TIMEOUT)); // Set the read timeout setReadTimeout((String)envprops.get(READ_TIMEOUT)); // Set the flag that controls whether to block until the first reply // is received setWaitForReply((String)envprops.get(WAIT_FOR_REPLY)); // Set the size of the queue of unprocessed search replies setReplyQueueSize((String)envprops.get(REPLY_QUEUE_SIZE)); // When connection is created, it will use these and other // properties from the environment } private void setDeleteRDN(String deleteRDNProp) { if ((deleteRDNProp != null) && (deleteRDNProp.equalsIgnoreCase("false"))) { deleteRDN = false; } else { deleteRDN = DEFAULT_DELETE_RDN; } } private void setTypesOnly(String typesOnlyProp) { if ((typesOnlyProp != null) && (typesOnlyProp.equalsIgnoreCase("true"))) { typesOnly = true; } else { typesOnly = DEFAULT_TYPES_ONLY; } } /** * Sets the batch size of this context; */ private void setBatchSize(String batchSizeProp) { // set batchsize if (batchSizeProp != null) { batchSize = Integer.parseInt(batchSizeProp); } else { batchSize = DEFAULT_BATCH_SIZE; } } /** * Sets the referral mode of this context to 'follow', 'throw' or 'ignore'. * If referral mode is 'ignore' then activate the manageReferral control. */ private void setReferralMode(String ref, boolean update) { // First determine the referral mode if (ref != null) { switch (ref) { case "follow": handleReferrals = LdapClient.LDAP_REF_FOLLOW; break; case "throw": handleReferrals = LdapClient.LDAP_REF_THROW; break; case "ignore": handleReferrals = LdapClient.LDAP_REF_IGNORE; break; default: throw new IllegalArgumentException( "Illegal value for " + Context.REFERRAL + " property."); } } else { handleReferrals = DEFAULT_REFERRAL_MODE; } if (handleReferrals == LdapClient.LDAP_REF_IGNORE) { // If ignoring referrals, add manageReferralControl reqCtls = addControl(reqCtls, manageReferralControl); } else if (update) { // If we're update an existing context, remove the control reqCtls = removeControl(reqCtls, manageReferralControl); } // else, leave alone; need not update } /** * Set whether aliases are dereferenced during resolution and searches. */ private void setDerefAliases(String deref) { if (deref != null) { switch (deref) { case "never": derefAliases = 0; // never de-reference aliases break; case "searching": derefAliases = 1; // de-reference aliases during searching break; case "finding": derefAliases = 2; // de-reference during name resolution break; case "always": derefAliases = 3; // always de-reference aliases break; default: throw new IllegalArgumentException("Illegal value for " + DEREF_ALIASES + " property."); } } else { derefAliases = DEFAULT_DEREF_ALIASES; } } private void setRefSeparator(String sepStr) throws NamingException { if (sepStr != null && sepStr.length() > 0) { addrEncodingSeparator = sepStr.charAt(0); } else { addrEncodingSeparator = DEFAULT_REF_SEPARATOR; } } /** * Sets the limit on referral chains */ private void setReferralLimit(String referralLimitProp) { // set referral limit if (referralLimitProp != null) { referralHopLimit = Integer.parseInt(referralLimitProp); // a zero setting indicates no limit if (referralHopLimit == 0) referralHopLimit = Integer.MAX_VALUE; } else { referralHopLimit = DEFAULT_REFERRAL_LIMIT; } } // For counting referral hops void setHopCount(int hopCount) { this.hopCount = hopCount; } /** * Sets the connect timeout value */ private void setConnectTimeout(String connectTimeoutProp) { if (connectTimeoutProp != null) { connectTimeout = Integer.parseInt(connectTimeoutProp); } else { connectTimeout = -1; } } /** * Sets the size of the queue of unprocessed search replies */ private void setReplyQueueSize(String replyQueueSizeProp) { if (replyQueueSizeProp != null) { replyQueueSize = Integer.parseInt(replyQueueSizeProp); // disallow an empty queue if (replyQueueSize <= 0) { replyQueueSize = -1; // unlimited } } else { replyQueueSize = -1; // unlimited } } /** * Sets the flag that controls whether to block until the first search * reply is received */ private void setWaitForReply(String waitForReplyProp) { if (waitForReplyProp != null && (waitForReplyProp.equalsIgnoreCase("false"))) { waitForReply = false; } else { waitForReply = true; } } /** * Sets the read timeout value */ private void setReadTimeout(String readTimeoutProp) { if (readTimeoutProp != null) { readTimeout = Integer.parseInt(readTimeoutProp); } else { readTimeout = -1; } } /* * Extract URLs from a string. The format of the string is: * * ::= "Referral:" * ::= | * ::= ASCII linefeed character (0x0a) * ::= LDAP URL format (RFC 1959) * * Returns a Vector of single-String Vectors. */ private static Vector> extractURLs(String refString) { int separator = 0; int urlCount = 0; // count the number of URLs while ((separator = refString.indexOf('\n', separator)) >= 0) { separator++; urlCount++; } Vector> referrals = new Vector<>(urlCount); int iURL; int i = 0; separator = refString.indexOf('\n'); iURL = separator + 1; while ((separator = refString.indexOf('\n', iURL)) >= 0) { Vector referral = new Vector<>(1); referral.addElement(refString.substring(iURL, separator)); referrals.addElement(referral); iURL = separator + 1; } Vector referral = new Vector<>(1); referral.addElement(refString.substring(iURL)); referrals.addElement(referral); return referrals; } /* * Argument is a space-separated list of attribute IDs * Converts attribute IDs to lowercase before adding to built-in list. */ private void setBinaryAttributes(String attrIds) { if (attrIds == null) { binaryAttrs = null; } else { binaryAttrs = new Hashtable<>(11, 0.75f); StringTokenizer tokens = new StringTokenizer(attrIds.toLowerCase(Locale.ENGLISH), " "); while (tokens.hasMoreTokens()) { binaryAttrs.put(tokens.nextToken(), Boolean.TRUE); } } } // ----------------- Connection --------------------- protected void finalize() { try { close(); } catch (NamingException e) { // ignore failures } } synchronized public void close() throws NamingException { if (debug) { System.err.println("LdapCtx: close() called " + this); (new Throwable()).printStackTrace(); } // Event (normal and unsolicited) if (eventSupport != null) { eventSupport.cleanup(); // idempotent removeUnsolicited(); } // Enumerations that are keeping the connection alive if (enumCount > 0) { if (debug) System.err.println("LdapCtx: close deferred"); closeRequested = true; return; } closeConnection(SOFT_CLOSE); // %%%: RL: There is no need to set these to null, as they're just // variables whose contents and references will automatically // be cleaned up when they're no longer referenced. // Also, setting these to null creates problems for the attribute // schema-related methods, which need these to work. /* schemaTrees = null; envprops = null; */ } @SuppressWarnings("unchecked") // clone() public void reconnect(Control[] connCtls) throws NamingException { // Update environment envprops = (envprops == null ? new Hashtable(5, 0.75f) : (Hashtable)envprops.clone()); if (connCtls == null) { envprops.remove(BIND_CONTROLS); bindCtls = null; } else { envprops.put(BIND_CONTROLS, bindCtls = cloneControls(connCtls)); } sharable = false; // can't share with existing contexts reconnect = true; ensureOpen(); // open or reauthenticated } private void ensureOpen() throws NamingException { ensureOpen(false); } private void ensureOpen(boolean startTLS) throws NamingException { try { if (clnt == null) { if (debug) { System.err.println("LdapCtx: Reconnecting " + this); } // reset the cache before a new connection is established schemaTrees = new Hashtable<>(11, 0.75f); connect(startTLS); } else if (!sharable || startTLS) { synchronized (clnt) { if (!clnt.isLdapv3 || clnt.referenceCount > 1 || clnt.usingSaslStreams()) { closeConnection(SOFT_CLOSE); } } // reset the cache before a new connection is established schemaTrees = new Hashtable<>(11, 0.75f); connect(startTLS); } } finally { sharable = true; // connection is now either new or single-use // OK for others to start sharing again } } private void connect(boolean startTLS) throws NamingException { if (debug) { System.err.println("LdapCtx: Connecting " + this); } String user = null; // authenticating user Object passwd = null; // password for authenticating user String secProtocol = null; // security protocol (e.g. "ssl") String socketFactory = null; // socket factory String authMechanism = null; // authentication mechanism String ver = null; int ldapVersion; // LDAP protocol version boolean usePool = false; // enable connection pooling if (envprops != null) { user = (String)envprops.get(Context.SECURITY_PRINCIPAL); passwd = envprops.get(Context.SECURITY_CREDENTIALS); ver = (String)envprops.get(VERSION); secProtocol = useSsl ? "ssl" : (String)envprops.get(Context.SECURITY_PROTOCOL); socketFactory = (String)envprops.get(SOCKET_FACTORY); authMechanism = (String)envprops.get(Context.SECURITY_AUTHENTICATION); usePool = "true".equalsIgnoreCase((String)envprops.get(ENABLE_POOL)); } if (socketFactory == null) { socketFactory = "ssl".equals(secProtocol) ? DEFAULT_SSL_FACTORY : null; } if (authMechanism == null) { authMechanism = (user == null) ? "none" : "simple"; } try { boolean initial = (clnt == null); if (initial || reconnect) { ldapVersion = (ver != null) ? Integer.parseInt(ver) : DEFAULT_LDAP_VERSION; clnt = LdapClient.getInstance( usePool, // Whether to use connection pooling // Required for LdapClient constructor hostname, port_number, socketFactory, connectTimeout, readTimeout, trace, // Required for basic client identity ldapVersion, authMechanism, bindCtls, secProtocol, // Required for simple client identity user, passwd, // Required for SASL client identity envprops); reconnect = false; /** * Pooled connections are preauthenticated; * newly created ones are not. */ if (clnt.authenticateCalled()) { return; } } else if (sharable && startTLS) { return; // no authentication required } else { // reauthenticating over existing connection; // only v3 supports this ldapVersion = LdapClient.LDAP_VERSION3; } LdapResult answer = clnt.authenticate(initial, user, passwd, ldapVersion, authMechanism, bindCtls, envprops); respCtls = answer.resControls; // retrieve (bind) response controls if (answer.status != LdapClient.LDAP_SUCCESS) { if (initial) { closeConnection(HARD_CLOSE); // hard close } processReturnCode(answer); } } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw e; String referral; LdapURL url; NamingException saved_ex = null; // Process the referrals sequentially (top level) and // recursively (per referral) while (true) { if ((referral = e.getNextReferral()) == null) { // No more referrals to follow if (saved_ex != null) { throw (NamingException)(saved_ex.fillInStackTrace()); } else { // No saved exception, something must have gone wrong throw new NamingException( "Internal error processing referral during connection"); } } // Use host/port number from referral url = new LdapURL(referral); hostname = url.getHost(); if ((hostname != null) && (hostname.charAt(0) == '[')) { hostname = hostname.substring(1, hostname.length() - 1); } port_number = url.getPort(); // Try to connect again using new host/port number try { connect(startTLS); break; } catch (NamingException ne) { saved_ex = ne; continue; // follow another referral } } } } private void closeConnection(boolean hardclose) { removeUnsolicited(); // idempotent if (clnt != null) { if (debug) { System.err.println("LdapCtx: calling clnt.close() " + this); } clnt.close(reqCtls, hardclose); clnt = null; } } // Used by Enum classes to track whether it still needs context private int enumCount = 0; private boolean closeRequested = false; synchronized void incEnumCount() { ++enumCount; if (debug) System.err.println("LdapCtx: " + this + " enum inc: " + enumCount); } synchronized void decEnumCount() { --enumCount; if (debug) System.err.println("LdapCtx: " + this + " enum dec: " + enumCount); if (enumCount == 0 && closeRequested) { try { close(); } catch (NamingException e) { // ignore failures } } } // ------------ Return code and Error messages ----------------------- protected void processReturnCode(LdapResult answer) throws NamingException { processReturnCode(answer, null, this, null, envprops, null); } void processReturnCode(LdapResult answer, Name remainName) throws NamingException { processReturnCode(answer, (new CompositeName()).add(currentDN), this, remainName, envprops, fullyQualifiedName(remainName)); } protected void processReturnCode(LdapResult res, Name resolvedName, Object resolvedObj, Name remainName, Hashtable envprops, String fullDN) throws NamingException { String msg = LdapClient.getErrorMessage(res.status, res.errorMessage); NamingException e; LdapReferralException r = null; switch (res.status) { case LdapClient.LDAP_SUCCESS: // handle Search continuation references if (res.referrals != null) { msg = "Unprocessed Continuation Reference(s)"; if (handleReferrals == LdapClient.LDAP_REF_IGNORE) { e = new PartialResultException(msg); break; } // handle multiple sets of URLs int contRefCount = res.referrals.size(); LdapReferralException head = null; LdapReferralException ptr = null; msg = "Continuation Reference"; // make a chain of LdapReferralExceptions for (int i = 0; i < contRefCount; i++) { r = new LdapReferralException(resolvedName, resolvedObj, remainName, msg, envprops, fullDN, handleReferrals, reqCtls); r.setReferralInfo(res.referrals.elementAt(i), true); if (hopCount > 1) { r.setHopCount(hopCount); } if (head == null) { head = ptr = r; } else { ptr.nextReferralEx = r; // append ex. to end of chain ptr = r; } } res.referrals = null; // reset if (res.refEx == null) { res.refEx = head; } else { ptr = res.refEx; while (ptr.nextReferralEx != null) { ptr = ptr.nextReferralEx; } ptr.nextReferralEx = head; } // check the hop limit if (hopCount > referralHopLimit) { NamingException lee = new LimitExceededException("Referral limit exceeded"); lee.setRootCause(r); throw lee; } } return; case LdapClient.LDAP_REFERRAL: if (handleReferrals == LdapClient.LDAP_REF_IGNORE) { e = new PartialResultException(msg); break; } r = new LdapReferralException(resolvedName, resolvedObj, remainName, msg, envprops, fullDN, handleReferrals, reqCtls); // only one set of URLs is present r.setReferralInfo(res.referrals.elementAt(0), false); if (hopCount > 1) { r.setHopCount(hopCount); } // check the hop limit if (hopCount > referralHopLimit) { NamingException lee = new LimitExceededException("Referral limit exceeded"); lee.setRootCause(r); e = lee; } else { e = r; } break; /* * Handle SLAPD-style referrals. * * Referrals received during name resolution should be followed * until one succeeds - the target entry is located. An exception * is thrown now to handle these. * * Referrals received during a search operation point to unexplored * parts of the directory and each should be followed. An exception * is thrown later (during results enumeration) to handle these. */ case LdapClient.LDAP_PARTIAL_RESULTS: if (handleReferrals == LdapClient.LDAP_REF_IGNORE) { e = new PartialResultException(msg); break; } // extract SLAPD-style referrals from errorMessage if ((res.errorMessage != null) && (!res.errorMessage.equals(""))) { res.referrals = extractURLs(res.errorMessage); } else { e = new PartialResultException(msg); break; } // build exception r = new LdapReferralException(resolvedName, resolvedObj, remainName, msg, envprops, fullDN, handleReferrals, reqCtls); if (hopCount > 1) { r.setHopCount(hopCount); } /* * %%% * SLAPD-style referrals received during name resolution * cannot be distinguished from those received during a * search operation. Since both must be handled differently * the following rule is applied: * * If 1 referral and 0 entries is received then * assume name resolution has not yet completed. */ if (((res.entries == null) || (res.entries.isEmpty())) && (res.referrals.size() == 1)) { r.setReferralInfo(res.referrals, false); // check the hop limit if (hopCount > referralHopLimit) { NamingException lee = new LimitExceededException("Referral limit exceeded"); lee.setRootCause(r); e = lee; } else { e = r; } } else { r.setReferralInfo(res.referrals, true); res.refEx = r; return; } break; case LdapClient.LDAP_INVALID_DN_SYNTAX: case LdapClient.LDAP_NAMING_VIOLATION: if (remainName != null) { e = new InvalidNameException(remainName.toString() + ": " + msg); } else { e = new InvalidNameException(msg); } break; default: e = mapErrorCode(res.status, res.errorMessage); break; } e.setResolvedName(resolvedName); e.setResolvedObj(resolvedObj); e.setRemainingName(remainName); throw e; } /** * Maps an LDAP error code to an appropriate NamingException. * %%% public; used by controls * * @param errorCode numeric LDAP error code * @param errorMessage textual description of the LDAP error. May be null. * * @return A NamingException or null if the error code indicates success. */ public static NamingException mapErrorCode(int errorCode, String errorMessage) { if (errorCode == LdapClient.LDAP_SUCCESS) return null; NamingException e = null; String message = LdapClient.getErrorMessage(errorCode, errorMessage); switch (errorCode) { case LdapClient.LDAP_ALIAS_DEREFERENCING_PROBLEM: e = new NamingException(message); break; case LdapClient.LDAP_ALIAS_PROBLEM: e = new NamingException(message); break; case LdapClient.LDAP_ATTRIBUTE_OR_VALUE_EXISTS: e = new AttributeInUseException(message); break; case LdapClient.LDAP_AUTH_METHOD_NOT_SUPPORTED: case LdapClient.LDAP_CONFIDENTIALITY_REQUIRED: case LdapClient.LDAP_STRONG_AUTH_REQUIRED: case LdapClient.LDAP_INAPPROPRIATE_AUTHENTICATION: e = new AuthenticationNotSupportedException(message); break; case LdapClient.LDAP_ENTRY_ALREADY_EXISTS: e = new NameAlreadyBoundException(message); break; case LdapClient.LDAP_INVALID_CREDENTIALS: case LdapClient.LDAP_SASL_BIND_IN_PROGRESS: e = new AuthenticationException(message); break; case LdapClient.LDAP_INAPPROPRIATE_MATCHING: e = new InvalidSearchFilterException(message); break; case LdapClient.LDAP_INSUFFICIENT_ACCESS_RIGHTS: e = new NoPermissionException(message); break; case LdapClient.LDAP_INVALID_ATTRIBUTE_SYNTAX: case LdapClient.LDAP_CONSTRAINT_VIOLATION: e = new InvalidAttributeValueException(message); break; case LdapClient.LDAP_LOOP_DETECT: e = new NamingException(message); break; case LdapClient.LDAP_NO_SUCH_ATTRIBUTE: e = new NoSuchAttributeException(message); break; case LdapClient.LDAP_NO_SUCH_OBJECT: e = new NameNotFoundException(message); break; case LdapClient.LDAP_OBJECT_CLASS_MODS_PROHIBITED: case LdapClient.LDAP_OBJECT_CLASS_VIOLATION: case LdapClient.LDAP_NOT_ALLOWED_ON_RDN: e = new SchemaViolationException(message); break; case LdapClient.LDAP_NOT_ALLOWED_ON_NON_LEAF: e = new ContextNotEmptyException(message); break; case LdapClient.LDAP_OPERATIONS_ERROR: // %%% need new exception ? e = new NamingException(message); break; case LdapClient.LDAP_OTHER: e = new NamingException(message); break; case LdapClient.LDAP_PROTOCOL_ERROR: e = new CommunicationException(message); break; case LdapClient.LDAP_SIZE_LIMIT_EXCEEDED: e = new SizeLimitExceededException(message); break; case LdapClient.LDAP_TIME_LIMIT_EXCEEDED: e = new TimeLimitExceededException(message); break; case LdapClient.LDAP_UNAVAILABLE_CRITICAL_EXTENSION: e = new OperationNotSupportedException(message); break; case LdapClient.LDAP_UNAVAILABLE: case LdapClient.LDAP_BUSY: e = new ServiceUnavailableException(message); break; case LdapClient.LDAP_UNDEFINED_ATTRIBUTE_TYPE: e = new InvalidAttributeIdentifierException(message); break; case LdapClient.LDAP_UNWILLING_TO_PERFORM: e = new OperationNotSupportedException(message); break; case LdapClient.LDAP_COMPARE_FALSE: case LdapClient.LDAP_COMPARE_TRUE: case LdapClient.LDAP_IS_LEAF: // these are really not exceptions and this code probably // never gets executed e = new NamingException(message); break; case LdapClient.LDAP_ADMIN_LIMIT_EXCEEDED: e = new LimitExceededException(message); break; case LdapClient.LDAP_REFERRAL: e = new NamingException(message); break; case LdapClient.LDAP_PARTIAL_RESULTS: e = new NamingException(message); break; case LdapClient.LDAP_INVALID_DN_SYNTAX: case LdapClient.LDAP_NAMING_VIOLATION: e = new InvalidNameException(message); break; default: e = new NamingException(message); break; } return e; } // ----------------- Extensions and Controls ------------------- public ExtendedResponse extendedOperation(ExtendedRequest request) throws NamingException { boolean startTLS = (request.getID().equals(STARTTLS_REQ_OID)); ensureOpen(startTLS); try { LdapResult answer = clnt.extendedOp(request.getID(), request.getEncodedValue(), reqCtls, startTLS); respCtls = answer.resControls; // retrieve response controls if (answer.status != LdapClient.LDAP_SUCCESS) { processReturnCode(answer, new CompositeName()); } // %%% verify request.getID() == answer.extensionId int len = (answer.extensionValue == null) ? 0 : answer.extensionValue.length; ExtendedResponse er = request.createExtendedResponse(answer.extensionId, answer.extensionValue, 0, len); if (er instanceof StartTlsResponseImpl) { // Pass the connection handle to StartTlsResponseImpl String domainName = (String) (envprops != null ? envprops.get(DOMAIN_NAME) : null); ((StartTlsResponseImpl)er).setConnection(clnt.conn, domainName); } return er; } catch (LdapReferralException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) throw e; // process the referrals sequentially while (true) { LdapReferralContext refCtx = (LdapReferralContext)e.getReferralContext(envprops, bindCtls); // repeat the original operation at the new context try { return refCtx.extendedOperation(request); } catch (LdapReferralException re) { e = re; continue; } finally { // Make sure we close referral context refCtx.close(); } } } catch (IOException e) { NamingException e2 = new CommunicationException(e.getMessage()); e2.setRootCause(e); throw e2; } } public void setRequestControls(Control[] reqCtls) throws NamingException { if (handleReferrals == LdapClient.LDAP_REF_IGNORE) { this.reqCtls = addControl(reqCtls, manageReferralControl); } else { this.reqCtls = cloneControls(reqCtls); } } public Control[] getRequestControls() throws NamingException { return cloneControls(reqCtls); } public Control[] getConnectControls() throws NamingException { return cloneControls(bindCtls); } public Control[] getResponseControls() throws NamingException { return (respCtls != null)? convertControls(respCtls) : null; } /** * Narrow controls using own default factory and ControlFactory. * @param ctls A non-null Vector */ Control[] convertControls(Vector ctls) throws NamingException { int count = ctls.size(); if (count == 0) { return null; } Control[] controls = new Control[count]; for (int i = 0; i < count; i++) { // Try own factory first controls[i] = myResponseControlFactory.getControlInstance( ctls.elementAt(i)); // Try assigned factories if own produced null if (controls[i] == null) { controls[i] = ControlFactory.getControlInstance( ctls.elementAt(i), this, envprops); } } return controls; } private static Control[] addControl(Control[] prevCtls, Control addition) { if (prevCtls == null) { return new Control[]{addition}; } // Find it int found = findControl(prevCtls, addition); if (found != -1) { return prevCtls; // no need to do it again } Control[] newCtls = new Control[prevCtls.length+1]; System.arraycopy(prevCtls, 0, newCtls, 0, prevCtls.length); newCtls[prevCtls.length] = addition; return newCtls; } private static int findControl(Control[] ctls, Control target) { for (int i = 0; i < ctls.length; i++) { if (ctls[i] == target) { return i; } } return -1; } private static Control[] removeControl(Control[] prevCtls, Control target) { if (prevCtls == null) { return null; } // Find it int found = findControl(prevCtls, target); if (found == -1) { return prevCtls; // not there } // Remove it Control[] newCtls = new Control[prevCtls.length-1]; System.arraycopy(prevCtls, 0, newCtls, 0, found); System.arraycopy(prevCtls, found+1, newCtls, found, prevCtls.length-found-1); return newCtls; } private static Control[] cloneControls(Control[] ctls) { if (ctls == null) { return null; } Control[] copiedCtls = new Control[ctls.length]; System.arraycopy(ctls, 0, copiedCtls, 0, ctls.length); return copiedCtls; } // -------------------- Events ------------------------ /* * Access to eventSupport need not be synchronized even though the * Connection thread can access it asynchronously. It is * impossible for a race condition to occur because * eventSupport.addNamingListener() must have been called before * the Connection thread can call back to this ctx. */ public void addNamingListener(Name nm, int scope, NamingListener l) throws NamingException { addNamingListener(getTargetName(nm), scope, l); } public void addNamingListener(String nm, int scope, NamingListener l) throws NamingException { if (eventSupport == null) eventSupport = new EventSupport(this); eventSupport.addNamingListener(getTargetName(new CompositeName(nm)), scope, l); // If first time asking for unsol if (l instanceof UnsolicitedNotificationListener && !unsolicited) { addUnsolicited(); } } public void removeNamingListener(NamingListener l) throws NamingException { if (eventSupport == null) return; // no activity before, so just return eventSupport.removeNamingListener(l); // If removing an Unsol listener and it is the last one, let clnt know if (l instanceof UnsolicitedNotificationListener && !eventSupport.hasUnsolicited()) { removeUnsolicited(); } } public void addNamingListener(String nm, String filter, SearchControls ctls, NamingListener l) throws NamingException { if (eventSupport == null) eventSupport = new EventSupport(this); eventSupport.addNamingListener(getTargetName(new CompositeName(nm)), filter, cloneSearchControls(ctls), l); // If first time asking for unsol if (l instanceof UnsolicitedNotificationListener && !unsolicited) { addUnsolicited(); } } public void addNamingListener(Name nm, String filter, SearchControls ctls, NamingListener l) throws NamingException { addNamingListener(getTargetName(nm), filter, ctls, l); } public void addNamingListener(Name nm, String filter, Object[] filterArgs, SearchControls ctls, NamingListener l) throws NamingException { addNamingListener(getTargetName(nm), filter, filterArgs, ctls, l); } public void addNamingListener(String nm, String filterExpr, Object[] filterArgs, SearchControls ctls, NamingListener l) throws NamingException { String strfilter = SearchFilter.format(filterExpr, filterArgs); addNamingListener(getTargetName(new CompositeName(nm)), strfilter, ctls, l); } public boolean targetMustExist() { return true; } /** * Retrieves the target name for which the listener is registering. * If nm is a CompositeName, use its first and only component. It * cannot have more than one components because a target be outside of * this namespace. If nm is not a CompositeName, then treat it as a * compound name. * @param nm The non-null target name. */ private static String getTargetName(Name nm) throws NamingException { if (nm instanceof CompositeName) { if (nm.size() > 1) { throw new InvalidNameException( "Target cannot span multiple namespaces: " + nm); } else if (nm.isEmpty()) { return ""; } else { return nm.get(0); } } else { // treat as compound name return nm.toString(); } } // ------------------ Unsolicited Notification --------------- // package private methods for handling unsolicited notification /** * Registers this context with the underlying LdapClient. * When the underlying LdapClient receives an unsolicited notification, * it will invoke LdapCtx.fireUnsolicited() so that this context * can (using EventSupport) notified any registered listeners. * This method is called by EventSupport when an unsolicited listener * first registers with this context (should be called just once). * @see #removeUnsolicited * @see #fireUnsolicited */ private void addUnsolicited() throws NamingException { if (debug) { System.out.println("LdapCtx.addUnsolicited: " + this); } // addNamingListener must have created EventSupport already ensureOpen(); synchronized (eventSupport) { clnt.addUnsolicited(this); unsolicited = true; } } /** * Removes this context from registering interest in unsolicited * notifications from the underlying LdapClient. This method is called * under any one of the following conditions: *
    *
  • All unsolicited listeners have been removed. (see removingNamingListener) *
  • This context is closed. *
  • This context's underlying LdapClient changes. *
* After this method has been called, this context will not pass * on any events related to unsolicited notifications to EventSupport and * and its listeners. */ private void removeUnsolicited() { if (debug) { System.out.println("LdapCtx.removeUnsolicited: " + unsolicited); } if (eventSupport == null) { return; } // addNamingListener must have created EventSupport already synchronized(eventSupport) { if (unsolicited && clnt != null) { clnt.removeUnsolicited(this); } unsolicited = false; } } /** * Uses EventSupport to fire an event related to an unsolicited notification. * Called by LdapClient when LdapClient receives an unsolicited notification. */ void fireUnsolicited(Object obj) { if (debug) { System.out.println("LdapCtx.fireUnsolicited: " + obj); } // addNamingListener must have created EventSupport already synchronized(eventSupport) { if (unsolicited) { eventSupport.fireUnsolicited(obj); if (obj instanceof NamingException) { unsolicited = false; // No need to notify clnt because clnt is the // only one that can fire a NamingException to // unsol listeners and it will handle its own cleanup } } } } }