Path: blob/master/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java
41161 views
/*1* Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.jndi.ldap;2627import javax.naming.*;28import javax.naming.directory.*;29import javax.naming.spi.*;30import javax.naming.event.*;31import javax.naming.ldap.*;32import javax.naming.ldap.LdapName;33import javax.naming.ldap.Rdn;3435import java.security.AccessController;36import java.security.PrivilegedAction;37import java.util.Arrays;38import java.util.Collections;39import java.util.Locale;40import java.util.Set;41import java.util.Vector;42import java.util.Hashtable;43import java.util.List;44import java.util.StringTokenizer;45import java.util.Enumeration;46import java.util.function.Predicate;47import java.util.stream.Collectors;4849import java.io.IOException;50import java.io.OutputStream;5152import com.sun.jndi.toolkit.ctx.*;53import com.sun.jndi.toolkit.dir.HierMemDirCtx;54import com.sun.jndi.toolkit.dir.SearchFilter;55import com.sun.jndi.ldap.ext.StartTlsResponseImpl;5657/**58* The LDAP context implementation.59*60* Implementation is not thread-safe. Caller must sync as per JNDI spec.61* Members that are used directly or indirectly by internal worker threads62* (Connection, EventQueue, NamingEventNotifier) must be thread-safe.63* Connection - calls LdapClient.processUnsolicited(), which in turn calls64* LdapCtx.convertControls() and LdapCtx.fireUnsolicited().65* convertControls() - no sync; reads envprops and 'this'66* fireUnsolicited() - sync on eventSupport for all references to 'unsolicited'67* (even those in other methods); don't sync on LdapCtx in case caller68* is already sync'ing on it - this would prevent Unsol events from firing69* and the Connection thread to block (thus preventing any other data70* from being read from the connection)71* References to 'eventSupport' need not be sync'ed because these72* methods can only be called after eventSupport has been set first73* (via addNamingListener()).74* EventQueue - no direct or indirect calls to LdapCtx75* NamingEventNotifier - calls newInstance() to get instance for run() to use;76* no sync needed for methods invoked on new instance;77*78* LdapAttribute links to LdapCtx in order to process getAttributeDefinition()79* and getAttributeSyntaxDefinition() calls. It invokes LdapCtx.getSchema(),80* which uses schemaTrees (a Hashtable - already sync). Potential conflict81* of duplicating construction of tree for same subschemasubentry82* but no inconsistency problems.83*84* NamingEnumerations link to LdapCtx for the following:85* 1. increment/decrement enum count so that ctx doesn't close the86* underlying connection87* 2. LdapClient handle to get next batch of results88* 3. Sets LdapCtx's response controls89* 4. Process return code90* 5. For narrowing response controls (using ctx's factories)91* Since processing of NamingEnumeration by client is treated the same as method92* invocation on LdapCtx, caller is responsible for locking.93*94* @author Vincent Ryan95* @author Rosanna Lee96*/9798public final class LdapCtx extends ComponentDirContext99implements EventDirContext, LdapContext {100101/*102* Used to store arguments to the search method.103*/104static final class SearchArgs {105Name name;106String filter;107SearchControls cons;108String[] reqAttrs; // those attributes originally requested109110SearchArgs(Name name, String filter, SearchControls cons, String[] ra) {111this.name = name;112this.filter = filter;113this.cons = cons;114this.reqAttrs = ra;115}116}117118private static final boolean debug = false;119120private static final boolean HARD_CLOSE = true;121private static final boolean SOFT_CLOSE = false;122123// ----------------- Constants -----------------124125public static final int DEFAULT_PORT = 389;126public static final int DEFAULT_SSL_PORT = 636;127public static final String DEFAULT_HOST = "localhost";128129private static final boolean DEFAULT_DELETE_RDN = true;130private static final boolean DEFAULT_TYPES_ONLY = false;131private static final int DEFAULT_DEREF_ALIASES = 3; // always deref132private static final int DEFAULT_LDAP_VERSION = LdapClient.LDAP_VERSION3_VERSION2;133private static final int DEFAULT_BATCH_SIZE = 1;134private static final int DEFAULT_REFERRAL_MODE = LdapClient.LDAP_REF_IGNORE;135private static final char DEFAULT_REF_SEPARATOR = '#';136137// Used by LdapPoolManager138static final String DEFAULT_SSL_FACTORY =139"javax.net.ssl.SSLSocketFactory"; // use Sun's SSL140private static final int DEFAULT_REFERRAL_LIMIT = 10;141private static final String STARTTLS_REQ_OID = "1.3.6.1.4.1.1466.20037";142143// schema operational and user attributes144private static final String[] SCHEMA_ATTRIBUTES =145{ "objectClasses", "attributeTypes", "matchingRules", "ldapSyntaxes" };146147// --------------- Environment property names ----------148149// LDAP protocol version: "2", "3"150private static final String VERSION = "java.naming.ldap.version";151152// Binary-valued attributes. Space separated string of attribute names.153private static final String BINARY_ATTRIBUTES =154"java.naming.ldap.attributes.binary";155156// Delete old RDN during modifyDN: "true", "false"157private static final String DELETE_RDN = "java.naming.ldap.deleteRDN";158159// De-reference aliases: "never", "searching", "finding", "always"160private static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";161162// Return only attribute types (no values)163private static final String TYPES_ONLY = "java.naming.ldap.typesOnly";164165// Separator character for encoding Reference's RefAddrs; default is '#'166private static final String REF_SEPARATOR = "java.naming.ldap.ref.separator";167168// Socket factory169private static final String SOCKET_FACTORY = "java.naming.ldap.factory.socket";170171// Bind Controls (used by LdapReferralException)172static final String BIND_CONTROLS = "java.naming.ldap.control.connect";173174private static final String REFERRAL_LIMIT =175"java.naming.ldap.referral.limit";176177// trace BER (java.io.OutputStream)178private static final String TRACE_BER = "com.sun.jndi.ldap.trace.ber";179180// Get around Netscape Schema Bugs181private static final String NETSCAPE_SCHEMA_BUG =182"com.sun.jndi.ldap.netscape.schemaBugs";183// deprecated184private static final String OLD_NETSCAPE_SCHEMA_BUG =185"com.sun.naming.netscape.schemaBugs"; // for backward compatibility186187// Timeout for socket connect188private static final String CONNECT_TIMEOUT =189"com.sun.jndi.ldap.connect.timeout";190191// Timeout for reading responses192private static final String READ_TIMEOUT =193"com.sun.jndi.ldap.read.timeout";194195// Environment property for connection pooling196private static final String ENABLE_POOL = "com.sun.jndi.ldap.connect.pool";197198// Environment property for the domain name (derived from this context's DN)199private static final String DOMAIN_NAME = "com.sun.jndi.ldap.domainname";200201// Block until the first search reply is received202private static final String WAIT_FOR_REPLY =203"com.sun.jndi.ldap.search.waitForReply";204205// Size of the queue of unprocessed search replies206private static final String REPLY_QUEUE_SIZE =207"com.sun.jndi.ldap.search.replyQueueSize";208209// System and environment property name to control allowed list of210// authentication mechanisms: "all" or "" or "mech1,mech2,...,mechN"211// "all": allow all mechanisms,212// "": allow none213// or comma separated list of allowed authentication mechanisms214// Note: "none" or "anonymous" are always allowed.215private static final String ALLOWED_MECHS_SP =216"jdk.jndi.ldap.mechsAllowedToSendCredentials";217218// System property value219private static final String ALLOWED_MECHS_SP_VALUE =220getMechsAllowedToSendCredentials();221222// Set of authentication mechanisms allowed by the system property223private static final Set<String> MECHS_ALLOWED_BY_SP =224getMechsFromPropertyValue(ALLOWED_MECHS_SP_VALUE);225226// The message to use in NamingException if the transmission of plain credentials are not allowed227private static final String UNSECURED_CRED_TRANSMIT_MSG =228"Transmission of credentials over unsecured connection is not allowed";229230// ----------------- Fields that don't change -----------------------231private static final NameParser parser = new LdapNameParser();232233// controls that Provider needs234private static final ControlFactory myResponseControlFactory =235new DefaultResponseControlFactory();236private static final Control manageReferralControl =237new ManageReferralControl(false);238239private static final HierMemDirCtx EMPTY_SCHEMA = new HierMemDirCtx();240static {241EMPTY_SCHEMA.setReadOnly(242new SchemaViolationException("Cannot update schema object"));243}244245// ------------ Package private instance variables ----------------246// Cannot be private; used by enums247248// ------- Inherited by derived context instances249250int port_number; // port number of server251String hostname = null; // host name of server (no brackets252// for IPv6 literals)253LdapClient clnt = null; // connection handle254Hashtable<String, java.lang.Object> envprops = null; // environment properties of context255int handleReferrals = DEFAULT_REFERRAL_MODE; // how referral is handled256boolean hasLdapsScheme = false; // true if the context was created257// using an LDAPS URL.258259// ------- Not inherited by derived context instances260261String currentDN; // DN of this context262Name currentParsedDN; // DN of this context263Vector<Control> respCtls = null; // Response controls read264Control[] reqCtls = null; // Controls to be sent with each request265// Used to track if context was seen to be secured with STARTTLS extended operation266volatile boolean contextSeenStartTlsEnabled;267268// ------------- Private instance variables ------------------------269270// ------- Inherited by derived context instances271272private OutputStream trace = null; // output stream for BER debug output273private boolean netscapeSchemaBug = false; // workaround274private Control[] bindCtls = null; // Controls to be sent with LDAP "bind"275private int referralHopLimit = DEFAULT_REFERRAL_LIMIT; // max referral276private Hashtable<String, DirContext> schemaTrees = null; // schema root of this context277private int batchSize = DEFAULT_BATCH_SIZE; // batch size for search results278private boolean deleteRDN = DEFAULT_DELETE_RDN; // delete the old RDN when modifying DN279private boolean typesOnly = DEFAULT_TYPES_ONLY; // return attribute types (no values)280private int derefAliases = DEFAULT_DEREF_ALIASES;// de-reference alias entries during searching281private char addrEncodingSeparator = DEFAULT_REF_SEPARATOR; // encoding RefAddr282283private Hashtable<String, Boolean> binaryAttrs = null; // attr values returned as byte[]284private int connectTimeout = -1; // no timeout value285private int readTimeout = -1; // no timeout value286private boolean waitForReply = true; // wait for search response287private int replyQueueSize = -1; // unlimited queue size288private boolean useSsl = false; // true if SSL protocol is active289private boolean useDefaultPortNumber = false; // no port number was supplied290291// ------- Not inherited by derived context instances292293// True if this context was created by another LdapCtx.294private boolean parentIsLdapCtx = false; // see composeName()295296private int hopCount = 1; // current referral hop count297private String url = null; // URL of context; see getURL()298private EventSupport eventSupport; // Event support helper for this ctx299private boolean unsolicited = false; // if there unsolicited listeners300private boolean sharable = true; // can share connection with other ctx301302// -------------- Constructors -----------------------------------303304@SuppressWarnings("unchecked")305public LdapCtx(String dn, String host, int port_number,306Hashtable<?,?> props,307boolean useSsl) throws NamingException {308309this.useSsl = this.hasLdapsScheme = useSsl;310311if (props != null) {312envprops = (Hashtable<String, java.lang.Object>) props.clone();313314// SSL env prop overrides the useSsl argument315if ("ssl".equals(envprops.get(Context.SECURITY_PROTOCOL))) {316this.useSsl = true;317}318319// %%% These are only examined when the context is created320// %%% because they are only for debugging or workaround purposes.321trace = (OutputStream)envprops.get(TRACE_BER);322323if (props.get(NETSCAPE_SCHEMA_BUG) != null ||324props.get(OLD_NETSCAPE_SCHEMA_BUG) != null) {325netscapeSchemaBug = true;326}327}328329currentDN = (dn != null) ? dn : "";330currentParsedDN = parser.parse(currentDN);331332hostname = (host != null && host.length() > 0) ? host : DEFAULT_HOST;333if (hostname.charAt(0) == '[') {334hostname = hostname.substring(1, hostname.length() - 1);335}336337if (port_number > 0) {338this.port_number = port_number;339} else {340this.port_number = this.useSsl ? DEFAULT_SSL_PORT : DEFAULT_PORT;341this.useDefaultPortNumber = true;342}343344schemaTrees = new Hashtable<>(11, 0.75f);345initEnv();346try {347connect(false);348} catch (NamingException e) {349try {350close();351} catch (Exception e2) {352// Nothing353}354throw e;355}356}357358LdapCtx(LdapCtx existing, String newDN) throws NamingException {359useSsl = existing.useSsl;360hasLdapsScheme = existing.hasLdapsScheme;361useDefaultPortNumber = existing.useDefaultPortNumber;362363hostname = existing.hostname;364port_number = existing.port_number;365currentDN = newDN;366if (existing.currentDN == currentDN) {367currentParsedDN = existing.currentParsedDN;368} else {369currentParsedDN = parser.parse(currentDN);370}371372envprops = existing.envprops;373schemaTrees = existing.schemaTrees;374375clnt = existing.clnt;376clnt.incRefCount();377378parentIsLdapCtx = ((newDN == null || newDN.equals(existing.currentDN))379? existing.parentIsLdapCtx380: true);381382// inherit these debugging/workaround flags383trace = existing.trace;384netscapeSchemaBug = existing.netscapeSchemaBug;385386initEnv();387}388389public LdapContext newInstance(Control[] reqCtls) throws NamingException {390391LdapContext clone = new LdapCtx(this, currentDN);392393// Connection controls are inherited from environment394395// Set clone's request controls396// setRequestControls() will clone reqCtls397clone.setRequestControls(reqCtls);398return clone;399}400401// --------------- Namespace Updates ---------------------402// -- bind/rebind/unbind403// -- rename404// -- createSubcontext/destroySubcontext405406protected void c_bind(Name name, Object obj, Continuation cont)407throws NamingException {408c_bind(name, obj, null, cont);409}410411/*412* attrs == null413* if obj is DirContext, attrs = obj.getAttributes()414* if attrs == null && obj == null415* disallow (cannot determine objectclass to use)416* if obj == null417* just create entry using attrs418* else419* objAttrs = create attributes for representing obj420* attrs += objAttrs421* create entry using attrs422*/423protected void c_bind(Name name, Object obj, Attributes attrs,424Continuation cont)425throws NamingException {426427cont.setError(this, name);428429Attributes inputAttrs = attrs; // Attributes supplied by caller430try {431ensureOpen();432433if (obj == null) {434if (attrs == null) {435throw new IllegalArgumentException(436"cannot bind null object with no attributes");437}438} else {439attrs = Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs,440false, name, this, envprops); // not cloned441}442443String newDN = fullyQualifiedName(name);444attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);445LdapEntry entry = new LdapEntry(newDN, attrs);446447LdapResult answer = clnt.add(entry, reqCtls);448respCtls = answer.resControls; // retrieve response controls449450if (answer.status != LdapClient.LDAP_SUCCESS) {451processReturnCode(answer, name);452}453454} catch (LdapReferralException e) {455if (handleReferrals == LdapClient.LDAP_REF_THROW)456throw cont.fillInException(e);457458// process the referrals sequentially459while (true) {460461LdapReferralContext refCtx =462(LdapReferralContext)e.getReferralContext(envprops, bindCtls);463464// repeat the original operation at the new context465try {466467refCtx.bind(name, obj, inputAttrs);468return;469470} catch (LdapReferralException re) {471e = re;472continue;473474} finally {475// Make sure we close referral context476refCtx.close();477}478}479480} catch (IOException e) {481NamingException e2 = new CommunicationException(e.getMessage());482e2.setRootCause(e);483throw cont.fillInException(e2);484485} catch (NamingException e) {486throw cont.fillInException(e);487}488}489490protected void c_rebind(Name name, Object obj, Continuation cont)491throws NamingException {492c_rebind(name, obj, null, cont);493}494495496/*497* attrs == null498* if obj is DirContext, attrs = obj.getAttributes().499* if attrs == null500* leave any existing attributes alone501* (set attrs = {objectclass=top} if object doesn't exist)502* else503* replace all existing attributes with attrs504* if obj == null505* just create entry using attrs506* else507* objAttrs = create attributes for representing obj508* attrs += objAttrs509* create entry using attrs510*/511protected void c_rebind(Name name, Object obj, Attributes attrs,512Continuation cont) throws NamingException {513514cont.setError(this, name);515516Attributes inputAttrs = attrs;517518try {519Attributes origAttrs = null;520521// Check if name is bound522try {523origAttrs = c_getAttributes(name, null, cont);524} catch (NameNotFoundException e) {}525526// Name not bound, just add it527if (origAttrs == null) {528c_bind(name, obj, attrs, cont);529return;530}531532// there's an object there already, need to figure out533// what to do about its attributes534535if (attrs == null && obj instanceof DirContext) {536attrs = ((DirContext)obj).getAttributes("");537}538Attributes keepAttrs = (Attributes)origAttrs.clone();539540if (attrs == null) {541// we're not changing any attrs, leave old attributes alone542543// Remove Java-related object classes from objectclass attribute544Attribute origObjectClass =545origAttrs.get(Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS]);546547if (origObjectClass != null) {548// clone so that keepAttrs is not affected549origObjectClass = (Attribute)origObjectClass.clone();550for (int i = 0; i < Obj.JAVA_OBJECT_CLASSES.length; i++) {551origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES_LOWER[i]);552origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES[i]);553}554// update;555origAttrs.put(origObjectClass);556}557558// remove all Java-related attributes except objectclass559for (int i = 1; i < Obj.JAVA_ATTRIBUTES.length; i++) {560origAttrs.remove(Obj.JAVA_ATTRIBUTES[i]);561}562563attrs = origAttrs;564}565if (obj != null) {566attrs =567Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs,568inputAttrs != attrs, name, this, envprops);569}570571String newDN = fullyQualifiedName(name);572// remove entry573LdapResult answer = clnt.delete(newDN, reqCtls);574respCtls = answer.resControls; // retrieve response controls575576if (answer.status != LdapClient.LDAP_SUCCESS) {577processReturnCode(answer, name);578return;579}580581Exception addEx = null;582try {583attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);584585// add it back using updated attrs586LdapEntry entry = new LdapEntry(newDN, attrs);587answer = clnt.add(entry, reqCtls);588if (answer.resControls != null) {589respCtls = appendVector(respCtls, answer.resControls);590}591} catch (NamingException | IOException ae) {592addEx = ae;593}594595if ((addEx != null && !(addEx instanceof LdapReferralException)) ||596answer.status != LdapClient.LDAP_SUCCESS) {597// Attempt to restore old entry598LdapResult answer2 =599clnt.add(new LdapEntry(newDN, keepAttrs), reqCtls);600if (answer2.resControls != null) {601respCtls = appendVector(respCtls, answer2.resControls);602}603604if (addEx == null) {605processReturnCode(answer, name);606}607}608609// Rethrow exception610if (addEx instanceof NamingException) {611throw (NamingException)addEx;612} else if (addEx instanceof IOException) {613throw (IOException)addEx;614}615616} catch (LdapReferralException e) {617if (handleReferrals == LdapClient.LDAP_REF_THROW)618throw cont.fillInException(e);619620// process the referrals sequentially621while (true) {622623LdapReferralContext refCtx =624(LdapReferralContext)e.getReferralContext(envprops, bindCtls);625626// repeat the original operation at the new context627try {628629refCtx.rebind(name, obj, inputAttrs);630return;631632} catch (LdapReferralException re) {633e = re;634continue;635636} finally {637// Make sure we close referral context638refCtx.close();639}640}641642} catch (IOException e) {643NamingException e2 = new CommunicationException(e.getMessage());644e2.setRootCause(e);645throw cont.fillInException(e2);646647} catch (NamingException e) {648throw cont.fillInException(e);649}650}651652protected void c_unbind(Name name, Continuation cont)653throws NamingException {654cont.setError(this, name);655656try {657ensureOpen();658659String fname = fullyQualifiedName(name);660LdapResult answer = clnt.delete(fname, reqCtls);661respCtls = answer.resControls; // retrieve response controls662663adjustDeleteStatus(fname, answer);664665if (answer.status != LdapClient.LDAP_SUCCESS) {666processReturnCode(answer, name);667}668669} catch (LdapReferralException e) {670if (handleReferrals == LdapClient.LDAP_REF_THROW)671throw cont.fillInException(e);672673// process the referrals sequentially674while (true) {675676LdapReferralContext refCtx =677(LdapReferralContext)e.getReferralContext(envprops, bindCtls);678679// repeat the original operation at the new context680try {681682refCtx.unbind(name);683return;684685} catch (LdapReferralException re) {686e = re;687continue;688689} finally {690// Make sure we close referral context691refCtx.close();692}693}694695} catch (IOException e) {696NamingException e2 = new CommunicationException(e.getMessage());697e2.setRootCause(e);698throw cont.fillInException(e2);699700} catch (NamingException e) {701throw cont.fillInException(e);702}703}704705protected void c_rename(Name oldName, Name newName, Continuation cont)706throws NamingException707{708Name oldParsed, newParsed;709Name oldParent, newParent;710String newRDN = null;711String newSuperior = null;712713// assert (oldName instanceOf CompositeName);714715cont.setError(this, oldName);716717try {718ensureOpen();719720// permit oldName to be empty (for processing referral contexts)721if (oldName.isEmpty()) {722oldParent = parser.parse("");723} else {724oldParsed = parser.parse(oldName.get(0)); // extract DN & parse725oldParent = oldParsed.getPrefix(oldParsed.size() - 1);726}727728if (newName instanceof CompositeName) {729newParsed = parser.parse(newName.get(0)); // extract DN & parse730} else {731newParsed = newName; // CompoundName/LdapName is already parsed732}733newParent = newParsed.getPrefix(newParsed.size() - 1);734735if(!oldParent.equals(newParent)) {736if (!clnt.isLdapv3) {737throw new InvalidNameException(738"LDAPv2 doesn't support changing " +739"the parent as a result of a rename");740} else {741newSuperior = fullyQualifiedName(newParent.toString());742}743}744745newRDN = newParsed.get(newParsed.size() - 1);746747LdapResult answer = clnt.moddn(fullyQualifiedName(oldName),748newRDN,749deleteRDN,750newSuperior,751reqCtls);752respCtls = answer.resControls; // retrieve response controls753754if (answer.status != LdapClient.LDAP_SUCCESS) {755processReturnCode(answer, oldName);756}757758} catch (LdapReferralException e) {759760// Record the new RDN (for use after the referral is followed).761e.setNewRdn(newRDN);762763// Cannot continue when a referral has been received and a764// newSuperior name was supplied (because the newSuperior is765// relative to a naming context BEFORE the referral is followed).766if (newSuperior != null) {767PartialResultException pre = new PartialResultException(768"Cannot continue referral processing when newSuperior is " +769"nonempty: " + newSuperior);770pre.setRootCause(cont.fillInException(e));771throw cont.fillInException(pre);772}773774if (handleReferrals == LdapClient.LDAP_REF_THROW)775throw cont.fillInException(e);776777// process the referrals sequentially778while (true) {779780LdapReferralContext refCtx =781(LdapReferralContext)e.getReferralContext(envprops, bindCtls);782783// repeat the original operation at the new context784try {785786refCtx.rename(oldName, newName);787return;788789} catch (LdapReferralException re) {790e = re;791continue;792793} finally {794// Make sure we close referral context795refCtx.close();796}797}798799} catch (IOException e) {800NamingException e2 = new CommunicationException(e.getMessage());801e2.setRootCause(e);802throw cont.fillInException(e2);803804} catch (NamingException e) {805throw cont.fillInException(e);806}807}808809protected Context c_createSubcontext(Name name, Continuation cont)810throws NamingException {811return c_createSubcontext(name, null, cont);812}813814protected DirContext c_createSubcontext(Name name, Attributes attrs,815Continuation cont)816throws NamingException {817cont.setError(this, name);818819Attributes inputAttrs = attrs;820try {821ensureOpen();822if (attrs == null) {823// add structural objectclass; name needs to have "cn"824Attribute oc = new BasicAttribute(825Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS],826Obj.JAVA_OBJECT_CLASSES[Obj.STRUCTURAL]);827oc.add("top");828attrs = new BasicAttributes(true); // case ignore829attrs.put(oc);830}831String newDN = fullyQualifiedName(name);832attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);833834LdapEntry entry = new LdapEntry(newDN, attrs);835836LdapResult answer = clnt.add(entry, reqCtls);837respCtls = answer.resControls; // retrieve response controls838839if (answer.status != LdapClient.LDAP_SUCCESS) {840processReturnCode(answer, name);841return null;842}843844// creation successful, get back live object845return new LdapCtx(this, newDN);846847} catch (LdapReferralException e) {848if (handleReferrals == LdapClient.LDAP_REF_THROW)849throw cont.fillInException(e);850851// process the referrals sequentially852while (true) {853854LdapReferralContext refCtx =855(LdapReferralContext)e.getReferralContext(envprops, bindCtls);856857// repeat the original operation at the new context858try {859860return refCtx.createSubcontext(name, inputAttrs);861862} catch (LdapReferralException re) {863e = re;864continue;865866} finally {867// Make sure we close referral context868refCtx.close();869}870}871872} catch (IOException e) {873NamingException e2 = new CommunicationException(e.getMessage());874e2.setRootCause(e);875throw cont.fillInException(e2);876877} catch (NamingException e) {878throw cont.fillInException(e);879}880}881882protected void c_destroySubcontext(Name name, Continuation cont)883throws NamingException {884cont.setError(this, name);885886try {887ensureOpen();888889String fname = fullyQualifiedName(name);890LdapResult answer = clnt.delete(fname, reqCtls);891respCtls = answer.resControls; // retrieve response controls892893adjustDeleteStatus(fname, answer);894895if (answer.status != LdapClient.LDAP_SUCCESS) {896processReturnCode(answer, name);897}898899} catch (LdapReferralException e) {900if (handleReferrals == LdapClient.LDAP_REF_THROW)901throw cont.fillInException(e);902903// process the referrals sequentially904while (true) {905906LdapReferralContext refCtx =907(LdapReferralContext)e.getReferralContext(envprops, bindCtls);908909// repeat the original operation at the new context910try {911912refCtx.destroySubcontext(name);913return;914} catch (LdapReferralException re) {915e = re;916continue;917} finally {918// Make sure we close referral context919refCtx.close();920}921}922} catch (IOException e) {923NamingException e2 = new CommunicationException(e.getMessage());924e2.setRootCause(e);925throw cont.fillInException(e2);926} catch (NamingException e) {927throw cont.fillInException(e);928}929}930931/**932* Adds attributes from RDN to attrs if not already present.933* Note that if attrs already contains an attribute by the same name,934* or if the distinguished name is empty, then leave attrs unchanged.935*936* @param dn The non-null DN of the entry to add937* @param attrs The non-null attributes of entry to add938* @param directUpdate Whether attrs can be updated directly939* @return Non-null attributes with attributes from the RDN added940*/941private static Attributes addRdnAttributes(String dn, Attributes attrs,942boolean directUpdate) throws NamingException {943944// Handle the empty name945if (dn.isEmpty()) {946return attrs;947}948949// Parse string name into list of RDNs950List<Rdn> rdnList = (new LdapName(dn)).getRdns();951952// Get leaf RDN953Rdn rdn = rdnList.get(rdnList.size() - 1);954Attributes nameAttrs = rdn.toAttributes();955956// Add attributes of RDN to attrs if not already there957NamingEnumeration<? extends Attribute> enum_ = nameAttrs.getAll();958Attribute nameAttr;959while (enum_.hasMore()) {960nameAttr = enum_.next();961962// If attrs already has the attribute, don't change or add to it963if (attrs.get(nameAttr.getID()) == null) {964965/**966* When attrs.isCaseIgnored() is false, attrs.get() will967* return null when the case mis-matches for otherwise968* equal attrIDs.969* As the attrIDs' case is irrelevant for LDAP, ignore970* the case of attrIDs even when attrs.isCaseIgnored() is971* false. This is done by explicitly comparing the elements in972* the enumeration of IDs with their case ignored.973*/974if (!attrs.isCaseIgnored() &&975containsIgnoreCase(attrs.getIDs(), nameAttr.getID())) {976continue;977}978979if (!directUpdate) {980attrs = (Attributes)attrs.clone();981directUpdate = true;982}983attrs.put(nameAttr);984}985}986987return attrs;988}989990991private static boolean containsIgnoreCase(NamingEnumeration<String> enumStr,992String str) throws NamingException {993String strEntry;994995while (enumStr.hasMore()) {996strEntry = enumStr.next();997if (strEntry.equalsIgnoreCase(str)) {998return true;999}1000}1001return false;1002}100310041005private void adjustDeleteStatus(String fname, LdapResult answer) {1006if (answer.status == LdapClient.LDAP_NO_SUCH_OBJECT &&1007answer.matchedDN != null) {1008try {1009// %%% RL: are there any implications for referrals?10101011Name orig = parser.parse(fname);1012Name matched = parser.parse(answer.matchedDN);1013if ((orig.size() - matched.size()) == 1)1014answer.status = LdapClient.LDAP_SUCCESS;1015} catch (NamingException e) {}1016}1017}10181019/*1020* Append the second Vector onto the first Vector1021* (v2 must be non-null)1022*/1023private static <T> Vector<T> appendVector(Vector<T> v1, Vector<T> v2) {1024if (v1 == null) {1025v1 = v2;1026} else {1027for (int i = 0; i < v2.size(); i++) {1028v1.addElement(v2.elementAt(i));1029}1030}1031return v1;1032}10331034// ------------- Lookups and Browsing -------------------------1035// lookup/lookupLink1036// list/listBindings10371038protected Object c_lookupLink(Name name, Continuation cont)1039throws NamingException {1040return c_lookup(name, cont);1041}10421043protected Object c_lookup(Name name, Continuation cont)1044throws NamingException {1045cont.setError(this, name);1046Object obj = null;1047Attributes attrs;10481049try {1050SearchControls cons = new SearchControls();1051cons.setSearchScope(SearchControls.OBJECT_SCOPE);1052cons.setReturningAttributes(null); // ask for all attributes1053cons.setReturningObjFlag(true); // need values to construct obj10541055LdapResult answer = doSearchOnce(name, "(objectClass=*)", cons, true);1056respCtls = answer.resControls; // retrieve response controls10571058// should get back 1 SearchResponse and 1 SearchResult10591060if (answer.status != LdapClient.LDAP_SUCCESS) {1061processReturnCode(answer, name);1062}10631064if (answer.entries == null || answer.entries.size() != 1) {1065// found it but got no attributes1066attrs = new BasicAttributes(LdapClient.caseIgnore);1067} else {1068LdapEntry entry = answer.entries.elementAt(0);1069attrs = entry.attributes;10701071Vector<Control> entryCtls = entry.respCtls; // retrieve entry controls1072if (entryCtls != null) {1073appendVector(respCtls, entryCtls); // concatenate controls1074}1075}10761077if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) {1078// serialized object or object reference1079obj = Obj.decodeObject(attrs);1080}1081if (obj == null) {1082obj = new LdapCtx(this, fullyQualifiedName(name));1083}1084} catch (LdapReferralException e) {1085if (handleReferrals == LdapClient.LDAP_REF_THROW)1086throw cont.fillInException(e);10871088// process the referrals sequentially1089while (true) {10901091LdapReferralContext refCtx =1092(LdapReferralContext)e.getReferralContext(envprops, bindCtls);1093// repeat the original operation at the new context1094try {10951096return refCtx.lookup(name);10971098} catch (LdapReferralException re) {1099e = re;1100continue;11011102} finally {1103// Make sure we close referral context1104refCtx.close();1105}1106}11071108} catch (NamingException e) {1109throw cont.fillInException(e);1110}11111112try {1113return DirectoryManager.getObjectInstance(obj, name,1114this, envprops, attrs);11151116} catch (NamingException e) {1117throw cont.fillInException(e);11181119} catch (Exception e) {1120NamingException e2 = new NamingException(1121"problem generating object using object factory");1122e2.setRootCause(e);1123throw cont.fillInException(e2);1124}1125}11261127protected NamingEnumeration<NameClassPair> c_list(Name name, Continuation cont)1128throws NamingException {1129SearchControls cons = new SearchControls();1130String[] classAttrs = new String[2];11311132classAttrs[0] = Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS];1133classAttrs[1] = Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME];1134cons.setReturningAttributes(classAttrs);11351136// set this flag to override the typesOnly flag1137cons.setReturningObjFlag(true);11381139cont.setError(this, name);11401141LdapResult answer = null;11421143try {1144answer = doSearch(name, "(objectClass=*)", cons, true, true);11451146// list result may contain continuation references1147if ((answer.status != LdapClient.LDAP_SUCCESS) ||1148(answer.referrals != null)) {1149processReturnCode(answer, name);1150}11511152return new LdapNamingEnumeration(this, answer, name, cont);11531154} catch (LdapReferralException e) {1155if (handleReferrals == LdapClient.LDAP_REF_THROW)1156throw cont.fillInException(e);11571158// process the referrals sequentially1159while (true) {11601161LdapReferralContext refCtx =1162(LdapReferralContext)e.getReferralContext(envprops, bindCtls);11631164// repeat the original operation at the new context1165try {11661167return refCtx.list(name);11681169} catch (LdapReferralException re) {1170e = re;1171continue;11721173} finally {1174// Make sure we close referral context1175refCtx.close();1176}1177}11781179} catch (LimitExceededException e) {1180LdapNamingEnumeration res =1181new LdapNamingEnumeration(this, answer, name, cont);11821183res.setNamingException(1184(LimitExceededException)cont.fillInException(e));1185return res;11861187} catch (PartialResultException e) {1188LdapNamingEnumeration res =1189new LdapNamingEnumeration(this, answer, name, cont);11901191res.setNamingException(1192(PartialResultException)cont.fillInException(e));1193return res;11941195} catch (NamingException e) {1196throw cont.fillInException(e);1197}1198}11991200protected NamingEnumeration<Binding> c_listBindings(Name name, Continuation cont)1201throws NamingException {12021203SearchControls cons = new SearchControls();1204cons.setReturningAttributes(null); // ask for all attributes1205cons.setReturningObjFlag(true); // need values to construct obj12061207cont.setError(this, name);12081209LdapResult answer = null;12101211try {1212answer = doSearch(name, "(objectClass=*)", cons, true, true);12131214// listBindings result may contain continuation references1215if ((answer.status != LdapClient.LDAP_SUCCESS) ||1216(answer.referrals != null)) {1217processReturnCode(answer, name);1218}12191220return new LdapBindingEnumeration(this, answer, name, cont);12211222} catch (LdapReferralException e) {1223if (handleReferrals == LdapClient.LDAP_REF_THROW)1224throw cont.fillInException(e);12251226// process the referrals sequentially1227while (true) {1228@SuppressWarnings("unchecked")1229LdapReferralContext refCtx =1230(LdapReferralContext)e.getReferralContext(envprops, bindCtls);12311232// repeat the original operation at the new context1233try {12341235return refCtx.listBindings(name);12361237} catch (LdapReferralException re) {1238e = re;1239continue;12401241} finally {1242// Make sure we close referral context1243refCtx.close();1244}1245}1246} catch (LimitExceededException e) {1247LdapBindingEnumeration res =1248new LdapBindingEnumeration(this, answer, name, cont);12491250res.setNamingException(cont.fillInException(e));1251return res;12521253} catch (PartialResultException e) {1254LdapBindingEnumeration res =1255new LdapBindingEnumeration(this, answer, name, cont);12561257res.setNamingException(cont.fillInException(e));1258return res;12591260} catch (NamingException e) {1261throw cont.fillInException(e);1262}1263}12641265// --------------- Name-related Methods -----------------------1266// -- getNameParser/getNameInNamespace/composeName12671268protected NameParser c_getNameParser(Name name, Continuation cont)1269throws NamingException1270{1271// ignore name, always return same parser1272cont.setSuccess();1273return parser;1274}12751276public String getNameInNamespace() {1277return currentDN;1278}12791280public Name composeName(Name name, Name prefix)1281throws NamingException1282{1283Name result;12841285// Handle compound names. A pair of LdapNames is an easy case.1286if ((name instanceof LdapName) && (prefix instanceof LdapName)) {1287result = (Name)(prefix.clone());1288result.addAll(name);1289return new CompositeName().add(result.toString());1290}1291if (!(name instanceof CompositeName)) {1292name = new CompositeName().add(name.toString());1293}1294if (!(prefix instanceof CompositeName)) {1295prefix = new CompositeName().add(prefix.toString());1296}12971298int prefixLast = prefix.size() - 1;12991300if (name.isEmpty() || prefix.isEmpty() ||1301name.get(0).isEmpty() || prefix.get(prefixLast).isEmpty()) {1302return super.composeName(name, prefix);1303}13041305result = (Name)(prefix.clone());1306result.addAll(name);13071308if (parentIsLdapCtx) {1309String ldapComp = concatNames(result.get(prefixLast + 1),1310result.get(prefixLast));1311result.remove(prefixLast + 1);1312result.remove(prefixLast);1313result.add(prefixLast, ldapComp);1314}1315return result;1316}13171318private String fullyQualifiedName(Name rel) {1319return rel.isEmpty()1320? currentDN1321: fullyQualifiedName(rel.get(0));1322}13231324private String fullyQualifiedName(String rel) {1325return (concatNames(rel, currentDN));1326}13271328// used by LdapSearchEnumeration1329private static String concatNames(String lesser, String greater) {1330if (lesser == null || lesser.isEmpty()) {1331return greater;1332} else if (greater == null || greater.isEmpty()) {1333return lesser;1334} else {1335return (lesser + "," + greater);1336}1337}13381339// --------------- Reading and Updating Attributes1340// getAttributes/modifyAttributes13411342protected Attributes c_getAttributes(Name name, String[] attrIds,1343Continuation cont)1344throws NamingException {1345cont.setError(this, name);13461347SearchControls cons = new SearchControls();1348cons.setSearchScope(SearchControls.OBJECT_SCOPE);1349cons.setReturningAttributes(attrIds);13501351try {1352LdapResult answer =1353doSearchOnce(name, "(objectClass=*)", cons, true);1354respCtls = answer.resControls; // retrieve response controls13551356if (answer.status != LdapClient.LDAP_SUCCESS) {1357processReturnCode(answer, name);1358}13591360if (answer.entries == null || answer.entries.size() != 1) {1361return new BasicAttributes(LdapClient.caseIgnore);1362}13631364// get attributes from result1365LdapEntry entry = answer.entries.elementAt(0);13661367Vector<Control> entryCtls = entry.respCtls; // retrieve entry controls1368if (entryCtls != null) {1369appendVector(respCtls, entryCtls); // concatenate controls1370}13711372// do this so attributes can find their schema1373setParents(entry.attributes, (Name) name.clone());13741375return (entry.attributes);13761377} catch (LdapReferralException e) {1378if (handleReferrals == LdapClient.LDAP_REF_THROW)1379throw cont.fillInException(e);13801381// process the referrals sequentially1382while (true) {13831384LdapReferralContext refCtx =1385(LdapReferralContext)e.getReferralContext(envprops, bindCtls);13861387// repeat the original operation at the new context1388try {13891390return refCtx.getAttributes(name, attrIds);13911392} catch (LdapReferralException re) {1393e = re;1394continue;13951396} finally {1397// Make sure we close referral context1398refCtx.close();1399}1400}14011402} catch (NamingException e) {1403throw cont.fillInException(e);1404}1405}14061407protected void c_modifyAttributes(Name name, int mod_op, Attributes attrs,1408Continuation cont)1409throws NamingException {14101411cont.setError(this, name);14121413try {1414ensureOpen();14151416if (attrs == null || attrs.size() == 0) {1417return; // nothing to do1418}1419String newDN = fullyQualifiedName(name);1420int jmod_op = convertToLdapModCode(mod_op);14211422// construct mod list1423int[] jmods = new int[attrs.size()];1424Attribute[] jattrs = new Attribute[attrs.size()];14251426NamingEnumeration<? extends Attribute> ae = attrs.getAll();1427for(int i = 0; i < jmods.length && ae.hasMore(); i++) {1428jmods[i] = jmod_op;1429jattrs[i] = ae.next();1430}14311432LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls);1433respCtls = answer.resControls; // retrieve response controls14341435if (answer.status != LdapClient.LDAP_SUCCESS) {1436processReturnCode(answer, name);1437return;1438}14391440} catch (LdapReferralException e) {1441if (handleReferrals == LdapClient.LDAP_REF_THROW)1442throw cont.fillInException(e);14431444// process the referrals sequentially1445while (true) {14461447LdapReferralContext refCtx =1448(LdapReferralContext)e.getReferralContext(envprops, bindCtls);14491450// repeat the original operation at the new context1451try {14521453refCtx.modifyAttributes(name, mod_op, attrs);1454return;14551456} catch (LdapReferralException re) {1457e = re;1458continue;14591460} finally {1461// Make sure we close referral context1462refCtx.close();1463}1464}14651466} catch (IOException e) {1467NamingException e2 = new CommunicationException(e.getMessage());1468e2.setRootCause(e);1469throw cont.fillInException(e2);14701471} catch (NamingException e) {1472throw cont.fillInException(e);1473}1474}14751476protected void c_modifyAttributes(Name name, ModificationItem[] mods,1477Continuation cont)1478throws NamingException {1479cont.setError(this, name);14801481try {1482ensureOpen();14831484if (mods == null || mods.length == 0) {1485return; // nothing to do1486}1487String newDN = fullyQualifiedName(name);14881489// construct mod list1490int[] jmods = new int[mods.length];1491Attribute[] jattrs = new Attribute[mods.length];1492ModificationItem mod;1493for (int i = 0; i < jmods.length; i++) {1494mod = mods[i];1495jmods[i] = convertToLdapModCode(mod.getModificationOp());1496jattrs[i] = mod.getAttribute();1497}14981499LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls);1500respCtls = answer.resControls; // retrieve response controls15011502if (answer.status != LdapClient.LDAP_SUCCESS) {1503processReturnCode(answer, name);1504}15051506} catch (LdapReferralException e) {1507if (handleReferrals == LdapClient.LDAP_REF_THROW)1508throw cont.fillInException(e);15091510// process the referrals sequentially1511while (true) {15121513LdapReferralContext refCtx =1514(LdapReferralContext)e.getReferralContext(envprops, bindCtls);15151516// repeat the original operation at the new context1517try {15181519refCtx.modifyAttributes(name, mods);1520return;15211522} catch (LdapReferralException re) {1523e = re;1524continue;15251526} finally {1527// Make sure we close referral context1528refCtx.close();1529}1530}15311532} catch (IOException e) {1533NamingException e2 = new CommunicationException(e.getMessage());1534e2.setRootCause(e);1535throw cont.fillInException(e2);15361537} catch (NamingException e) {1538throw cont.fillInException(e);1539}1540}15411542private static int convertToLdapModCode(int mod_op) {1543switch (mod_op) {1544case DirContext.ADD_ATTRIBUTE:1545return(LdapClient.ADD);15461547case DirContext.REPLACE_ATTRIBUTE:1548return (LdapClient.REPLACE);15491550case DirContext.REMOVE_ATTRIBUTE:1551return (LdapClient.DELETE);15521553default:1554throw new IllegalArgumentException("Invalid modification code");1555}1556}15571558// ------------------- Schema -----------------------15591560protected DirContext c_getSchema(Name name, Continuation cont)1561throws NamingException {1562cont.setError(this, name);1563try {1564return getSchemaTree(name);15651566} catch (NamingException e) {1567throw cont.fillInException(e);1568}1569}15701571protected DirContext c_getSchemaClassDefinition(Name name,1572Continuation cont)1573throws NamingException {1574cont.setError(this, name);15751576try {1577// retrieve the objectClass attribute from LDAP1578Attribute objectClassAttr = c_getAttributes(name,1579new String[]{"objectclass"}, cont).get("objectclass");1580if (objectClassAttr == null || objectClassAttr.size() == 0) {1581return EMPTY_SCHEMA;1582}15831584// retrieve the root of the ObjectClass schema tree1585Context ocSchema = (Context) c_getSchema(name, cont).lookup(1586LdapSchemaParser.OBJECTCLASS_DEFINITION_NAME);15871588// create a context to hold the schema objects representing the object1589// classes1590HierMemDirCtx objectClassCtx = new HierMemDirCtx();1591DirContext objectClassDef;1592String objectClassName;1593for (Enumeration<?> objectClasses = objectClassAttr.getAll();1594objectClasses.hasMoreElements(); ) {1595objectClassName = (String)objectClasses.nextElement();1596// %%% Should we fail if not found, or just continue?1597objectClassDef = (DirContext)ocSchema.lookup(objectClassName);1598objectClassCtx.bind(objectClassName, objectClassDef);1599}16001601// Make context read-only1602objectClassCtx.setReadOnly(1603new SchemaViolationException("Cannot update schema object"));1604return (DirContext)objectClassCtx;16051606} catch (NamingException e) {1607throw cont.fillInException(e);1608}1609}16101611/*1612* getSchemaTree first looks to see if we have already built a1613* schema tree for the given entry. If not, it builds a new one and1614* stores it in our private hash table1615*/1616private DirContext getSchemaTree(Name name) throws NamingException {1617String subschemasubentry = getSchemaEntry(name, true);16181619DirContext schemaTree = schemaTrees.get(subschemasubentry);16201621if(schemaTree==null) {1622if(debug){System.err.println("LdapCtx: building new schema tree " + this);}1623schemaTree = buildSchemaTree(subschemasubentry);1624schemaTrees.put(subschemasubentry, schemaTree);1625}16261627return schemaTree;1628}16291630/*1631* buildSchemaTree builds the schema tree corresponding to the1632* given subschemasubentree1633*/1634private DirContext buildSchemaTree(String subschemasubentry)1635throws NamingException {16361637// get the schema entry itself1638// DO ask for return object here because we need it to1639// create context. Since asking for all attrs, we won't1640// be transmitting any specific attrIDs (like Java-specific ones).1641SearchControls constraints = new1642SearchControls(SearchControls.OBJECT_SCOPE,16430, 0, /* count and time limits */1644SCHEMA_ATTRIBUTES /* return schema attrs */,1645true /* return obj */,1646false /*deref link */ );16471648Name sse = (new CompositeName()).add(subschemasubentry);1649NamingEnumeration<SearchResult> results =1650searchAux(sse, "(objectClass=subschema)", constraints,1651false, true, new Continuation());16521653if(!results.hasMore()) {1654throw new OperationNotSupportedException(1655"Cannot get read subschemasubentry: " + subschemasubentry);1656}1657SearchResult result = results.next();1658results.close();16591660Object obj = result.getObject();1661if(!(obj instanceof LdapCtx)) {1662throw new NamingException(1663"Cannot get schema object as DirContext: " + subschemasubentry);1664}16651666return LdapSchemaCtx.createSchemaTree(envprops, subschemasubentry,1667(LdapCtx)obj /* schema entry */,1668result.getAttributes() /* schema attributes */,1669netscapeSchemaBug);1670}16711672/*1673* getSchemaEntree returns the DN of the subschemasubentree for the1674* given entree. It first looks to see if the given entry has1675* a subschema different from that of the root DIT (by looking for1676* a "subschemasubentry" attribute). If it doesn't find one, it returns1677* the one for the root of the DIT (by looking for the root's1678* "subschemasubentry" attribute).1679*1680* This function is called regardless of the server's version, since1681* an administrator may have setup the server to support client schema1682* queries. If this function tries a search on a v2 server that1683* doesn't support schema, one of these two things will happen:1684* 1) It will get an exception when querying the root DSE1685* 2) It will not find a subschemasubentry on the root DSE1686* If either of these things occur and the server is not v3, we1687* throw OperationNotSupported.1688*1689* the relative flag tells whether the given name is relative to this1690* context.1691*/1692private String getSchemaEntry(Name name, boolean relative)1693throws NamingException {16941695// Asks for operational attribute "subschemasubentry"1696SearchControls constraints = new SearchControls(SearchControls.OBJECT_SCOPE,16970, 0, /* count and time limits */1698new String[]{"subschemasubentry"} /* attr to return */,1699false /* returning obj */,1700false /* deref link */);17011702NamingEnumeration<SearchResult> results;1703try {1704results = searchAux(name, "objectclass=*", constraints, relative,1705true, new Continuation());17061707} catch (NamingException ne) {1708if (!clnt.isLdapv3 && currentDN.length() == 0 && name.isEmpty()) {1709// we got an error looking for a root entry on an ldapv21710// server. The server must not support schema.1711throw new OperationNotSupportedException(1712"Cannot get schema information from server");1713} else {1714throw ne;1715}1716}17171718if (!results.hasMoreElements()) {1719throw new ConfigurationException(1720"Requesting schema of nonexistent entry: " + name);1721}17221723SearchResult result = results.next();1724results.close();17251726Attribute schemaEntryAttr =1727result.getAttributes().get("subschemasubentry");1728//System.err.println("schema entry attrs: " + schemaEntryAttr);17291730if (schemaEntryAttr == null || schemaEntryAttr.size() < 0) {1731if (currentDN.length() == 0 && name.isEmpty()) {1732// the server doesn't have a subschemasubentry in its root DSE.1733// therefore, it doesn't support schema.1734throw new OperationNotSupportedException(1735"Cannot read subschemasubentry of root DSE");1736} else {1737return getSchemaEntry(new CompositeName(), false);1738}1739}17401741return (String)(schemaEntryAttr.get()); // return schema entry name1742}17431744// package-private; used by search enum.1745// Set attributes to point to this context in case some one1746// asked for their schema1747void setParents(Attributes attrs, Name name) throws NamingException {1748NamingEnumeration<? extends Attribute> ae = attrs.getAll();1749while(ae.hasMore()) {1750((LdapAttribute) ae.next()).setParent(this, name);1751}1752}17531754/*1755* Returns the URL associated with this context; used by LdapAttribute1756* after deserialization to get pointer to this context.1757*/1758String getURL() {1759if (url == null) {1760url = LdapURL.toUrlString(hostname, port_number, currentDN,1761hasLdapsScheme);1762}17631764return url;1765}17661767// --------------------- Searches -----------------------------1768protected NamingEnumeration<SearchResult> c_search(Name name,1769Attributes matchingAttributes,1770Continuation cont)1771throws NamingException {1772return c_search(name, matchingAttributes, null, cont);1773}17741775protected NamingEnumeration<SearchResult> c_search(Name name,1776Attributes matchingAttributes,1777String[] attributesToReturn,1778Continuation cont)1779throws NamingException {1780SearchControls cons = new SearchControls();1781cons.setReturningAttributes(attributesToReturn);1782String filter;1783try {1784filter = SearchFilter.format(matchingAttributes);1785} catch (NamingException e) {1786cont.setError(this, name);1787throw cont.fillInException(e);1788}1789return c_search(name, filter, cons, cont);1790}17911792protected NamingEnumeration<SearchResult> c_search(Name name,1793String filter,1794SearchControls cons,1795Continuation cont)1796throws NamingException {1797return searchAux(name, filter, cloneSearchControls(cons), true,1798waitForReply, cont);1799}18001801protected NamingEnumeration<SearchResult> c_search(Name name,1802String filterExpr,1803Object[] filterArgs,1804SearchControls cons,1805Continuation cont)1806throws NamingException {1807String strfilter;1808try {1809strfilter = SearchFilter.format(filterExpr, filterArgs);1810} catch (NamingException e) {1811cont.setError(this, name);1812throw cont.fillInException(e);1813}1814return c_search(name, strfilter, cons, cont);1815}18161817// Used by NamingNotifier1818NamingEnumeration<SearchResult> searchAux(Name name,1819String filter,1820SearchControls cons,1821boolean relative,1822boolean waitForReply, Continuation cont) throws NamingException {18231824LdapResult answer = null;1825String[] tokens = new String[2]; // stores ldap compare op. values1826String[] reqAttrs; // remember what was asked18271828if (cons == null) {1829cons = new SearchControls();1830}1831reqAttrs = cons.getReturningAttributes();18321833// if objects are requested then request the Java attributes too1834// so that the objects can be constructed1835if (cons.getReturningObjFlag()) {1836if (reqAttrs != null) {18371838// check for presence of "*" (user attributes wildcard)1839boolean hasWildcard = false;1840for (int i = reqAttrs.length - 1; i >= 0; i--) {1841if (reqAttrs[i].equals("*")) {1842hasWildcard = true;1843break;1844}1845}1846if (! hasWildcard) {1847String[] totalAttrs =1848new String[reqAttrs.length +Obj.JAVA_ATTRIBUTES.length];1849System.arraycopy(reqAttrs, 0, totalAttrs, 0,1850reqAttrs.length);1851System.arraycopy(Obj.JAVA_ATTRIBUTES, 0, totalAttrs,1852reqAttrs.length, Obj.JAVA_ATTRIBUTES.length);18531854cons.setReturningAttributes(totalAttrs);1855}1856}1857}18581859LdapCtx.SearchArgs args =1860new LdapCtx.SearchArgs(name, filter, cons, reqAttrs);18611862cont.setError(this, name);1863try {1864// see if this can be done as a compare, otherwise do a search1865if (searchToCompare(filter, cons, tokens)){1866//System.err.println("compare triggered");1867answer = compare(name, tokens[0], tokens[1]);1868if (! (answer.compareToSearchResult(fullyQualifiedName(name)))){1869processReturnCode(answer, name);1870}1871} else {1872answer = doSearch(name, filter, cons, relative, waitForReply);1873// search result may contain referrals1874processReturnCode(answer, name);1875}1876return new LdapSearchEnumeration(this, answer,1877fullyQualifiedName(name),1878args, cont);18791880} catch (LdapReferralException e) {1881if (handleReferrals == LdapClient.LDAP_REF_THROW)1882throw cont.fillInException(e);18831884// process the referrals sequentially1885while (true) {18861887@SuppressWarnings("unchecked")1888LdapReferralContext refCtx = (LdapReferralContext)1889e.getReferralContext(envprops, bindCtls);18901891// repeat the original operation at the new context1892try {18931894return refCtx.search(name, filter, cons);18951896} catch (LdapReferralException re) {1897e = re;1898continue;18991900} finally {1901// Make sure we close referral context1902refCtx.close();1903}1904}19051906} catch (LimitExceededException e) {1907LdapSearchEnumeration res =1908new LdapSearchEnumeration(this, answer, fullyQualifiedName(name),1909args, cont);1910res.setNamingException(e);1911return res;19121913} catch (PartialResultException e) {1914LdapSearchEnumeration res =1915new LdapSearchEnumeration(this, answer, fullyQualifiedName(name),1916args, cont);19171918res.setNamingException(e);1919return res;19201921} catch (IOException e) {1922NamingException e2 = new CommunicationException(e.getMessage());1923e2.setRootCause(e);1924throw cont.fillInException(e2);19251926} catch (NamingException e) {1927throw cont.fillInException(e);1928}1929}193019311932LdapResult getSearchReply(LdapClient eClnt, LdapResult res)1933throws NamingException {1934// ensureOpen() won't work here because1935// session was associated with previous connection19361937// %%% RL: we can actually allow the enumeration to continue1938// using the old handle but other weird things might happen1939// when we hit a referral1940if (clnt != eClnt) {1941throw new CommunicationException(1942"Context's connection changed; unable to continue enumeration");1943}19441945try {1946return eClnt.getSearchReply(batchSize, res, binaryAttrs);1947} catch (IOException e) {1948NamingException e2 = new CommunicationException(e.getMessage());1949e2.setRootCause(e);1950throw e2;1951}1952}19531954// Perform a search. Expect 1 SearchResultEntry and the SearchResultDone.1955private LdapResult doSearchOnce(Name name, String filter,1956SearchControls cons, boolean relative) throws NamingException {19571958int savedBatchSize = batchSize;1959batchSize = 2; // 2 protocol elements19601961LdapResult answer = doSearch(name, filter, cons, relative, true);19621963batchSize = savedBatchSize;1964return answer;1965}19661967private LdapResult doSearch(Name name, String filter, SearchControls cons,1968boolean relative, boolean waitForReply) throws NamingException {1969ensureOpen();1970try {1971int scope;19721973switch (cons.getSearchScope()) {1974case SearchControls.OBJECT_SCOPE:1975scope = LdapClient.SCOPE_BASE_OBJECT;1976break;1977default:1978case SearchControls.ONELEVEL_SCOPE:1979scope = LdapClient.SCOPE_ONE_LEVEL;1980break;1981case SearchControls.SUBTREE_SCOPE:1982scope = LdapClient.SCOPE_SUBTREE;1983break;1984}19851986// If cons.getReturningObjFlag() then caller should already1987// have make sure to request the appropriate attrs19881989String[] retattrs = cons.getReturningAttributes();1990if (retattrs != null && retattrs.length == 0) {1991// Ldap treats null and empty array the same1992// need to replace with single element array1993retattrs = new String[1];1994retattrs[0] = "1.1";1995}19961997String nm = (relative1998? fullyQualifiedName(name)1999: (name.isEmpty()2000? ""2001: name.get(0)));20022003// JNDI unit is milliseconds, LDAP unit is seconds.2004// Zero means no limit.2005int msecLimit = cons.getTimeLimit();2006int secLimit = 0;20072008if (msecLimit > 0) {2009secLimit = (msecLimit / 1000) + 1;2010}20112012LdapResult answer =2013clnt.search(nm,2014scope,2015derefAliases,2016(int)cons.getCountLimit(),2017secLimit,2018cons.getReturningObjFlag() ? false : typesOnly,2019retattrs,2020filter,2021batchSize,2022reqCtls,2023binaryAttrs,2024waitForReply,2025replyQueueSize);2026respCtls = answer.resControls; // retrieve response controls2027return answer;20282029} catch (IOException e) {2030NamingException e2 = new CommunicationException(e.getMessage());2031e2.setRootCause(e);2032throw e2;2033}2034}203520362037/*2038* Certain simple JNDI searches are automatically converted to2039* LDAP compare operations by the LDAP service provider. A search2040* is converted to a compare iff:2041*2042* - the scope is set to OBJECT_SCOPE2043* - the filter string contains a simple assertion: "<type>=<value>"2044* - the returning attributes list is present but empty2045*/20462047// returns true if a search can be carried out as a compare, and sets2048// tokens[0] and tokens[1] to the type and value respectively.2049// e.g. filter "cn=Jon Ruiz" becomes, type "cn" and value "Jon Ruiz"2050// This function uses the documents JNDI Compare example as a model2051// for when to turn a search into a compare.20522053private static boolean searchToCompare(2054String filter,2055SearchControls cons,2056String tokens[]) {20572058// if scope is not object-scope, it's really a search2059if (cons.getSearchScope() != SearchControls.OBJECT_SCOPE) {2060return false;2061}20622063// if attributes are to be returned, it's really a search2064String[] attrs = cons.getReturningAttributes();2065if (attrs == null || attrs.length != 0) {2066return false;2067}20682069// if the filter not a simple assertion, it's really a search2070if (! filterToAssertion(filter, tokens)) {2071return false;2072}20732074// it can be converted to a compare2075return true;2076}20772078// If the supplied filter is a simple assertion i.e. "<type>=<value>"2079// (enclosing parentheses are permitted) then2080// filterToAssertion will return true and pass the type and value as2081// the first and second elements of tokens respectively.2082// precondition: tokens[] must be initialized and be at least of size 2.20832084private static boolean filterToAssertion(String filter, String tokens[]) {20852086// find the left and right half of the assertion2087StringTokenizer assertionTokenizer = new StringTokenizer(filter, "=");20882089if (assertionTokenizer.countTokens() != 2) {2090return false;2091}20922093tokens[0] = assertionTokenizer.nextToken();2094tokens[1] = assertionTokenizer.nextToken();20952096// make sure the value does not contain a wildcard2097if (tokens[1].indexOf('*') != -1) {2098return false;2099}21002101// test for enclosing parenthesis2102boolean hasParens = false;2103int len = tokens[1].length();21042105if ((tokens[0].charAt(0) == '(') &&2106(tokens[1].charAt(len - 1) == ')')) {2107hasParens = true;21082109} else if ((tokens[0].charAt(0) == '(') ||2110(tokens[1].charAt(len - 1) == ')')) {2111return false; // unbalanced2112}21132114// make sure the left and right half are not expressions themselves2115StringTokenizer illegalCharsTokenizer =2116new StringTokenizer(tokens[0], "()&|!=~><*", true);21172118if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {2119return false;2120}21212122illegalCharsTokenizer =2123new StringTokenizer(tokens[1], "()&|!=~><*", true);21242125if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {2126return false;2127}21282129// strip off enclosing parenthesis, if present2130if (hasParens) {2131tokens[0] = tokens[0].substring(1);2132tokens[1] = tokens[1].substring(0, len - 1);2133}21342135return true;2136}21372138private LdapResult compare(Name name, String type, String value)2139throws IOException, NamingException {21402141ensureOpen();2142String nm = fullyQualifiedName(name);21432144LdapResult answer = clnt.compare(nm, type, value, reqCtls);2145respCtls = answer.resControls; // retrieve response controls21462147return answer;2148}21492150private static SearchControls cloneSearchControls(SearchControls cons) {2151if (cons == null) {2152return null;2153}2154String[] retAttrs = cons.getReturningAttributes();2155if (retAttrs != null) {2156String[] attrs = new String[retAttrs.length];2157System.arraycopy(retAttrs, 0, attrs, 0, retAttrs.length);2158retAttrs = attrs;2159}2160return new SearchControls(cons.getSearchScope(),2161cons.getCountLimit(),2162cons.getTimeLimit(),2163retAttrs,2164cons.getReturningObjFlag(),2165cons.getDerefLinkFlag());2166}21672168// -------------- Environment Properties ------------------21692170/**2171* Override with noncloning version.2172*/2173protected Hashtable<String, Object> p_getEnvironment() {2174return envprops;2175}21762177@SuppressWarnings("unchecked") // clone()2178public Hashtable<String, Object> getEnvironment() throws NamingException {2179return (envprops == null2180? new Hashtable<String, Object>(5, 0.75f)2181: (Hashtable<String, Object>)envprops.clone());2182}21832184@SuppressWarnings("unchecked") // clone()2185public Object removeFromEnvironment(String propName)2186throws NamingException {21872188// not there; just return2189if (envprops == null || envprops.get(propName) == null) {2190return null;2191}2192switch (propName) {2193case REF_SEPARATOR:2194addrEncodingSeparator = DEFAULT_REF_SEPARATOR;2195break;2196case TYPES_ONLY:2197typesOnly = DEFAULT_TYPES_ONLY;2198break;2199case DELETE_RDN:2200deleteRDN = DEFAULT_DELETE_RDN;2201break;2202case DEREF_ALIASES:2203derefAliases = DEFAULT_DEREF_ALIASES;2204break;2205case Context.BATCHSIZE:2206batchSize = DEFAULT_BATCH_SIZE;2207break;2208case REFERRAL_LIMIT:2209referralHopLimit = DEFAULT_REFERRAL_LIMIT;2210break;2211case Context.REFERRAL:2212setReferralMode(null, true);2213break;2214case BINARY_ATTRIBUTES:2215setBinaryAttributes(null);2216break;2217case CONNECT_TIMEOUT:2218connectTimeout = -1;2219break;2220case READ_TIMEOUT:2221readTimeout = -1;2222break;2223case WAIT_FOR_REPLY:2224waitForReply = true;2225break;2226case REPLY_QUEUE_SIZE:2227replyQueueSize = -1;2228break;22292230// The following properties affect the connection22312232case Context.SECURITY_PROTOCOL:2233closeConnection(SOFT_CLOSE);2234// De-activate SSL and reset the context's url and port number2235if (useSsl && !hasLdapsScheme) {2236useSsl = false;2237url = null;2238if (useDefaultPortNumber) {2239port_number = DEFAULT_PORT;2240}2241}2242break;2243case VERSION:2244case SOCKET_FACTORY:2245closeConnection(SOFT_CLOSE);2246break;2247case Context.SECURITY_AUTHENTICATION:2248case Context.SECURITY_PRINCIPAL:2249case Context.SECURITY_CREDENTIALS:2250sharable = false;2251break;2252}22532254// Update environment; reconnection will use new props2255envprops = (Hashtable<String, Object>)envprops.clone();2256return envprops.remove(propName);2257}22582259@SuppressWarnings("unchecked") // clone()2260public Object addToEnvironment(String propName, Object propVal)2261throws NamingException {22622263// If adding null, call remove2264if (propVal == null) {2265return removeFromEnvironment(propName);2266}2267switch (propName) {2268case REF_SEPARATOR:2269setRefSeparator((String)propVal);2270break;2271case TYPES_ONLY:2272setTypesOnly((String)propVal);2273break;2274case DELETE_RDN:2275setDeleteRDN((String)propVal);2276break;2277case DEREF_ALIASES:2278setDerefAliases((String)propVal);2279break;2280case Context.BATCHSIZE:2281setBatchSize((String)propVal);2282break;2283case REFERRAL_LIMIT:2284setReferralLimit((String)propVal);2285break;2286case Context.REFERRAL:2287setReferralMode((String)propVal, true);2288break;2289case BINARY_ATTRIBUTES:2290setBinaryAttributes((String)propVal);2291break;2292case CONNECT_TIMEOUT:2293setConnectTimeout((String)propVal);2294break;2295case READ_TIMEOUT:2296setReadTimeout((String)propVal);2297break;2298case WAIT_FOR_REPLY:2299setWaitForReply((String)propVal);2300break;2301case REPLY_QUEUE_SIZE:2302setReplyQueueSize((String)propVal);2303break;23042305// The following properties affect the connection23062307case Context.SECURITY_PROTOCOL:2308closeConnection(SOFT_CLOSE);2309// Activate SSL and reset the context's url and port number2310if ("ssl".equals(propVal)) {2311useSsl = true;2312url = null;2313if (useDefaultPortNumber) {2314port_number = DEFAULT_SSL_PORT;2315}2316}2317break;2318case VERSION:2319case SOCKET_FACTORY:2320closeConnection(SOFT_CLOSE);2321break;2322case Context.SECURITY_AUTHENTICATION:2323case Context.SECURITY_PRINCIPAL:2324case Context.SECURITY_CREDENTIALS:2325sharable = false;2326break;2327}23282329// Update environment; reconnection will use new props2330envprops = (envprops == null2331? new Hashtable<String, Object>(5, 0.75f)2332: (Hashtable<String, Object>)envprops.clone());2333return envprops.put(propName, propVal);2334}23352336/**2337* Sets the URL that created the context in the java.naming.provider.url2338* property.2339*/2340void setProviderUrl(String providerUrl) { // called by LdapCtxFactory2341if (envprops != null) {2342envprops.put(Context.PROVIDER_URL, providerUrl);2343}2344}23452346/**2347* Sets the domain name for the context in the com.sun.jndi.ldap.domainname2348* property.2349* Used for hostname verification by Start TLS2350*/2351void setDomainName(String domainName) { // called by LdapCtxFactory2352if (envprops != null) {2353envprops.put(DOMAIN_NAME, domainName);2354}2355}23562357private void initEnv() throws NamingException {2358if (envprops == null) {2359// Make sure that referrals are to their default2360setReferralMode(null, false);2361return;2362}23632364// Set batch size2365setBatchSize((String)envprops.get(Context.BATCHSIZE));23662367// Set separator used for encoding RefAddr2368setRefSeparator((String)envprops.get(REF_SEPARATOR));23692370// Set whether RDN is removed when renaming object2371setDeleteRDN((String)envprops.get(DELETE_RDN));23722373// Set whether types are returned only2374setTypesOnly((String)envprops.get(TYPES_ONLY));23752376// Set how aliases are dereferenced2377setDerefAliases((String)envprops.get(DEREF_ALIASES));23782379// Set the limit on referral chains2380setReferralLimit((String)envprops.get(REFERRAL_LIMIT));23812382setBinaryAttributes((String)envprops.get(BINARY_ATTRIBUTES));23832384bindCtls = cloneControls((Control[]) envprops.get(BIND_CONTROLS));23852386// set referral handling2387setReferralMode((String)envprops.get(Context.REFERRAL), false);23882389// Set the connect timeout2390setConnectTimeout((String)envprops.get(CONNECT_TIMEOUT));23912392// Set the read timeout2393setReadTimeout((String)envprops.get(READ_TIMEOUT));23942395// Set the flag that controls whether to block until the first reply2396// is received2397setWaitForReply((String)envprops.get(WAIT_FOR_REPLY));23982399// Set the size of the queue of unprocessed search replies2400setReplyQueueSize((String)envprops.get(REPLY_QUEUE_SIZE));24012402// When connection is created, it will use these and other2403// properties from the environment2404}24052406private void setDeleteRDN(String deleteRDNProp) {2407if ((deleteRDNProp != null) &&2408(deleteRDNProp.equalsIgnoreCase("false"))) {2409deleteRDN = false;2410} else {2411deleteRDN = DEFAULT_DELETE_RDN;2412}2413}24142415private void setTypesOnly(String typesOnlyProp) {2416if ((typesOnlyProp != null) &&2417(typesOnlyProp.equalsIgnoreCase("true"))) {2418typesOnly = true;2419} else {2420typesOnly = DEFAULT_TYPES_ONLY;2421}2422}24232424/**2425* Sets the batch size of this context;2426*/2427private void setBatchSize(String batchSizeProp) {2428// set batchsize2429if (batchSizeProp != null) {2430batchSize = Integer.parseInt(batchSizeProp);2431} else {2432batchSize = DEFAULT_BATCH_SIZE;2433}2434}24352436/**2437* Sets the referral mode of this context to 'follow', 'throw' or 'ignore'.2438* If referral mode is 'ignore' then activate the manageReferral control.2439*/2440private void setReferralMode(String ref, boolean update) {2441// First determine the referral mode2442if (ref != null) {2443switch (ref) {2444case "follow-scheme":2445handleReferrals = LdapClient.LDAP_REF_FOLLOW_SCHEME;2446break;2447case "follow":2448handleReferrals = LdapClient.LDAP_REF_FOLLOW;2449break;2450case "throw":2451handleReferrals = LdapClient.LDAP_REF_THROW;2452break;2453case "ignore":2454handleReferrals = LdapClient.LDAP_REF_IGNORE;2455break;2456default:2457throw new IllegalArgumentException(2458"Illegal value for " + Context.REFERRAL + " property.");2459}2460} else {2461handleReferrals = DEFAULT_REFERRAL_MODE;2462}24632464if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {2465// If ignoring referrals, add manageReferralControl2466reqCtls = addControl(reqCtls, manageReferralControl);24672468} else if (update) {24692470// If we're update an existing context, remove the control2471reqCtls = removeControl(reqCtls, manageReferralControl);24722473} // else, leave alone; need not update2474}24752476/**2477* Set whether aliases are dereferenced during resolution and searches.2478*/2479private void setDerefAliases(String deref) {2480if (deref != null) {2481switch (deref) {2482case "never":2483derefAliases = 0; // never de-reference aliases2484break;2485case "searching":2486derefAliases = 1; // de-reference aliases during searching2487break;2488case "finding":2489derefAliases = 2; // de-reference during name resolution2490break;2491case "always":2492derefAliases = 3; // always de-reference aliases2493break;2494default:2495throw new IllegalArgumentException("Illegal value for " +2496DEREF_ALIASES + " property.");2497}2498} else {2499derefAliases = DEFAULT_DEREF_ALIASES;2500}2501}25022503private void setRefSeparator(String sepStr) throws NamingException {2504if (sepStr != null && sepStr.length() > 0) {2505addrEncodingSeparator = sepStr.charAt(0);2506} else {2507addrEncodingSeparator = DEFAULT_REF_SEPARATOR;2508}2509}25102511/**2512* Sets the limit on referral chains2513*/2514private void setReferralLimit(String referralLimitProp) {2515// set referral limit2516if (referralLimitProp != null) {2517referralHopLimit = Integer.parseInt(referralLimitProp);25182519// a zero setting indicates no limit2520if (referralHopLimit == 0)2521referralHopLimit = Integer.MAX_VALUE;2522} else {2523referralHopLimit = DEFAULT_REFERRAL_LIMIT;2524}2525}25262527// For counting referral hops2528void setHopCount(int hopCount) {2529this.hopCount = hopCount;2530}25312532/**2533* Sets the connect timeout value2534*/2535private void setConnectTimeout(String connectTimeoutProp) {2536if (connectTimeoutProp != null) {2537connectTimeout = Integer.parseInt(connectTimeoutProp);2538} else {2539connectTimeout = -1;2540}2541}25422543/**2544* Sets the size of the queue of unprocessed search replies2545*/2546private void setReplyQueueSize(String replyQueueSizeProp) {2547if (replyQueueSizeProp != null) {2548replyQueueSize = Integer.parseInt(replyQueueSizeProp);2549// disallow an empty queue2550if (replyQueueSize <= 0) {2551replyQueueSize = -1; // unlimited2552}2553} else {2554replyQueueSize = -1; // unlimited2555}2556}25572558/**2559* Sets the flag that controls whether to block until the first search2560* reply is received2561*/2562private void setWaitForReply(String waitForReplyProp) {2563if (waitForReplyProp != null &&2564(waitForReplyProp.equalsIgnoreCase("false"))) {2565waitForReply = false;2566} else {2567waitForReply = true;2568}2569}25702571/**2572* Sets the read timeout value2573*/2574private void setReadTimeout(String readTimeoutProp) {2575if (readTimeoutProp != null) {2576readTimeout = Integer.parseInt(readTimeoutProp);2577} else {2578readTimeout = -1;2579}2580}25812582/*2583* Extract URLs from a string. The format of the string is:2584*2585* <urlstring > ::= "Referral:" <ldapurls>2586* <ldapurls> ::= <separator> <ldapurl> | <ldapurls>2587* <separator> ::= ASCII linefeed character (0x0a)2588* <ldapurl> ::= LDAP URL format (RFC 1959)2589*2590* Returns a Vector of single-String Vectors.2591*/2592private static Vector<Vector<String>> extractURLs(String refString) {25932594int separator = 0;2595int urlCount = 0;25962597// count the number of URLs2598while ((separator = refString.indexOf('\n', separator)) >= 0) {2599separator++;2600urlCount++;2601}26022603Vector<Vector<String>> referrals = new Vector<>(urlCount);2604int iURL;2605int i = 0;26062607separator = refString.indexOf('\n');2608iURL = separator + 1;2609while ((separator = refString.indexOf('\n', iURL)) >= 0) {2610Vector<String> referral = new Vector<>(1);2611referral.addElement(refString.substring(iURL, separator));2612referrals.addElement(referral);2613iURL = separator + 1;2614}2615Vector<String> referral = new Vector<>(1);2616referral.addElement(refString.substring(iURL));2617referrals.addElement(referral);26182619return referrals;2620}26212622/*2623* Argument is a space-separated list of attribute IDs2624* Converts attribute IDs to lowercase before adding to built-in list.2625*/2626private void setBinaryAttributes(String attrIds) {2627if (attrIds == null) {2628binaryAttrs = null;2629} else {2630binaryAttrs = new Hashtable<>(11, 0.75f);2631StringTokenizer tokens =2632new StringTokenizer(attrIds.toLowerCase(Locale.ENGLISH), " ");26332634while (tokens.hasMoreTokens()) {2635binaryAttrs.put(tokens.nextToken(), Boolean.TRUE);2636}2637}2638}26392640// ----------------- Connection ---------------------26412642@SuppressWarnings("deprecation")2643protected void finalize() {2644try {2645close();2646} catch (NamingException e) {2647// ignore failures2648}2649}26502651synchronized public void close() throws NamingException {2652if (debug) {2653System.err.println("LdapCtx: close() called " + this);2654(new Throwable()).printStackTrace();2655}26562657// Event (normal and unsolicited)2658if (eventSupport != null) {2659eventSupport.cleanup(); // idempotent2660removeUnsolicited();2661}26622663// Enumerations that are keeping the connection alive2664if (enumCount > 0) {2665if (debug)2666System.err.println("LdapCtx: close deferred");2667closeRequested = true;2668return;2669}2670closeConnection(SOFT_CLOSE);26712672// %%%: RL: There is no need to set these to null, as they're just2673// variables whose contents and references will automatically2674// be cleaned up when they're no longer referenced.2675// Also, setting these to null creates problems for the attribute2676// schema-related methods, which need these to work.2677/*2678schemaTrees = null;2679envprops = null;2680*/2681}26822683@SuppressWarnings("unchecked") // clone()2684public void reconnect(Control[] connCtls) throws NamingException {2685// Update environment2686envprops = (envprops == null2687? new Hashtable<String, Object>(5, 0.75f)2688: (Hashtable<String, Object>)envprops.clone());26892690if (connCtls == null) {2691envprops.remove(BIND_CONTROLS);2692bindCtls = null;2693} else {2694envprops.put(BIND_CONTROLS, bindCtls = cloneControls(connCtls));2695}26962697sharable = false; // can't share with existing contexts2698ensureOpen(); // open or reauthenticated2699}27002701// Load 'mechsAllowedToSendCredentials' system property value2702@SuppressWarnings("removal")2703private static String getMechsAllowedToSendCredentials() {2704PrivilegedAction<String> pa = () -> System.getProperty(ALLOWED_MECHS_SP);2705return System.getSecurityManager() == null ? pa.run() : AccessController.doPrivileged(pa);2706}27072708// Get set of allowed authentication mechanism names from the property value2709private static Set<String> getMechsFromPropertyValue(String propValue) {2710if (propValue == null || propValue.isBlank()) {2711return Collections.emptySet();2712}2713return Arrays.stream(propValue.split(","))2714.map(String::trim)2715.filter(Predicate.not(String::isBlank))2716.collect(Collectors.toUnmodifiableSet());2717}27182719// Returns true if TLS connection opened using "ldaps" scheme, or using "ldap" and then upgraded with2720// startTLS extended operation, and startTLS is still active.2721private boolean isConnectionEncrypted() {2722return hasLdapsScheme || clnt.isUpgradedToStartTls();2723}27242725// Ensure connection and context are in a safe state to transmit credentials2726private void ensureCanTransmitCredentials(String authMechanism) throws NamingException {27272728// "none" and "anonumous" authentication mechanisms are allowed unconditionally2729if ("none".equalsIgnoreCase(authMechanism) || "anonymous".equalsIgnoreCase(authMechanism)) {2730return;2731}27322733// Check environment first2734String allowedMechanismsOrTrue = (String) envprops.get(ALLOWED_MECHS_SP);2735boolean useSpMechsCache = false;2736boolean anyPropertyIsSet = ALLOWED_MECHS_SP_VALUE != null || allowedMechanismsOrTrue != null;27372738// If current connection is not encrypted, and context seen to be secured with STARTTLS2739// or 'mechsAllowedToSendCredentials' is set to any value via system/context environment properties2740if (!isConnectionEncrypted() && (contextSeenStartTlsEnabled || anyPropertyIsSet)) {2741// First, check if security principal is provided in context environment for "simple"2742// authentication mechanism. There is no check for other SASL mechanisms since the credentials2743// can be specified via other properties2744if ("simple".equalsIgnoreCase(authMechanism) && !envprops.containsKey(SECURITY_PRINCIPAL)) {2745return;2746}27472748// If null - will use mechanism name cached from system property2749if (allowedMechanismsOrTrue == null) {2750useSpMechsCache = true;2751allowedMechanismsOrTrue = ALLOWED_MECHS_SP_VALUE;2752}27532754// If the property value (system or environment) is 'all':2755// any kind of authentication is allowed unconditionally - no check is needed2756if ("all".equalsIgnoreCase(allowedMechanismsOrTrue)) {2757return;2758}27592760// Get the set with allowed authentication mechanisms and check current mechanism2761Set<String> allowedAuthMechs = useSpMechsCache ?2762MECHS_ALLOWED_BY_SP : getMechsFromPropertyValue(allowedMechanismsOrTrue);2763if (!allowedAuthMechs.contains(authMechanism)) {2764throw new NamingException(UNSECURED_CRED_TRANSMIT_MSG);2765}2766}2767}27682769private void ensureOpen() throws NamingException {2770ensureOpen(false);2771}27722773private void ensureOpen(boolean startTLS) throws NamingException {27742775try {2776if (clnt == null) {2777if (debug) {2778System.err.println("LdapCtx: Reconnecting " + this);2779}27802781// reset the cache before a new connection is established2782schemaTrees = new Hashtable<>(11, 0.75f);2783connect(startTLS);27842785} else if (!sharable || startTLS) {27862787synchronized (clnt) {2788if (!clnt.isLdapv32789|| clnt.referenceCount > 12790|| clnt.usingSaslStreams()2791|| !clnt.conn.useable) {2792closeConnection(SOFT_CLOSE);2793}2794}2795// reset the cache before a new connection is established2796schemaTrees = new Hashtable<>(11, 0.75f);2797connect(startTLS);2798}27992800} finally {2801sharable = true; // connection is now either new or single-use2802// OK for others to start sharing again2803}2804}28052806private void connect(boolean startTLS) throws NamingException {2807if (debug) { System.err.println("LdapCtx: Connecting " + this); }28082809String user = null; // authenticating user2810Object passwd = null; // password for authenticating user2811String secProtocol = null; // security protocol (e.g. "ssl")2812String socketFactory = null; // socket factory2813String authMechanism = null; // authentication mechanism2814String ver = null;2815int ldapVersion; // LDAP protocol version2816boolean usePool = false; // enable connection pooling28172818if (envprops != null) {2819user = (String)envprops.get(Context.SECURITY_PRINCIPAL);2820passwd = envprops.get(Context.SECURITY_CREDENTIALS);2821ver = (String)envprops.get(VERSION);2822secProtocol =2823useSsl ? "ssl" : (String)envprops.get(Context.SECURITY_PROTOCOL);2824socketFactory = (String)envprops.get(SOCKET_FACTORY);2825authMechanism =2826(String)envprops.get(Context.SECURITY_AUTHENTICATION);28272828usePool = "true".equalsIgnoreCase((String)envprops.get(ENABLE_POOL));2829}28302831if (socketFactory == null) {2832socketFactory =2833"ssl".equals(secProtocol) ? DEFAULT_SSL_FACTORY : null;2834}28352836if (authMechanism == null) {2837authMechanism = (user == null) ? "none" : "simple";2838}28392840try {2841boolean initial = (clnt == null);28422843if (initial) {2844ldapVersion = (ver != null) ? Integer.parseInt(ver) :2845DEFAULT_LDAP_VERSION;28462847clnt = LdapClient.getInstance(2848usePool, // Whether to use connection pooling28492850// Required for LdapClient constructor2851hostname,2852port_number,2853socketFactory,2854connectTimeout,2855readTimeout,2856trace,28572858// Required for basic client identity2859ldapVersion,2860authMechanism,2861bindCtls,2862secProtocol,28632864// Required for simple client identity2865user,2866passwd,28672868// Required for SASL client identity2869envprops);28702871// Mark current context as secure if the connection is acquired2872// from the pool and it is secure.2873contextSeenStartTlsEnabled |= clnt.isUpgradedToStartTls();28742875/**2876* Pooled connections are preauthenticated;2877* newly created ones are not.2878*/2879if (clnt.authenticateCalled()) {2880return;2881}28822883} else if (sharable && startTLS) {2884return; // no authentication required28852886} else {2887// reauthenticating over existing connection;2888// only v3 supports this2889ldapVersion = LdapClient.LDAP_VERSION3;2890}28912892LdapResult answer;2893synchronized (clnt.conn.startTlsLock) {2894ensureCanTransmitCredentials(authMechanism);2895answer = clnt.authenticate(initial, user, passwd, ldapVersion,2896authMechanism, bindCtls, envprops);2897}28982899respCtls = answer.resControls; // retrieve (bind) response controls29002901if (answer.status != LdapClient.LDAP_SUCCESS) {2902if (initial) {2903closeConnection(HARD_CLOSE); // hard close2904}2905processReturnCode(answer);2906}29072908} catch (LdapReferralException e) {2909if (handleReferrals == LdapClient.LDAP_REF_THROW)2910throw e;29112912String referral;2913LdapURL url;2914NamingException saved_ex = null;29152916// Process the referrals sequentially (top level) and2917// recursively (per referral)2918while (true) {29192920if ((referral = e.getNextReferral()) == null) {2921// No more referrals to follow29222923if (saved_ex != null) {2924throw (NamingException)(saved_ex.fillInStackTrace());2925} else {2926// No saved exception, something must have gone wrong2927throw new NamingException(2928"Internal error processing referral during connection");2929}2930}29312932// Use host/port number from referral2933url = new LdapURL(referral);2934hostname = url.getHost();2935if ((hostname != null) && (hostname.charAt(0) == '[')) {2936hostname = hostname.substring(1, hostname.length() - 1);2937}2938port_number = url.getPort();29392940// Try to connect again using new host/port number2941try {2942connect(startTLS);2943break;29442945} catch (NamingException ne) {2946saved_ex = ne;2947continue; // follow another referral2948}2949}2950}2951}29522953private void closeConnection(boolean hardclose) {2954removeUnsolicited(); // idempotent29552956if (clnt != null) {2957if (debug) {2958System.err.println("LdapCtx: calling clnt.close() " + this);2959}2960clnt.close(reqCtls, hardclose);2961clnt = null;2962}2963}29642965// Used by Enum classes to track whether it still needs context2966private int enumCount = 0;2967private boolean closeRequested = false;29682969synchronized void incEnumCount() {2970++enumCount;2971if (debug) System.err.println("LdapCtx: " + this + " enum inc: " + enumCount);2972}29732974synchronized void decEnumCount() {2975--enumCount;2976if (debug) System.err.println("LdapCtx: " + this + " enum dec: " + enumCount);29772978if (enumCount == 0 && closeRequested) {2979try {2980close();2981} catch (NamingException e) {2982// ignore failures2983}2984}2985}298629872988// ------------ Return code and Error messages -----------------------29892990protected void processReturnCode(LdapResult answer) throws NamingException {2991processReturnCode(answer, null, this, null, envprops, null);2992}29932994void processReturnCode(LdapResult answer, Name remainName)2995throws NamingException {2996processReturnCode(answer,2997(new CompositeName()).add(currentDN),2998this,2999remainName,3000envprops,3001fullyQualifiedName(remainName));3002}30033004protected void processReturnCode(LdapResult res, Name resolvedName,3005Object resolvedObj, Name remainName, Hashtable<?,?> envprops, String fullDN)3006throws NamingException {30073008String msg = LdapClient.getErrorMessage(res.status, res.errorMessage);3009NamingException e;3010LdapReferralException r = null;30113012switch (res.status) {30133014case LdapClient.LDAP_SUCCESS:30153016// handle Search continuation references3017if (res.referrals != null) {30183019msg = "Unprocessed Continuation Reference(s)";30203021if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {3022e = new PartialResultException(msg);3023break;3024}30253026// handle multiple sets of URLs3027int contRefCount = res.referrals.size();3028LdapReferralException head = null;3029LdapReferralException ptr = null;30303031msg = "Continuation Reference";30323033// make a chain of LdapReferralExceptions3034for (int i = 0; i < contRefCount; i++) {30353036r = new LdapReferralException(resolvedName, resolvedObj,3037remainName, msg, envprops, fullDN, handleReferrals,3038reqCtls);3039r.setReferralInfo(res.referrals.elementAt(i), true);30403041if (hopCount > 1) {3042r.setHopCount(hopCount);3043}30443045if (head == null) {3046head = ptr = r;3047} else {3048ptr.nextReferralEx = r; // append ex. to end of chain3049ptr = r;3050}3051}3052res.referrals = null; // reset30533054if (res.refEx == null) {3055res.refEx = head;30563057} else {3058ptr = res.refEx;30593060while (ptr.nextReferralEx != null) {3061ptr = ptr.nextReferralEx;3062}3063ptr.nextReferralEx = head;3064}30653066// check the hop limit3067if (hopCount > referralHopLimit) {3068NamingException lee =3069new LimitExceededException("Referral limit exceeded");3070lee.setRootCause(r);3071throw lee;3072}3073}3074return;30753076case LdapClient.LDAP_REFERRAL:30773078if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {3079e = new PartialResultException(msg);3080break;3081}30823083r = new LdapReferralException(resolvedName, resolvedObj, remainName,3084msg, envprops, fullDN, handleReferrals, reqCtls);3085// only one set of URLs is present3086Vector<String> refs;3087if (res.referrals == null) {3088refs = null;3089} else if (handleReferrals == LdapClient.LDAP_REF_FOLLOW_SCHEME) {3090refs = new Vector<>();3091for (String s : res.referrals.elementAt(0)) {3092if (s.startsWith("ldap:")) {3093refs.add(s);3094}3095}3096if (refs.isEmpty()) {3097refs = null;3098}3099} else {3100refs = res.referrals.elementAt(0);3101}3102r.setReferralInfo(refs, false);31033104if (hopCount > 1) {3105r.setHopCount(hopCount);3106}31073108// check the hop limit3109if (hopCount > referralHopLimit) {3110NamingException lee =3111new LimitExceededException("Referral limit exceeded");3112lee.setRootCause(r);3113e = lee;31143115} else {3116e = r;3117}3118break;31193120/*3121* Handle SLAPD-style referrals.3122*3123* Referrals received during name resolution should be followed3124* until one succeeds - the target entry is located. An exception3125* is thrown now to handle these.3126*3127* Referrals received during a search operation point to unexplored3128* parts of the directory and each should be followed. An exception3129* is thrown later (during results enumeration) to handle these.3130*/31313132case LdapClient.LDAP_PARTIAL_RESULTS:31333134if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {3135e = new PartialResultException(msg);3136break;3137}31383139// extract SLAPD-style referrals from errorMessage3140if ((res.errorMessage != null) && (!res.errorMessage.isEmpty())) {3141res.referrals = extractURLs(res.errorMessage);3142} else {3143e = new PartialResultException(msg);3144break;3145}31463147// build exception3148r = new LdapReferralException(resolvedName,3149resolvedObj,3150remainName,3151msg,3152envprops,3153fullDN,3154handleReferrals,3155reqCtls);31563157if (hopCount > 1) {3158r.setHopCount(hopCount);3159}3160/*3161* %%%3162* SLAPD-style referrals received during name resolution3163* cannot be distinguished from those received during a3164* search operation. Since both must be handled differently3165* the following rule is applied:3166*3167* If 1 referral and 0 entries is received then3168* assume name resolution has not yet completed.3169*/3170if (((res.entries == null) || (res.entries.isEmpty())) &&3171((res.referrals != null) && (res.referrals.size() == 1))) {31723173r.setReferralInfo(res.referrals, false);31743175// check the hop limit3176if (hopCount > referralHopLimit) {3177NamingException lee =3178new LimitExceededException("Referral limit exceeded");3179lee.setRootCause(r);3180e = lee;31813182} else {3183e = r;3184}31853186} else {3187r.setReferralInfo(res.referrals, true);3188res.refEx = r;3189return;3190}3191break;31923193case LdapClient.LDAP_INVALID_DN_SYNTAX:3194case LdapClient.LDAP_NAMING_VIOLATION:31953196if (remainName != null) {3197e = new3198InvalidNameException(remainName.toString() + ": " + msg);3199} else {3200e = new InvalidNameException(msg);3201}3202break;32033204default:3205e = mapErrorCode(res.status, res.errorMessage);3206break;3207}3208e.setResolvedName(resolvedName);3209e.setResolvedObj(resolvedObj);3210e.setRemainingName(remainName);3211throw e;3212}32133214/**3215* Maps an LDAP error code to an appropriate NamingException.3216* %%% public; used by controls3217*3218* @param errorCode numeric LDAP error code3219* @param errorMessage textual description of the LDAP error. May be null.3220*3221* @return A NamingException or null if the error code indicates success.3222*/3223public static NamingException mapErrorCode(int errorCode,3224String errorMessage) {32253226if (errorCode == LdapClient.LDAP_SUCCESS)3227return null;32283229NamingException e = null;3230String message = LdapClient.getErrorMessage(errorCode, errorMessage);32313232switch (errorCode) {32333234case LdapClient.LDAP_ALIAS_DEREFERENCING_PROBLEM:3235e = new NamingException(message);3236break;32373238case LdapClient.LDAP_ALIAS_PROBLEM:3239e = new NamingException(message);3240break;32413242case LdapClient.LDAP_ATTRIBUTE_OR_VALUE_EXISTS:3243e = new AttributeInUseException(message);3244break;32453246case LdapClient.LDAP_AUTH_METHOD_NOT_SUPPORTED:3247case LdapClient.LDAP_CONFIDENTIALITY_REQUIRED:3248case LdapClient.LDAP_STRONG_AUTH_REQUIRED:3249case LdapClient.LDAP_INAPPROPRIATE_AUTHENTICATION:3250e = new AuthenticationNotSupportedException(message);3251break;32523253case LdapClient.LDAP_ENTRY_ALREADY_EXISTS:3254e = new NameAlreadyBoundException(message);3255break;32563257case LdapClient.LDAP_INVALID_CREDENTIALS:3258case LdapClient.LDAP_SASL_BIND_IN_PROGRESS:3259e = new AuthenticationException(message);3260break;32613262case LdapClient.LDAP_INAPPROPRIATE_MATCHING:3263e = new InvalidSearchFilterException(message);3264break;32653266case LdapClient.LDAP_INSUFFICIENT_ACCESS_RIGHTS:3267e = new NoPermissionException(message);3268break;32693270case LdapClient.LDAP_INVALID_ATTRIBUTE_SYNTAX:3271case LdapClient.LDAP_CONSTRAINT_VIOLATION:3272e = new InvalidAttributeValueException(message);3273break;32743275case LdapClient.LDAP_LOOP_DETECT:3276e = new NamingException(message);3277break;32783279case LdapClient.LDAP_NO_SUCH_ATTRIBUTE:3280e = new NoSuchAttributeException(message);3281break;32823283case LdapClient.LDAP_NO_SUCH_OBJECT:3284e = new NameNotFoundException(message);3285break;32863287case LdapClient.LDAP_OBJECT_CLASS_MODS_PROHIBITED:3288case LdapClient.LDAP_OBJECT_CLASS_VIOLATION:3289case LdapClient.LDAP_NOT_ALLOWED_ON_RDN:3290e = new SchemaViolationException(message);3291break;32923293case LdapClient.LDAP_NOT_ALLOWED_ON_NON_LEAF:3294e = new ContextNotEmptyException(message);3295break;32963297case LdapClient.LDAP_OPERATIONS_ERROR:3298// %%% need new exception ?3299e = new NamingException(message);3300break;33013302case LdapClient.LDAP_OTHER:3303e = new NamingException(message);3304break;33053306case LdapClient.LDAP_PROTOCOL_ERROR:3307e = new CommunicationException(message);3308break;33093310case LdapClient.LDAP_SIZE_LIMIT_EXCEEDED:3311e = new SizeLimitExceededException(message);3312break;33133314case LdapClient.LDAP_TIME_LIMIT_EXCEEDED:3315e = new TimeLimitExceededException(message);3316break;33173318case LdapClient.LDAP_UNAVAILABLE_CRITICAL_EXTENSION:3319e = new OperationNotSupportedException(message);3320break;33213322case LdapClient.LDAP_UNAVAILABLE:3323case LdapClient.LDAP_BUSY:3324e = new ServiceUnavailableException(message);3325break;33263327case LdapClient.LDAP_UNDEFINED_ATTRIBUTE_TYPE:3328e = new InvalidAttributeIdentifierException(message);3329break;33303331case LdapClient.LDAP_UNWILLING_TO_PERFORM:3332e = new OperationNotSupportedException(message);3333break;33343335case LdapClient.LDAP_COMPARE_FALSE:3336case LdapClient.LDAP_COMPARE_TRUE:3337case LdapClient.LDAP_IS_LEAF:3338// these are really not exceptions and this code probably3339// never gets executed3340e = new NamingException(message);3341break;33423343case LdapClient.LDAP_ADMIN_LIMIT_EXCEEDED:3344e = new LimitExceededException(message);3345break;33463347case LdapClient.LDAP_REFERRAL:3348e = new NamingException(message);3349break;33503351case LdapClient.LDAP_PARTIAL_RESULTS:3352e = new NamingException(message);3353break;33543355case LdapClient.LDAP_INVALID_DN_SYNTAX:3356case LdapClient.LDAP_NAMING_VIOLATION:3357e = new InvalidNameException(message);3358break;33593360default:3361e = new NamingException(message);3362break;3363}33643365return e;3366}33673368// ----------------- Extensions and Controls -------------------33693370public ExtendedResponse extendedOperation(ExtendedRequest request)3371throws NamingException {33723373boolean startTLS = (request.getID().equals(STARTTLS_REQ_OID));3374ensureOpen(startTLS);33753376try {33773378LdapResult answer =3379clnt.extendedOp(request.getID(), request.getEncodedValue(),3380reqCtls, startTLS);3381respCtls = answer.resControls; // retrieve response controls33823383if (answer.status != LdapClient.LDAP_SUCCESS) {3384processReturnCode(answer, new CompositeName());3385}3386// %%% verify request.getID() == answer.extensionId33873388int len = (answer.extensionValue == null) ?33890 :3390answer.extensionValue.length;33913392ExtendedResponse er =3393request.createExtendedResponse(answer.extensionId,3394answer.extensionValue, 0, len);33953396if (er instanceof StartTlsResponseImpl) {3397// Pass the connection handle to StartTlsResponseImpl3398String domainName = (String)3399(envprops != null ? envprops.get(DOMAIN_NAME) : null);3400((StartTlsResponseImpl)er).setConnection(clnt.conn, domainName);3401contextSeenStartTlsEnabled |= startTLS;3402}3403return er;34043405} catch (LdapReferralException e) {34063407if (handleReferrals == LdapClient.LDAP_REF_THROW)3408throw e;34093410// process the referrals sequentially3411while (true) {34123413LdapReferralContext refCtx =3414(LdapReferralContext)e.getReferralContext(envprops, bindCtls);34153416// repeat the original operation at the new context3417try {34183419return refCtx.extendedOperation(request);34203421} catch (LdapReferralException re) {3422e = re;3423continue;34243425} finally {3426// Make sure we close referral context3427refCtx.close();3428}3429}34303431} catch (IOException e) {3432NamingException e2 = new CommunicationException(e.getMessage());3433e2.setRootCause(e);3434throw e2;3435}3436}34373438public void setRequestControls(Control[] reqCtls) throws NamingException {3439if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {3440this.reqCtls = addControl(reqCtls, manageReferralControl);3441} else {3442this.reqCtls = cloneControls(reqCtls);3443}3444}34453446public Control[] getRequestControls() throws NamingException {3447return cloneControls(reqCtls);3448}34493450public Control[] getConnectControls() throws NamingException {3451return cloneControls(bindCtls);3452}34533454public Control[] getResponseControls() throws NamingException {3455return (respCtls != null)? convertControls(respCtls) : null;3456}34573458/**3459* Narrow controls using own default factory and ControlFactory.3460* @param ctls A non-null Vector<Control>3461*/3462Control[] convertControls(Vector<Control> ctls) throws NamingException {3463int count = ctls.size();34643465if (count == 0) {3466return null;3467}34683469Control[] controls = new Control[count];34703471for (int i = 0; i < count; i++) {3472// Try own factory first3473controls[i] = myResponseControlFactory.getControlInstance(3474ctls.elementAt(i));34753476// Try assigned factories if own produced null3477if (controls[i] == null) {3478controls[i] = ControlFactory.getControlInstance(3479ctls.elementAt(i), this, envprops);3480}3481}3482return controls;3483}34843485private static Control[] addControl(Control[] prevCtls, Control addition) {3486if (prevCtls == null) {3487return new Control[]{addition};3488}34893490// Find it3491int found = findControl(prevCtls, addition);3492if (found != -1) {3493return prevCtls; // no need to do it again3494}34953496Control[] newCtls = new Control[prevCtls.length+1];3497System.arraycopy(prevCtls, 0, newCtls, 0, prevCtls.length);3498newCtls[prevCtls.length] = addition;3499return newCtls;3500}35013502private static int findControl(Control[] ctls, Control target) {3503for (int i = 0; i < ctls.length; i++) {3504if (ctls[i] == target) {3505return i;3506}3507}3508return -1;3509}35103511private static Control[] removeControl(Control[] prevCtls, Control target) {3512if (prevCtls == null) {3513return null;3514}35153516// Find it3517int found = findControl(prevCtls, target);3518if (found == -1) {3519return prevCtls; // not there3520}35213522// Remove it3523Control[] newCtls = new Control[prevCtls.length-1];3524System.arraycopy(prevCtls, 0, newCtls, 0, found);3525System.arraycopy(prevCtls, found+1, newCtls, found,3526prevCtls.length-found-1);3527return newCtls;3528}35293530private static Control[] cloneControls(Control[] ctls) {3531if (ctls == null) {3532return null;3533}3534Control[] copiedCtls = new Control[ctls.length];3535System.arraycopy(ctls, 0, copiedCtls, 0, ctls.length);3536return copiedCtls;3537}35383539// -------------------- Events ------------------------3540/*3541* Access to eventSupport need not be synchronized even though the3542* Connection thread can access it asynchronously. It is3543* impossible for a race condition to occur because3544* eventSupport.addNamingListener() must have been called before3545* the Connection thread can call back to this ctx.3546*/3547public void addNamingListener(Name nm, int scope, NamingListener l)3548throws NamingException {3549addNamingListener(getTargetName(nm), scope, l);3550}35513552public void addNamingListener(String nm, int scope, NamingListener l)3553throws NamingException {3554if (eventSupport == null)3555eventSupport = new EventSupport(this);3556eventSupport.addNamingListener(getTargetName(new CompositeName(nm)),3557scope, l);35583559// If first time asking for unsol3560if (l instanceof UnsolicitedNotificationListener && !unsolicited) {3561addUnsolicited();3562}3563}35643565public void removeNamingListener(NamingListener l) throws NamingException {3566if (eventSupport == null)3567return; // no activity before, so just return35683569eventSupport.removeNamingListener(l);35703571// If removing an Unsol listener and it is the last one, let clnt know3572if (l instanceof UnsolicitedNotificationListener &&3573!eventSupport.hasUnsolicited()) {3574removeUnsolicited();3575}3576}35773578public void addNamingListener(String nm, String filter, SearchControls ctls,3579NamingListener l) throws NamingException {3580if (eventSupport == null)3581eventSupport = new EventSupport(this);3582eventSupport.addNamingListener(getTargetName(new CompositeName(nm)),3583filter, cloneSearchControls(ctls), l);35843585// If first time asking for unsol3586if (l instanceof UnsolicitedNotificationListener && !unsolicited) {3587addUnsolicited();3588}3589}35903591public void addNamingListener(Name nm, String filter, SearchControls ctls,3592NamingListener l) throws NamingException {3593addNamingListener(getTargetName(nm), filter, ctls, l);3594}35953596public void addNamingListener(Name nm, String filter, Object[] filterArgs,3597SearchControls ctls, NamingListener l) throws NamingException {3598addNamingListener(getTargetName(nm), filter, filterArgs, ctls, l);3599}36003601public void addNamingListener(String nm, String filterExpr, Object[] filterArgs,3602SearchControls ctls, NamingListener l) throws NamingException {3603String strfilter = SearchFilter.format(filterExpr, filterArgs);3604addNamingListener(getTargetName(new CompositeName(nm)), strfilter, ctls, l);3605}36063607public boolean targetMustExist() {3608return true;3609}36103611/**3612* Retrieves the target name for which the listener is registering.3613* If nm is a CompositeName, use its first and only component. It3614* cannot have more than one components because a target be outside of3615* this namespace. If nm is not a CompositeName, then treat it as a3616* compound name.3617* @param nm The non-null target name.3618*/3619private static String getTargetName(Name nm) throws NamingException {3620if (nm instanceof CompositeName) {3621if (nm.size() > 1) {3622throw new InvalidNameException(3623"Target cannot span multiple namespaces: " + nm);3624} else if (nm.isEmpty()) {3625return "";3626} else {3627return nm.get(0);3628}3629} else {3630// treat as compound name3631return nm.toString();3632}3633}36343635// ------------------ Unsolicited Notification ---------------3636// package private methods for handling unsolicited notification36373638/**3639* Registers this context with the underlying LdapClient.3640* When the underlying LdapClient receives an unsolicited notification,3641* it will invoke LdapCtx.fireUnsolicited() so that this context3642* can (using EventSupport) notified any registered listeners.3643* This method is called by EventSupport when an unsolicited listener3644* first registers with this context (should be called just once).3645* @see #removeUnsolicited3646* @see #fireUnsolicited3647*/3648private void addUnsolicited() throws NamingException {3649if (debug) {3650System.out.println("LdapCtx.addUnsolicited: " + this);3651}36523653// addNamingListener must have created EventSupport already3654ensureOpen();3655synchronized (eventSupport) {3656clnt.addUnsolicited(this);3657unsolicited = true;3658}3659}36603661/**3662* Removes this context from registering interest in unsolicited3663* notifications from the underlying LdapClient. This method is called3664* under any one of the following conditions:3665* <ul>3666* <li>All unsolicited listeners have been removed. (see removingNamingListener)3667* <li>This context is closed.3668* <li>This context's underlying LdapClient changes.3669*</ul>3670* After this method has been called, this context will not pass3671* on any events related to unsolicited notifications to EventSupport and3672* and its listeners.3673*/36743675private void removeUnsolicited() {3676if (debug) {3677System.out.println("LdapCtx.removeUnsolicited: " + unsolicited);3678}3679if (eventSupport == null) {3680return;3681}36823683// addNamingListener must have created EventSupport already3684synchronized(eventSupport) {3685if (unsolicited && clnt != null) {3686clnt.removeUnsolicited(this);3687}3688unsolicited = false;3689}3690}36913692/**3693* Uses EventSupport to fire an event related to an unsolicited notification.3694* Called by LdapClient when LdapClient receives an unsolicited notification.3695*/3696void fireUnsolicited(Object obj) {3697if (debug) {3698System.out.println("LdapCtx.fireUnsolicited: " + obj);3699}3700// addNamingListener must have created EventSupport already3701synchronized(eventSupport) {3702if (unsolicited) {3703eventSupport.fireUnsolicited(obj);37043705if (obj instanceof NamingException) {3706unsolicited = false;3707// No need to notify clnt because clnt is the3708// only one that can fire a NamingException to3709// unsol listeners and it will handle its own cleanup3710}3711}3712}3713}3714}371537163717