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