| /* |
| * Copyright (c) 1999, 2013, 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 |
| Hashtable<String, java.lang.Object> 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<Control> 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<String, DirContext> 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<String, Boolean> 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<String, java.lang.Object>) 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 |
| * @returns 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<Rdn> 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<? extends Attribute> 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<String> 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 the second Vector onto the first Vector |
| * (v2 must be non-null) |
| */ |
| private static <T> Vector<T> appendVector(Vector<T> v1, Vector<T> 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<Control> 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<NameClassPair> 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<Binding> 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<Control> 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<? extends Attribute> 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<SearchResult> 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 trys a serarch 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<SearchResult> 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<? extends Attribute> 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<SearchResult> c_search(Name name, |
| Attributes matchingAttributes, |
| Continuation cont) |
| throws NamingException { |
| return c_search(name, matchingAttributes, null, cont); |
| } |
| |
| protected NamingEnumeration<SearchResult> 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<SearchResult> c_search(Name name, |
| String filter, |
| SearchControls cons, |
| Continuation cont) |
| throws NamingException { |
| return searchAux(name, filter, cloneSearchControls(cons), true, |
| waitForReply, cont); |
| } |
| |
| protected NamingEnumeration<SearchResult> 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<SearchResult> 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: "<type>=<value>" |
| * - the returning attributes list is present but empty |
| */ |
| |
| // returns true if a search can be caried 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. "<type>=<value>" |
| // (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 expresions 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<String, Object> p_getEnvironment() { |
| return envprops; |
| } |
| |
| @SuppressWarnings("unchecked") // clone() |
| public Hashtable<String, Object> getEnvironment() throws NamingException { |
| return (envprops == null |
| ? new Hashtable<String, Object>(5, 0.75f) |
| : (Hashtable<String, Object>)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<String, Object>)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<String, Object>(5, 0.75f) |
| : (Hashtable<String, Object>)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 derefereced 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: |
| * |
| * <urlstring > ::= "Referral:" <ldapurls> |
| * <ldapurls> ::= <separator> <ldapurl> | <ldapurls> |
| * <separator> ::= ASCII linefeed character (0x0a) |
| * <ldapurl> ::= LDAP URL format (RFC 1959) |
| * |
| * Returns a Vector of single-String Vectors. |
| */ |
| private static Vector<Vector<String>> extractURLs(String refString) { |
| |
| int separator = 0; |
| int urlCount = 0; |
| |
| // count the number of URLs |
| while ((separator = refString.indexOf('\n', separator)) >= 0) { |
| separator++; |
| urlCount++; |
| } |
| |
| Vector<Vector<String>> referrals = new Vector<>(urlCount); |
| int iURL; |
| int i = 0; |
| |
| separator = refString.indexOf('\n'); |
| iURL = separator + 1; |
| while ((separator = refString.indexOf('\n', iURL)) >= 0) { |
| Vector<String> referral = new Vector<>(1); |
| referral.addElement(refString.substring(iURL, separator)); |
| referrals.addElement(referral); |
| iURL = separator + 1; |
| } |
| Vector<String> 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<String, Object>(5, 0.75f) |
| : (Hashtable<String, Object>)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 |
| 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) { |
| 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); |
| |
| |
| /** |
| * 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 == null ? null : |
| 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 != null) && (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> |
| */ |
| Control[] convertControls(Vector<Control> 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: |
| * <ul> |
| * <li>All unsolicited listeners have been removed. (see removingNamingListener) |
| * <li>This context is closed. |
| * <li>This context's underlying LdapClient changes. |
| *</ul> |
| * 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 |
| } |
| } |
| } |
| } |
| } |