/*
* Copyright (c) 1999, 2020, 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.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import java.util.Vector;
import java.util.Hashtable;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Enumeration;
import java.util.function.Predicate;
import java.util.stream.Collectors;
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, Rosanna Lee
/**
* 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";
// System and environment property name to control allowed list of
// authentication mechanisms: "all" or "" or "mech1,mech2,...,mechN"
// "all": allow all mechanisms,
// "": allow none
// or comma separated list of allowed authentication mechanisms
// Note: "none" or "anonymous" are always allowed.
private static final String ALLOWED_MECHS_SP =
"jdk.jndi.ldap.mechsAllowedToSendCredentials";
// System property value
private static final String ALLOWED_MECHS_SP_VALUE =
getMechsAllowedToSendCredentials();
// Set of authentication mechanisms allowed by the system property
private static final Set<String> MECHS_ALLOWED_BY_SP =
getMechsFromPropertyValue(ALLOWED_MECHS_SP_VALUE);
// The message to use in NamingException if the transmission of plain credentials are not allowed
private static final String UNSECURED_CRED_TRANSMIT_MSG =
"Transmission of credentials over unsecured connection is not allowed";
// ----------------- 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
// Used to track if context was seen to be secured with STARTTLS extended operation
volatile boolean contextSeenStartTlsEnabled;
// ------------- 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.
Params: - dn – The non-null DN of the entry to add
- attrs – The non-null attributes of entry to add
- directUpdate – Whether attrs can be updated directly
Returns: Non-null attributes with attributes from the RDN added
/**
* Adds attributes from RDN to attrs if not already present.
* Note that if attrs already contains an attribute by the same name,
* or if the distinguished name is empty, then leave attrs unchanged.
*
* @param dn The non-null DN of the entry to add
* @param attrs The non-null attributes of entry to add
* @param directUpdate Whether attrs can be updated directly
* @return Non-null attributes with attributes from the RDN added
*/
private static Attributes addRdnAttributes(String dn, Attributes attrs,
boolean directUpdate) throws NamingException {
// Handle the empty name
if (dn.isEmpty()) {
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 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).isEmpty() || prefix.get(prefixLast).isEmpty()) {
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.isEmpty()) {
return greater;
} else if (greater == null || greater.isEmpty()) {
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 tries a search on a v2 server that
* doesn't support schema, one of these two things will happen:
* 1) It will get an exception when querying the root DSE
* 2) It will not find a subschemasubentry on the root DSE
* If either of these things occur and the server is not v3, we
* throw OperationNotSupported.
*
* the relative flag tells whether the given name is relative to this
* context.
*/
private String getSchemaEntry(Name name, boolean relative)
throws NamingException {
// Asks for operational attribute "subschemasubentry"
SearchControls constraints = new SearchControls(SearchControls.OBJECT_SCOPE,
0, 0, /* count and time limits */
new String[]{"subschemasubentry"} /* attr to return */,
false /* returning obj */,
false /* deref link */);
NamingEnumeration<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 carried out as a compare, and sets
// tokens[0] and tokens[1] to the type and value respectively.
// e.g. filter "cn=Jon Ruiz" becomes, type "cn" and value "Jon Ruiz"
// This function uses the documents JNDI Compare example as a model
// for when to turn a search into a compare.
private static boolean searchToCompare(
String filter,
SearchControls cons,
String tokens[]) {
// if scope is not object-scope, it's really a search
if (cons.getSearchScope() != SearchControls.OBJECT_SCOPE) {
return false;
}
// if attributes are to be returned, it's really a search
String[] attrs = cons.getReturningAttributes();
if (attrs == null || attrs.length != 0) {
return false;
}
// if the filter not a simple assertion, it's really a search
if (! filterToAssertion(filter, tokens)) {
return false;
}
// it can be converted to a compare
return true;
}
// If the supplied filter is a simple assertion i.e. "<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 expressions themselves
StringTokenizer illegalCharsTokenizer =
new StringTokenizer(tokens[0], "()&|!=~><*", true);
if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {
return false;
}
illegalCharsTokenizer =
new StringTokenizer(tokens[1], "()&|!=~><*", true);
if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {
return false;
}
// strip off enclosing parenthesis, if present
if (hasParens) {
tokens[0] = tokens[0].substring(1);
tokens[1] = tokens[1].substring(0, len - 1);
}
return true;
}
private LdapResult compare(Name name, String type, String value)
throws IOException, NamingException {
ensureOpen();
String nm = fullyQualifiedName(name);
LdapResult answer = clnt.compare(nm, type, value, reqCtls);
respCtls = answer.resControls; // retrieve response controls
return answer;
}
private static SearchControls cloneSearchControls(SearchControls cons) {
if (cons == null) {
return null;
}
String[] retAttrs = cons.getReturningAttributes();
if (retAttrs != null) {
String[] attrs = new String[retAttrs.length];
System.arraycopy(retAttrs, 0, attrs, 0, retAttrs.length);
retAttrs = attrs;
}
return new SearchControls(cons.getSearchScope(),
cons.getCountLimit(),
cons.getTimeLimit(),
retAttrs,
cons.getReturningObjFlag(),
cons.getDerefLinkFlag());
}
// -------------- Environment Properties ------------------
Override with noncloning version.
/**
* 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.
/**
* 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
/**
* 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;
/**
* 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.
/**
* 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-scheme":
handleReferrals = LdapClient.LDAP_REF_FOLLOW_SCHEME;
break;
case "follow":
handleReferrals = LdapClient.LDAP_REF_FOLLOW;
break;
case "throw":
handleReferrals = LdapClient.LDAP_REF_THROW;
break;
case "ignore":
handleReferrals = LdapClient.LDAP_REF_IGNORE;
break;
default:
throw new IllegalArgumentException(
"Illegal value for " + Context.REFERRAL + " property.");
}
} else {
handleReferrals = DEFAULT_REFERRAL_MODE;
}
if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {
// If ignoring referrals, add manageReferralControl
reqCtls = addControl(reqCtls, manageReferralControl);
} else if (update) {
// If we're update an existing context, remove the control
reqCtls = removeControl(reqCtls, manageReferralControl);
} // else, leave alone; need not update
}
Set whether aliases are dereferenced during resolution and searches.
/**
* Set whether aliases are dereferenced during resolution and searches.
*/
private void setDerefAliases(String deref) {
if (deref != null) {
switch (deref) {
case "never":
derefAliases = 0; // never de-reference aliases
break;
case "searching":
derefAliases = 1; // de-reference aliases during searching
break;
case "finding":
derefAliases = 2; // de-reference during name resolution
break;
case "always":
derefAliases = 3; // always de-reference aliases
break;
default:
throw new IllegalArgumentException("Illegal value for " +
DEREF_ALIASES + " property.");
}
} else {
derefAliases = DEFAULT_DEREF_ALIASES;
}
}
private void setRefSeparator(String sepStr) throws NamingException {
if (sepStr != null && sepStr.length() > 0) {
addrEncodingSeparator = sepStr.charAt(0);
} else {
addrEncodingSeparator = DEFAULT_REF_SEPARATOR;
}
}
Sets the limit on referral chains
/**
* 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
/**
* 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
/**
* 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
/**
* 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
/**
* 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 ---------------------
@SuppressWarnings("deprecation")
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
}
// Load 'mechsAllowedToSendCredentials' system property value
private static String getMechsAllowedToSendCredentials() {
PrivilegedAction<String> pa = () -> System.getProperty(ALLOWED_MECHS_SP);
return System.getSecurityManager() == null ? pa.run() : AccessController.doPrivileged(pa);
}
// Get set of allowed authentication mechanism names from the property value
private static Set<String> getMechsFromPropertyValue(String propValue) {
if (propValue == null || propValue.isBlank()) {
return Collections.emptySet();
}
return Arrays.stream(propValue.split(","))
.map(String::trim)
.filter(Predicate.not(String::isBlank))
.collect(Collectors.toUnmodifiableSet());
}
// Returns true if TLS connection opened using "ldaps" scheme, or using "ldap" and then upgraded with
// startTLS extended operation, and startTLS is still active.
private boolean isConnectionEncrypted() {
return hasLdapsScheme || clnt.isUpgradedToStartTls();
}
// Ensure connection and context are in a safe state to transmit credentials
private void ensureCanTransmitCredentials(String authMechanism) throws NamingException {
// "none" and "anonumous" authentication mechanisms are allowed unconditionally
if ("none".equalsIgnoreCase(authMechanism) || "anonymous".equalsIgnoreCase(authMechanism)) {
return;
}
// Check environment first
String allowedMechanismsOrTrue = (String) envprops.get(ALLOWED_MECHS_SP);
boolean useSpMechsCache = false;
boolean anyPropertyIsSet = ALLOWED_MECHS_SP_VALUE != null || allowedMechanismsOrTrue != null;
// If current connection is not encrypted, and context seen to be secured with STARTTLS
// or 'mechsAllowedToSendCredentials' is set to any value via system/context environment properties
if (!isConnectionEncrypted() && (contextSeenStartTlsEnabled || anyPropertyIsSet)) {
// First, check if security principal is provided in context environment for "simple"
// authentication mechanism. There is no check for other SASL mechanisms since the credentials
// can be specified via other properties
if ("simple".equalsIgnoreCase(authMechanism) && !envprops.containsKey(SECURITY_PRINCIPAL)) {
return;
}
// If null - will use mechanism name cached from system property
if (allowedMechanismsOrTrue == null) {
useSpMechsCache = true;
allowedMechanismsOrTrue = ALLOWED_MECHS_SP_VALUE;
}
// If the property value (system or environment) is 'all':
// any kind of authentication is allowed unconditionally - no check is needed
if ("all".equalsIgnoreCase(allowedMechanismsOrTrue)) {
return;
}
// Get the set with allowed authentication mechanisms and check current mechanism
Set<String> allowedAuthMechs = useSpMechsCache ?
MECHS_ALLOWED_BY_SP : getMechsFromPropertyValue(allowedMechanismsOrTrue);
if (!allowedAuthMechs.contains(authMechanism)) {
throw new NamingException(UNSECURED_CRED_TRANSMIT_MSG);
}
}
}
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()
|| !clnt.conn.useable) {
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);
// Mark current context as secure if the connection is acquired
// from the pool and it is secure.
contextSeenStartTlsEnabled |= clnt.isUpgradedToStartTls();
/**
* 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;
synchronized (clnt.conn.startTlsLock) {
ensureCanTransmitCredentials(authMechanism);
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
Vector<String> refs;
if (res.referrals == null) {
refs = null;
} else if (handleReferrals == LdapClient.LDAP_REF_FOLLOW_SCHEME) {
refs = new Vector<>();
for (String s : res.referrals.elementAt(0)) {
if (s.startsWith("ldap:")) {
refs.add(s);
}
}
if (refs.isEmpty()) {
refs = null;
}
} else {
refs = res.referrals.elementAt(0);
}
r.setReferralInfo(refs, 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.isEmpty())) {
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
Params: - errorCode – numeric LDAP error code
- errorMessage – textual description of the LDAP error. May be null.
Returns: A NamingException or null if the error code indicates success.
/**
* 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);
contextSeenStartTlsEnabled |= startTLS;
}
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.
Params: - ctls – A non-null Vector
/**
* 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.
Params: - nm – The non-null target name.
/**
* 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 Also: - removeUnsolicited
- fireUnsolicited
/**
* Registers this context with the underlying LdapClient.
* When the underlying LdapClient receives an unsolicited notification,
* it will invoke LdapCtx.fireUnsolicited() so that this context
* can (using EventSupport) notified any registered listeners.
* This method is called by EventSupport when an unsolicited listener
* first registers with this context (should be called just once).
* @see #removeUnsolicited
* @see #fireUnsolicited
*/
private void addUnsolicited() throws NamingException {
if (debug) {
System.out.println("LdapCtx.addUnsolicited: " + this);
}
// addNamingListener must have created EventSupport already
ensureOpen();
synchronized (eventSupport) {
clnt.addUnsolicited(this);
unsolicited = true;
}
}
Removes this context from registering interest in unsolicited
notifications from the underlying LdapClient. This method is called
under any one of the following conditions:
- All unsolicited listeners have been removed. (see removingNamingListener)
- This context is closed.
- This context's underlying LdapClient changes.
After this method has been called, this context will not pass
on any events related to unsolicited notifications to EventSupport and
and its listeners.
/**
* 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.
/**
* 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
}
}
}
}
}