Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/com/sun/jndi/ldap/LdapCtx.java
38924 views
/*1* Copyright (c) 1999, 2020, 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.Collections;38import java.util.Locale;39import java.util.Set;40import java.util.Vector;41import java.util.Hashtable;42import java.util.HashSet;43import java.util.List;44import java.util.StringTokenizer;45import java.util.Enumeration;464748import java.io.IOException;49import java.io.OutputStream;5051import com.sun.jndi.toolkit.ctx.*;52import com.sun.jndi.toolkit.dir.HierMemDirCtx;53import com.sun.jndi.toolkit.dir.SearchFilter;54import com.sun.jndi.ldap.ext.StartTlsResponseImpl;5556/**57* The LDAP context implementation.58*59* Implementation is not thread-safe. Caller must sync as per JNDI spec.60* Members that are used directly or indirectly by internal worker threads61* (Connection, EventQueue, NamingEventNotifier) must be thread-safe.62* Connection - calls LdapClient.processUnsolicited(), which in turn calls63* LdapCtx.convertControls() and LdapCtx.fireUnsolicited().64* convertControls() - no sync; reads envprops and 'this'65* fireUnsolicited() - sync on eventSupport for all references to 'unsolicited'66* (even those in other methods); don't sync on LdapCtx in case caller67* is already sync'ing on it - this would prevent Unsol events from firing68* and the Connection thread to block (thus preventing any other data69* from being read from the connection)70* References to 'eventSupport' need not be sync'ed because these71* methods can only be called after eventSupport has been set first72* (via addNamingListener()).73* EventQueue - no direct or indirect calls to LdapCtx74* NamingEventNotifier - calls newInstance() to get instance for run() to use;75* no sync needed for methods invoked on new instance;76*77* LdapAttribute links to LdapCtx in order to process getAttributeDefinition()78* and getAttributeSyntaxDefinition() calls. It invokes LdapCtx.getSchema(),79* which uses schemaTrees (a Hashtable - already sync). Potential conflict80* of duplicating construction of tree for same subschemasubentry81* but no inconsistency problems.82*83* NamingEnumerations link to LdapCtx for the following:84* 1. increment/decrement enum count so that ctx doesn't close the85* underlying connection86* 2. LdapClient handle to get next batch of results87* 3. Sets LdapCtx's response controls88* 4. Process return code89* 5. For narrowing response controls (using ctx's factories)90* Since processing of NamingEnumeration by client is treated the same as method91* invocation on LdapCtx, caller is responsible for locking.92*93* @author Vincent Ryan94* @author Rosanna Lee95*/9697final public class LdapCtx extends ComponentDirContext98implements EventDirContext, LdapContext {99100/*101* Used to store arguments to the search method.102*/103final static class SearchArgs {104Name name;105String filter;106SearchControls cons;107String[] reqAttrs; // those attributes originally requested108109SearchArgs(Name name, String filter, SearchControls cons, String[] ra) {110this.name = name;111this.filter = filter;112this.cons = cons;113this.reqAttrs = ra;114}115}116117private static final boolean debug = false;118119private static final boolean HARD_CLOSE = true;120private static final boolean SOFT_CLOSE = false;121122// ----------------- Constants -----------------123124public static final int DEFAULT_PORT = 389;125public static final int DEFAULT_SSL_PORT = 636;126public static final String DEFAULT_HOST = "localhost";127128private static final boolean DEFAULT_DELETE_RDN = true;129private static final boolean DEFAULT_TYPES_ONLY = false;130private static final int DEFAULT_DEREF_ALIASES = 3; // always deref131private static final int DEFAULT_LDAP_VERSION = LdapClient.LDAP_VERSION3_VERSION2;132private static final int DEFAULT_BATCH_SIZE = 1;133private static final int DEFAULT_REFERRAL_MODE = LdapClient.LDAP_REF_IGNORE;134private static final char DEFAULT_REF_SEPARATOR = '#';135136// Used by LdapPoolManager137static final String DEFAULT_SSL_FACTORY =138"javax.net.ssl.SSLSocketFactory"; // use Sun's SSL139private static final int DEFAULT_REFERRAL_LIMIT = 10;140private static final String STARTTLS_REQ_OID = "1.3.6.1.4.1.1466.20037";141142// schema operational and user attributes143private static final String[] SCHEMA_ATTRIBUTES =144{ "objectClasses", "attributeTypes", "matchingRules", "ldapSyntaxes" };145146// --------------- Environment property names ----------147148// LDAP protocol version: "2", "3"149private static final String VERSION = "java.naming.ldap.version";150151// Binary-valued attributes. Space separated string of attribute names.152private static final String BINARY_ATTRIBUTES =153"java.naming.ldap.attributes.binary";154155// Delete old RDN during modifyDN: "true", "false"156private static final String DELETE_RDN = "java.naming.ldap.deleteRDN";157158// De-reference aliases: "never", "searching", "finding", "always"159private static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";160161// Return only attribute types (no values)162private static final String TYPES_ONLY = "java.naming.ldap.typesOnly";163164// Separator character for encoding Reference's RefAddrs; default is '#'165private static final String REF_SEPARATOR = "java.naming.ldap.ref.separator";166167// Socket factory168private static final String SOCKET_FACTORY = "java.naming.ldap.factory.socket";169170// Bind Controls (used by LdapReferralException)171static final String BIND_CONTROLS = "java.naming.ldap.control.connect";172173private static final String REFERRAL_LIMIT =174"java.naming.ldap.referral.limit";175176// trace BER (java.io.OutputStream)177private static final String TRACE_BER = "com.sun.jndi.ldap.trace.ber";178179// Get around Netscape Schema Bugs180private static final String NETSCAPE_SCHEMA_BUG =181"com.sun.jndi.ldap.netscape.schemaBugs";182// deprecated183private static final String OLD_NETSCAPE_SCHEMA_BUG =184"com.sun.naming.netscape.schemaBugs"; // for backward compatibility185186// Timeout for socket connect187private static final String CONNECT_TIMEOUT =188"com.sun.jndi.ldap.connect.timeout";189190// Timeout for reading responses191private static final String READ_TIMEOUT =192"com.sun.jndi.ldap.read.timeout";193194// Environment property for connection pooling195private static final String ENABLE_POOL = "com.sun.jndi.ldap.connect.pool";196197// Environment property for the domain name (derived from this context's DN)198private static final String DOMAIN_NAME = "com.sun.jndi.ldap.domainname";199200// Block until the first search reply is received201private static final String WAIT_FOR_REPLY =202"com.sun.jndi.ldap.search.waitForReply";203204// Size of the queue of unprocessed search replies205private static final String REPLY_QUEUE_SIZE =206"com.sun.jndi.ldap.search.replyQueueSize";207208// System and environment property name to control allowed list of209// authentication mechanisms: "all" or "" or "mech1,mech2,...,mechN"210// "all": allow all mechanisms,211// "": allow none212// or comma separated list of allowed authentication mechanisms213// Note: "none" or "anonymous" are always allowed.214private static final String ALLOWED_MECHS_SP =215"jdk.jndi.ldap.mechsAllowedToSendCredentials";216217// System property value218private static final String ALLOWED_MECHS_SP_VALUE =219getMechsAllowedToSendCredentials();220221// Set of authentication mechanisms allowed by the system property222private static final Set<String> MECHS_ALLOWED_BY_SP =223getMechsFromPropertyValue(ALLOWED_MECHS_SP_VALUE);224225// The message to use in NamingException if the transmission of plain credentials are not allowed226private static final String UNSECURED_CRED_TRANSMIT_MSG =227"Transmission of credentials over unsecured connection is not allowed";228229// ----------------- Fields that don't change -----------------------230private static final NameParser parser = new LdapNameParser();231232// controls that Provider needs233private static final ControlFactory myResponseControlFactory =234new DefaultResponseControlFactory();235private static final Control manageReferralControl =236new ManageReferralControl(false);237238private static final HierMemDirCtx EMPTY_SCHEMA = new HierMemDirCtx();239static {240EMPTY_SCHEMA.setReadOnly(241new SchemaViolationException("Cannot update schema object"));242}243244// ------------ Package private instance variables ----------------245// Cannot be private; used by enums246247// ------- Inherited by derived context instances248249int port_number; // port number of server250String hostname = null; // host name of server (no brackets251// for IPv6 literals)252LdapClient clnt = null; // connection handle253Hashtable<String, java.lang.Object> envprops = null; // environment properties of context254int handleReferrals = DEFAULT_REFERRAL_MODE; // how referral is handled255boolean hasLdapsScheme = false; // true if the context was created256// using an LDAPS URL.257258// ------- Not inherited by derived context instances259260String currentDN; // DN of this context261Name currentParsedDN; // DN of this context262Vector<Control> respCtls = null; // Response controls read263Control[] reqCtls = null; // Controls to be sent with each request264// Used to track if context was seen to be secured with STARTTLS extended operation265volatile boolean contextSeenStartTlsEnabled;266267// ------------- Private instance variables ------------------------268269// ------- Inherited by derived context instances270271private OutputStream trace = null; // output stream for BER debug output272private boolean netscapeSchemaBug = false; // workaround273private Control[] bindCtls = null; // Controls to be sent with LDAP "bind"274private int referralHopLimit = DEFAULT_REFERRAL_LIMIT; // max referral275private Hashtable<String, DirContext> schemaTrees = null; // schema root of this context276private int batchSize = DEFAULT_BATCH_SIZE; // batch size for search results277private boolean deleteRDN = DEFAULT_DELETE_RDN; // delete the old RDN when modifying DN278private boolean typesOnly = DEFAULT_TYPES_ONLY; // return attribute types (no values)279private int derefAliases = DEFAULT_DEREF_ALIASES;// de-reference alias entries during searching280private char addrEncodingSeparator = DEFAULT_REF_SEPARATOR; // encoding RefAddr281282private Hashtable<String, Boolean> binaryAttrs = null; // attr values returned as byte[]283private int connectTimeout = -1; // no timeout value284private int readTimeout = -1; // no timeout value285private boolean waitForReply = true; // wait for search response286private int replyQueueSize = -1; // unlimited queue size287private boolean useSsl = false; // true if SSL protocol is active288private boolean useDefaultPortNumber = false; // no port number was supplied289290// ------- Not inherited by derived context instances291292// True if this context was created by another LdapCtx.293private boolean parentIsLdapCtx = false; // see composeName()294295private int hopCount = 1; // current referral hop count296private String url = null; // URL of context; see getURL()297private EventSupport eventSupport; // Event support helper for this ctx298private boolean unsolicited = false; // if there unsolicited listeners299private boolean sharable = true; // can share connection with other ctx300301// -------------- Constructors -----------------------------------302303@SuppressWarnings("unchecked")304public LdapCtx(String dn, String host, int port_number,305Hashtable<?,?> props,306boolean useSsl) throws NamingException {307308this.useSsl = this.hasLdapsScheme = useSsl;309310if (props != null) {311envprops = (Hashtable<String, java.lang.Object>) props.clone();312313// SSL env prop overrides the useSsl argument314if ("ssl".equals(envprops.get(Context.SECURITY_PROTOCOL))) {315this.useSsl = true;316}317318// %%% These are only examined when the context is created319// %%% because they are only for debugging or workaround purposes.320trace = (OutputStream)envprops.get(TRACE_BER);321322if (props.get(NETSCAPE_SCHEMA_BUG) != null ||323props.get(OLD_NETSCAPE_SCHEMA_BUG) != null) {324netscapeSchemaBug = true;325}326}327328currentDN = (dn != null) ? dn : "";329currentParsedDN = parser.parse(currentDN);330331hostname = (host != null && host.length() > 0) ? host : DEFAULT_HOST;332if (hostname.charAt(0) == '[') {333hostname = hostname.substring(1, hostname.length() - 1);334}335336if (port_number > 0) {337this.port_number = port_number;338} else {339this.port_number = this.useSsl ? DEFAULT_SSL_PORT : DEFAULT_PORT;340this.useDefaultPortNumber = true;341}342343schemaTrees = new Hashtable<>(11, 0.75f);344initEnv();345try {346connect(false);347} catch (NamingException e) {348try {349close();350} catch (Exception e2) {351// Nothing352}353throw e;354}355}356357LdapCtx(LdapCtx existing, String newDN) throws NamingException {358useSsl = existing.useSsl;359hasLdapsScheme = existing.hasLdapsScheme;360useDefaultPortNumber = existing.useDefaultPortNumber;361362hostname = existing.hostname;363port_number = existing.port_number;364currentDN = newDN;365if (existing.currentDN == currentDN) {366currentParsedDN = existing.currentParsedDN;367} else {368currentParsedDN = parser.parse(currentDN);369}370371envprops = existing.envprops;372schemaTrees = existing.schemaTrees;373374clnt = existing.clnt;375clnt.incRefCount();376377parentIsLdapCtx = ((newDN == null || newDN.equals(existing.currentDN))378? existing.parentIsLdapCtx379: true);380381// inherit these debugging/workaround flags382trace = existing.trace;383netscapeSchemaBug = existing.netscapeSchemaBug;384385initEnv();386}387388public LdapContext newInstance(Control[] reqCtls) throws NamingException {389390LdapContext clone = new LdapCtx(this, currentDN);391392// Connection controls are inherited from environment393394// Set clone's request controls395// setRequestControls() will clone reqCtls396clone.setRequestControls(reqCtls);397return clone;398}399400// --------------- Namespace Updates ---------------------401// -- bind/rebind/unbind402// -- rename403// -- createSubcontext/destroySubcontext404405protected void c_bind(Name name, Object obj, Continuation cont)406throws NamingException {407c_bind(name, obj, null, cont);408}409410/*411* attrs == null412* if obj is DirContext, attrs = obj.getAttributes()413* if attrs == null && obj == null414* disallow (cannot determine objectclass to use)415* if obj == null416* just create entry using attrs417* else418* objAttrs = create attributes for representing obj419* attrs += objAttrs420* create entry using attrs421*/422protected void c_bind(Name name, Object obj, Attributes attrs,423Continuation cont)424throws NamingException {425426cont.setError(this, name);427428Attributes inputAttrs = attrs; // Attributes supplied by caller429try {430ensureOpen();431432if (obj == null) {433if (attrs == null) {434throw new IllegalArgumentException(435"cannot bind null object with no attributes");436}437} else {438attrs = Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs,439false, name, this, envprops); // not cloned440}441442String newDN = fullyQualifiedName(name);443attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);444LdapEntry entry = new LdapEntry(newDN, attrs);445446LdapResult answer = clnt.add(entry, reqCtls);447respCtls = answer.resControls; // retrieve response controls448449if (answer.status != LdapClient.LDAP_SUCCESS) {450processReturnCode(answer, name);451}452453} catch (LdapReferralException e) {454if (handleReferrals == LdapClient.LDAP_REF_THROW)455throw cont.fillInException(e);456457// process the referrals sequentially458while (true) {459460LdapReferralContext refCtx =461(LdapReferralContext)e.getReferralContext(envprops, bindCtls);462463// repeat the original operation at the new context464try {465466refCtx.bind(name, obj, inputAttrs);467return;468469} catch (LdapReferralException re) {470e = re;471continue;472473} finally {474// Make sure we close referral context475refCtx.close();476}477}478479} catch (IOException e) {480NamingException e2 = new CommunicationException(e.getMessage());481e2.setRootCause(e);482throw cont.fillInException(e2);483484} catch (NamingException e) {485throw cont.fillInException(e);486}487}488489protected void c_rebind(Name name, Object obj, Continuation cont)490throws NamingException {491c_rebind(name, obj, null, cont);492}493494495/*496* attrs == null497* if obj is DirContext, attrs = obj.getAttributes().498* if attrs == null499* leave any existing attributes alone500* (set attrs = {objectclass=top} if object doesn't exist)501* else502* replace all existing attributes with attrs503* if obj == null504* just create entry using attrs505* else506* objAttrs = create attributes for representing obj507* attrs += objAttrs508* create entry using attrs509*/510protected void c_rebind(Name name, Object obj, Attributes attrs,511Continuation cont) throws NamingException {512513cont.setError(this, name);514515Attributes inputAttrs = attrs;516517try {518Attributes origAttrs = null;519520// Check if name is bound521try {522origAttrs = c_getAttributes(name, null, cont);523} catch (NameNotFoundException e) {}524525// Name not bound, just add it526if (origAttrs == null) {527c_bind(name, obj, attrs, cont);528return;529}530531// there's an object there already, need to figure out532// what to do about its attributes533534if (attrs == null && obj instanceof DirContext) {535attrs = ((DirContext)obj).getAttributes("");536}537Attributes keepAttrs = (Attributes)origAttrs.clone();538539if (attrs == null) {540// we're not changing any attrs, leave old attributes alone541542// Remove Java-related object classes from objectclass attribute543Attribute origObjectClass =544origAttrs.get(Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS]);545546if (origObjectClass != null) {547// clone so that keepAttrs is not affected548origObjectClass = (Attribute)origObjectClass.clone();549for (int i = 0; i < Obj.JAVA_OBJECT_CLASSES.length; i++) {550origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES_LOWER[i]);551origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES[i]);552}553// update;554origAttrs.put(origObjectClass);555}556557// remove all Java-related attributes except objectclass558for (int i = 1; i < Obj.JAVA_ATTRIBUTES.length; i++) {559origAttrs.remove(Obj.JAVA_ATTRIBUTES[i]);560}561562attrs = origAttrs;563}564if (obj != null) {565attrs =566Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs,567inputAttrs != attrs, name, this, envprops);568}569570String newDN = fullyQualifiedName(name);571// remove entry572LdapResult answer = clnt.delete(newDN, reqCtls);573respCtls = answer.resControls; // retrieve response controls574575if (answer.status != LdapClient.LDAP_SUCCESS) {576processReturnCode(answer, name);577return;578}579580Exception addEx = null;581try {582attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);583584// add it back using updated attrs585LdapEntry entry = new LdapEntry(newDN, attrs);586answer = clnt.add(entry, reqCtls);587if (answer.resControls != null) {588respCtls = appendVector(respCtls, answer.resControls);589}590} catch (NamingException | IOException ae) {591addEx = ae;592}593594if ((addEx != null && !(addEx instanceof LdapReferralException)) ||595answer.status != LdapClient.LDAP_SUCCESS) {596// Attempt to restore old entry597LdapResult answer2 =598clnt.add(new LdapEntry(newDN, keepAttrs), reqCtls);599if (answer2.resControls != null) {600respCtls = appendVector(respCtls, answer2.resControls);601}602603if (addEx == null) {604processReturnCode(answer, name);605}606}607608// Rethrow exception609if (addEx instanceof NamingException) {610throw (NamingException)addEx;611} else if (addEx instanceof IOException) {612throw (IOException)addEx;613}614615} catch (LdapReferralException e) {616if (handleReferrals == LdapClient.LDAP_REF_THROW)617throw cont.fillInException(e);618619// process the referrals sequentially620while (true) {621622LdapReferralContext refCtx =623(LdapReferralContext)e.getReferralContext(envprops, bindCtls);624625// repeat the original operation at the new context626try {627628refCtx.rebind(name, obj, inputAttrs);629return;630631} catch (LdapReferralException re) {632e = re;633continue;634635} finally {636// Make sure we close referral context637refCtx.close();638}639}640641} catch (IOException e) {642NamingException e2 = new CommunicationException(e.getMessage());643e2.setRootCause(e);644throw cont.fillInException(e2);645646} catch (NamingException e) {647throw cont.fillInException(e);648}649}650651protected void c_unbind(Name name, Continuation cont)652throws NamingException {653cont.setError(this, name);654655try {656ensureOpen();657658String fname = fullyQualifiedName(name);659LdapResult answer = clnt.delete(fname, reqCtls);660respCtls = answer.resControls; // retrieve response controls661662adjustDeleteStatus(fname, answer);663664if (answer.status != LdapClient.LDAP_SUCCESS) {665processReturnCode(answer, name);666}667668} catch (LdapReferralException e) {669if (handleReferrals == LdapClient.LDAP_REF_THROW)670throw cont.fillInException(e);671672// process the referrals sequentially673while (true) {674675LdapReferralContext refCtx =676(LdapReferralContext)e.getReferralContext(envprops, bindCtls);677678// repeat the original operation at the new context679try {680681refCtx.unbind(name);682return;683684} catch (LdapReferralException re) {685e = re;686continue;687688} finally {689// Make sure we close referral context690refCtx.close();691}692}693694} catch (IOException e) {695NamingException e2 = new CommunicationException(e.getMessage());696e2.setRootCause(e);697throw cont.fillInException(e2);698699} catch (NamingException e) {700throw cont.fillInException(e);701}702}703704protected void c_rename(Name oldName, Name newName, Continuation cont)705throws NamingException706{707Name oldParsed, newParsed;708Name oldParent, newParent;709String newRDN = null;710String newSuperior = null;711712// assert (oldName instanceOf CompositeName);713714cont.setError(this, oldName);715716try {717ensureOpen();718719// permit oldName to be empty (for processing referral contexts)720if (oldName.isEmpty()) {721oldParent = parser.parse("");722} else {723oldParsed = parser.parse(oldName.get(0)); // extract DN & parse724oldParent = oldParsed.getPrefix(oldParsed.size() - 1);725}726727if (newName instanceof CompositeName) {728newParsed = parser.parse(newName.get(0)); // extract DN & parse729} else {730newParsed = newName; // CompoundName/LdapName is already parsed731}732newParent = newParsed.getPrefix(newParsed.size() - 1);733734if(!oldParent.equals(newParent)) {735if (!clnt.isLdapv3) {736throw new InvalidNameException(737"LDAPv2 doesn't support changing " +738"the parent as a result of a rename");739} else {740newSuperior = fullyQualifiedName(newParent.toString());741}742}743744newRDN = newParsed.get(newParsed.size() - 1);745746LdapResult answer = clnt.moddn(fullyQualifiedName(oldName),747newRDN,748deleteRDN,749newSuperior,750reqCtls);751respCtls = answer.resControls; // retrieve response controls752753if (answer.status != LdapClient.LDAP_SUCCESS) {754processReturnCode(answer, oldName);755}756757} catch (LdapReferralException e) {758759// Record the new RDN (for use after the referral is followed).760e.setNewRdn(newRDN);761762// Cannot continue when a referral has been received and a763// newSuperior name was supplied (because the newSuperior is764// relative to a naming context BEFORE the referral is followed).765if (newSuperior != null) {766PartialResultException pre = new PartialResultException(767"Cannot continue referral processing when newSuperior is " +768"nonempty: " + newSuperior);769pre.setRootCause(cont.fillInException(e));770throw cont.fillInException(pre);771}772773if (handleReferrals == LdapClient.LDAP_REF_THROW)774throw cont.fillInException(e);775776// process the referrals sequentially777while (true) {778779LdapReferralContext refCtx =780(LdapReferralContext)e.getReferralContext(envprops, bindCtls);781782// repeat the original operation at the new context783try {784785refCtx.rename(oldName, newName);786return;787788} catch (LdapReferralException re) {789e = re;790continue;791792} finally {793// Make sure we close referral context794refCtx.close();795}796}797798} catch (IOException e) {799NamingException e2 = new CommunicationException(e.getMessage());800e2.setRootCause(e);801throw cont.fillInException(e2);802803} catch (NamingException e) {804throw cont.fillInException(e);805}806}807808protected Context c_createSubcontext(Name name, Continuation cont)809throws NamingException {810return c_createSubcontext(name, null, cont);811}812813protected DirContext c_createSubcontext(Name name, Attributes attrs,814Continuation cont)815throws NamingException {816cont.setError(this, name);817818Attributes inputAttrs = attrs;819try {820ensureOpen();821if (attrs == null) {822// add structural objectclass; name needs to have "cn"823Attribute oc = new BasicAttribute(824Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS],825Obj.JAVA_OBJECT_CLASSES[Obj.STRUCTURAL]);826oc.add("top");827attrs = new BasicAttributes(true); // case ignore828attrs.put(oc);829}830String newDN = fullyQualifiedName(name);831attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);832833LdapEntry entry = new LdapEntry(newDN, attrs);834835LdapResult answer = clnt.add(entry, reqCtls);836respCtls = answer.resControls; // retrieve response controls837838if (answer.status != LdapClient.LDAP_SUCCESS) {839processReturnCode(answer, name);840return null;841}842843// creation successful, get back live object844return new LdapCtx(this, newDN);845846} catch (LdapReferralException e) {847if (handleReferrals == LdapClient.LDAP_REF_THROW)848throw cont.fillInException(e);849850// process the referrals sequentially851while (true) {852853LdapReferralContext refCtx =854(LdapReferralContext)e.getReferralContext(envprops, bindCtls);855856// repeat the original operation at the new context857try {858859return refCtx.createSubcontext(name, inputAttrs);860861} catch (LdapReferralException re) {862e = re;863continue;864865} finally {866// Make sure we close referral context867refCtx.close();868}869}870871} catch (IOException e) {872NamingException e2 = new CommunicationException(e.getMessage());873e2.setRootCause(e);874throw cont.fillInException(e2);875876} catch (NamingException e) {877throw cont.fillInException(e);878}879}880881protected void c_destroySubcontext(Name name, Continuation cont)882throws NamingException {883cont.setError(this, name);884885try {886ensureOpen();887888String fname = fullyQualifiedName(name);889LdapResult answer = clnt.delete(fname, reqCtls);890respCtls = answer.resControls; // retrieve response controls891892adjustDeleteStatus(fname, answer);893894if (answer.status != LdapClient.LDAP_SUCCESS) {895processReturnCode(answer, name);896}897898} catch (LdapReferralException e) {899if (handleReferrals == LdapClient.LDAP_REF_THROW)900throw cont.fillInException(e);901902// process the referrals sequentially903while (true) {904905LdapReferralContext refCtx =906(LdapReferralContext)e.getReferralContext(envprops, bindCtls);907908// repeat the original operation at the new context909try {910911refCtx.destroySubcontext(name);912return;913} catch (LdapReferralException re) {914e = re;915continue;916} finally {917// Make sure we close referral context918refCtx.close();919}920}921} catch (IOException e) {922NamingException e2 = new CommunicationException(e.getMessage());923e2.setRootCause(e);924throw cont.fillInException(e2);925} catch (NamingException e) {926throw cont.fillInException(e);927}928}929930/**931* Adds attributes from RDN to attrs if not already present.932* Note that if attrs already contains an attribute by the same name,933* or if the distinguished name is empty, then leave attrs unchanged.934*935* @param dn The non-null DN of the entry to add936* @param attrs The non-null attributes of entry to add937* @param directUpdate Whether attrs can be updated directly938* @returns Non-null attributes with attributes from the RDN added939*/940private static Attributes addRdnAttributes(String dn, Attributes attrs,941boolean directUpdate) throws NamingException {942943// Handle the empty name944if (dn.equals("")) {945return attrs;946}947948// Parse string name into list of RDNs949List<Rdn> rdnList = (new LdapName(dn)).getRdns();950951// Get leaf RDN952Rdn rdn = rdnList.get(rdnList.size() - 1);953Attributes nameAttrs = rdn.toAttributes();954955// Add attributes of RDN to attrs if not already there956NamingEnumeration<? extends Attribute> enum_ = nameAttrs.getAll();957Attribute nameAttr;958while (enum_.hasMore()) {959nameAttr = enum_.next();960961// If attrs already has the attribute, don't change or add to it962if (attrs.get(nameAttr.getID()) == null) {963964/**965* When attrs.isCaseIgnored() is false, attrs.get() will966* return null when the case mis-matches for otherwise967* equal attrIDs.968* As the attrIDs' case is irrelevant for LDAP, ignore969* the case of attrIDs even when attrs.isCaseIgnored() is970* false. This is done by explicitly comparing the elements in971* the enumeration of IDs with their case ignored.972*/973if (!attrs.isCaseIgnored() &&974containsIgnoreCase(attrs.getIDs(), nameAttr.getID())) {975continue;976}977978if (!directUpdate) {979attrs = (Attributes)attrs.clone();980directUpdate = true;981}982attrs.put(nameAttr);983}984}985986return attrs;987}988989990private static boolean containsIgnoreCase(NamingEnumeration<String> enumStr,991String str) throws NamingException {992String strEntry;993994while (enumStr.hasMore()) {995strEntry = enumStr.next();996if (strEntry.equalsIgnoreCase(str)) {997return true;998}999}1000return false;1001}100210031004private void adjustDeleteStatus(String fname, LdapResult answer) {1005if (answer.status == LdapClient.LDAP_NO_SUCH_OBJECT &&1006answer.matchedDN != null) {1007try {1008// %%% RL: are there any implications for referrals?10091010Name orig = parser.parse(fname);1011Name matched = parser.parse(answer.matchedDN);1012if ((orig.size() - matched.size()) == 1)1013answer.status = LdapClient.LDAP_SUCCESS;1014} catch (NamingException e) {}1015}1016}10171018/*1019* Append the the second Vector onto the first Vector1020* (v2 must be non-null)1021*/1022private static <T> Vector<T> appendVector(Vector<T> v1, Vector<T> v2) {1023if (v1 == null) {1024v1 = v2;1025} else {1026for (int i = 0; i < v2.size(); i++) {1027v1.addElement(v2.elementAt(i));1028}1029}1030return v1;1031}10321033// ------------- Lookups and Browsing -------------------------1034// lookup/lookupLink1035// list/listBindings10361037protected Object c_lookupLink(Name name, Continuation cont)1038throws NamingException {1039return c_lookup(name, cont);1040}10411042protected Object c_lookup(Name name, Continuation cont)1043throws NamingException {1044cont.setError(this, name);1045Object obj = null;1046Attributes attrs;10471048try {1049SearchControls cons = new SearchControls();1050cons.setSearchScope(SearchControls.OBJECT_SCOPE);1051cons.setReturningAttributes(null); // ask for all attributes1052cons.setReturningObjFlag(true); // need values to construct obj10531054LdapResult answer = doSearchOnce(name, "(objectClass=*)", cons, true);1055respCtls = answer.resControls; // retrieve response controls10561057// should get back 1 SearchResponse and 1 SearchResult10581059if (answer.status != LdapClient.LDAP_SUCCESS) {1060processReturnCode(answer, name);1061}10621063if (answer.entries == null || answer.entries.size() != 1) {1064// found it but got no attributes1065attrs = new BasicAttributes(LdapClient.caseIgnore);1066} else {1067LdapEntry entry = answer.entries.elementAt(0);1068attrs = entry.attributes;10691070Vector<Control> entryCtls = entry.respCtls; // retrieve entry controls1071if (entryCtls != null) {1072appendVector(respCtls, entryCtls); // concatenate controls1073}1074}10751076if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) {1077// serialized object or object reference1078obj = Obj.decodeObject(attrs);1079}1080if (obj == null) {1081obj = new LdapCtx(this, fullyQualifiedName(name));1082}1083} catch (LdapReferralException e) {1084if (handleReferrals == LdapClient.LDAP_REF_THROW)1085throw cont.fillInException(e);10861087// process the referrals sequentially1088while (true) {10891090LdapReferralContext refCtx =1091(LdapReferralContext)e.getReferralContext(envprops, bindCtls);1092// repeat the original operation at the new context1093try {10941095return refCtx.lookup(name);10961097} catch (LdapReferralException re) {1098e = re;1099continue;11001101} finally {1102// Make sure we close referral context1103refCtx.close();1104}1105}11061107} catch (NamingException e) {1108throw cont.fillInException(e);1109}11101111try {1112return DirectoryManager.getObjectInstance(obj, name,1113this, envprops, attrs);11141115} catch (NamingException e) {1116throw cont.fillInException(e);11171118} catch (Exception e) {1119NamingException e2 = new NamingException(1120"problem generating object using object factory");1121e2.setRootCause(e);1122throw cont.fillInException(e2);1123}1124}11251126protected NamingEnumeration<NameClassPair> c_list(Name name, Continuation cont)1127throws NamingException {1128SearchControls cons = new SearchControls();1129String[] classAttrs = new String[2];11301131classAttrs[0] = Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS];1132classAttrs[1] = Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME];1133cons.setReturningAttributes(classAttrs);11341135// set this flag to override the typesOnly flag1136cons.setReturningObjFlag(true);11371138cont.setError(this, name);11391140LdapResult answer = null;11411142try {1143answer = doSearch(name, "(objectClass=*)", cons, true, true);11441145// list result may contain continuation references1146if ((answer.status != LdapClient.LDAP_SUCCESS) ||1147(answer.referrals != null)) {1148processReturnCode(answer, name);1149}11501151return new LdapNamingEnumeration(this, answer, name, cont);11521153} catch (LdapReferralException e) {1154if (handleReferrals == LdapClient.LDAP_REF_THROW)1155throw cont.fillInException(e);11561157// process the referrals sequentially1158while (true) {11591160LdapReferralContext refCtx =1161(LdapReferralContext)e.getReferralContext(envprops, bindCtls);11621163// repeat the original operation at the new context1164try {11651166return refCtx.list(name);11671168} catch (LdapReferralException re) {1169e = re;1170continue;11711172} finally {1173// Make sure we close referral context1174refCtx.close();1175}1176}11771178} catch (LimitExceededException e) {1179LdapNamingEnumeration res =1180new LdapNamingEnumeration(this, answer, name, cont);11811182res.setNamingException(1183(LimitExceededException)cont.fillInException(e));1184return res;11851186} catch (PartialResultException e) {1187LdapNamingEnumeration res =1188new LdapNamingEnumeration(this, answer, name, cont);11891190res.setNamingException(1191(PartialResultException)cont.fillInException(e));1192return res;11931194} catch (NamingException e) {1195throw cont.fillInException(e);1196}1197}11981199protected NamingEnumeration<Binding> c_listBindings(Name name, Continuation cont)1200throws NamingException {12011202SearchControls cons = new SearchControls();1203cons.setReturningAttributes(null); // ask for all attributes1204cons.setReturningObjFlag(true); // need values to construct obj12051206cont.setError(this, name);12071208LdapResult answer = null;12091210try {1211answer = doSearch(name, "(objectClass=*)", cons, true, true);12121213// listBindings result may contain continuation references1214if ((answer.status != LdapClient.LDAP_SUCCESS) ||1215(answer.referrals != null)) {1216processReturnCode(answer, name);1217}12181219return new LdapBindingEnumeration(this, answer, name, cont);12201221} catch (LdapReferralException e) {1222if (handleReferrals == LdapClient.LDAP_REF_THROW)1223throw cont.fillInException(e);12241225// process the referrals sequentially1226while (true) {1227@SuppressWarnings("unchecked")1228LdapReferralContext refCtx =1229(LdapReferralContext)e.getReferralContext(envprops, bindCtls);12301231// repeat the original operation at the new context1232try {12331234return refCtx.listBindings(name);12351236} catch (LdapReferralException re) {1237e = re;1238continue;12391240} finally {1241// Make sure we close referral context1242refCtx.close();1243}1244}1245} catch (LimitExceededException e) {1246LdapBindingEnumeration res =1247new LdapBindingEnumeration(this, answer, name, cont);12481249res.setNamingException(cont.fillInException(e));1250return res;12511252} catch (PartialResultException e) {1253LdapBindingEnumeration res =1254new LdapBindingEnumeration(this, answer, name, cont);12551256res.setNamingException(cont.fillInException(e));1257return res;12581259} catch (NamingException e) {1260throw cont.fillInException(e);1261}1262}12631264// --------------- Name-related Methods -----------------------1265// -- getNameParser/getNameInNamespace/composeName12661267protected NameParser c_getNameParser(Name name, Continuation cont)1268throws NamingException1269{1270// ignore name, always return same parser1271cont.setSuccess();1272return parser;1273}12741275public String getNameInNamespace() {1276return currentDN;1277}12781279public Name composeName(Name name, Name prefix)1280throws NamingException1281{1282Name result;12831284// Handle compound names. A pair of LdapNames is an easy case.1285if ((name instanceof LdapName) && (prefix instanceof LdapName)) {1286result = (Name)(prefix.clone());1287result.addAll(name);1288return new CompositeName().add(result.toString());1289}1290if (!(name instanceof CompositeName)) {1291name = new CompositeName().add(name.toString());1292}1293if (!(prefix instanceof CompositeName)) {1294prefix = new CompositeName().add(prefix.toString());1295}12961297int prefixLast = prefix.size() - 1;12981299if (name.isEmpty() || prefix.isEmpty() ||1300name.get(0).equals("") || prefix.get(prefixLast).equals("")) {1301return super.composeName(name, prefix);1302}13031304result = (Name)(prefix.clone());1305result.addAll(name);13061307if (parentIsLdapCtx) {1308String ldapComp = concatNames(result.get(prefixLast + 1),1309result.get(prefixLast));1310result.remove(prefixLast + 1);1311result.remove(prefixLast);1312result.add(prefixLast, ldapComp);1313}1314return result;1315}13161317private String fullyQualifiedName(Name rel) {1318return rel.isEmpty()1319? currentDN1320: fullyQualifiedName(rel.get(0));1321}13221323private String fullyQualifiedName(String rel) {1324return (concatNames(rel, currentDN));1325}13261327// used by LdapSearchEnumeration1328private static String concatNames(String lesser, String greater) {1329if (lesser == null || lesser.equals("")) {1330return greater;1331} else if (greater == null || greater.equals("")) {1332return lesser;1333} else {1334return (lesser + "," + greater);1335}1336}13371338// --------------- Reading and Updating Attributes1339// getAttributes/modifyAttributes13401341protected Attributes c_getAttributes(Name name, String[] attrIds,1342Continuation cont)1343throws NamingException {1344cont.setError(this, name);13451346SearchControls cons = new SearchControls();1347cons.setSearchScope(SearchControls.OBJECT_SCOPE);1348cons.setReturningAttributes(attrIds);13491350try {1351LdapResult answer =1352doSearchOnce(name, "(objectClass=*)", cons, true);1353respCtls = answer.resControls; // retrieve response controls13541355if (answer.status != LdapClient.LDAP_SUCCESS) {1356processReturnCode(answer, name);1357}13581359if (answer.entries == null || answer.entries.size() != 1) {1360return new BasicAttributes(LdapClient.caseIgnore);1361}13621363// get attributes from result1364LdapEntry entry = answer.entries.elementAt(0);13651366Vector<Control> entryCtls = entry.respCtls; // retrieve entry controls1367if (entryCtls != null) {1368appendVector(respCtls, entryCtls); // concatenate controls1369}13701371// do this so attributes can find their schema1372setParents(entry.attributes, (Name) name.clone());13731374return (entry.attributes);13751376} catch (LdapReferralException e) {1377if (handleReferrals == LdapClient.LDAP_REF_THROW)1378throw cont.fillInException(e);13791380// process the referrals sequentially1381while (true) {13821383LdapReferralContext refCtx =1384(LdapReferralContext)e.getReferralContext(envprops, bindCtls);13851386// repeat the original operation at the new context1387try {13881389return refCtx.getAttributes(name, attrIds);13901391} catch (LdapReferralException re) {1392e = re;1393continue;13941395} finally {1396// Make sure we close referral context1397refCtx.close();1398}1399}14001401} catch (NamingException e) {1402throw cont.fillInException(e);1403}1404}14051406protected void c_modifyAttributes(Name name, int mod_op, Attributes attrs,1407Continuation cont)1408throws NamingException {14091410cont.setError(this, name);14111412try {1413ensureOpen();14141415if (attrs == null || attrs.size() == 0) {1416return; // nothing to do1417}1418String newDN = fullyQualifiedName(name);1419int jmod_op = convertToLdapModCode(mod_op);14201421// construct mod list1422int[] jmods = new int[attrs.size()];1423Attribute[] jattrs = new Attribute[attrs.size()];14241425NamingEnumeration<? extends Attribute> ae = attrs.getAll();1426for(int i = 0; i < jmods.length && ae.hasMore(); i++) {1427jmods[i] = jmod_op;1428jattrs[i] = ae.next();1429}14301431LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls);1432respCtls = answer.resControls; // retrieve response controls14331434if (answer.status != LdapClient.LDAP_SUCCESS) {1435processReturnCode(answer, name);1436return;1437}14381439} catch (LdapReferralException e) {1440if (handleReferrals == LdapClient.LDAP_REF_THROW)1441throw cont.fillInException(e);14421443// process the referrals sequentially1444while (true) {14451446LdapReferralContext refCtx =1447(LdapReferralContext)e.getReferralContext(envprops, bindCtls);14481449// repeat the original operation at the new context1450try {14511452refCtx.modifyAttributes(name, mod_op, attrs);1453return;14541455} catch (LdapReferralException re) {1456e = re;1457continue;14581459} finally {1460// Make sure we close referral context1461refCtx.close();1462}1463}14641465} catch (IOException e) {1466NamingException e2 = new CommunicationException(e.getMessage());1467e2.setRootCause(e);1468throw cont.fillInException(e2);14691470} catch (NamingException e) {1471throw cont.fillInException(e);1472}1473}14741475protected void c_modifyAttributes(Name name, ModificationItem[] mods,1476Continuation cont)1477throws NamingException {1478cont.setError(this, name);14791480try {1481ensureOpen();14821483if (mods == null || mods.length == 0) {1484return; // nothing to do1485}1486String newDN = fullyQualifiedName(name);14871488// construct mod list1489int[] jmods = new int[mods.length];1490Attribute[] jattrs = new Attribute[mods.length];1491ModificationItem mod;1492for (int i = 0; i < jmods.length; i++) {1493mod = mods[i];1494jmods[i] = convertToLdapModCode(mod.getModificationOp());1495jattrs[i] = mod.getAttribute();1496}14971498LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls);1499respCtls = answer.resControls; // retrieve response controls15001501if (answer.status != LdapClient.LDAP_SUCCESS) {1502processReturnCode(answer, name);1503}15041505} catch (LdapReferralException e) {1506if (handleReferrals == LdapClient.LDAP_REF_THROW)1507throw cont.fillInException(e);15081509// process the referrals sequentially1510while (true) {15111512LdapReferralContext refCtx =1513(LdapReferralContext)e.getReferralContext(envprops, bindCtls);15141515// repeat the original operation at the new context1516try {15171518refCtx.modifyAttributes(name, mods);1519return;15201521} catch (LdapReferralException re) {1522e = re;1523continue;15241525} finally {1526// Make sure we close referral context1527refCtx.close();1528}1529}15301531} catch (IOException e) {1532NamingException e2 = new CommunicationException(e.getMessage());1533e2.setRootCause(e);1534throw cont.fillInException(e2);15351536} catch (NamingException e) {1537throw cont.fillInException(e);1538}1539}15401541private static int convertToLdapModCode(int mod_op) {1542switch (mod_op) {1543case DirContext.ADD_ATTRIBUTE:1544return(LdapClient.ADD);15451546case DirContext.REPLACE_ATTRIBUTE:1547return (LdapClient.REPLACE);15481549case DirContext.REMOVE_ATTRIBUTE:1550return (LdapClient.DELETE);15511552default:1553throw new IllegalArgumentException("Invalid modification code");1554}1555}15561557// ------------------- Schema -----------------------15581559protected DirContext c_getSchema(Name name, Continuation cont)1560throws NamingException {1561cont.setError(this, name);1562try {1563return getSchemaTree(name);15641565} catch (NamingException e) {1566throw cont.fillInException(e);1567}1568}15691570protected DirContext c_getSchemaClassDefinition(Name name,1571Continuation cont)1572throws NamingException {1573cont.setError(this, name);15741575try {1576// retrieve the objectClass attribute from LDAP1577Attribute objectClassAttr = c_getAttributes(name,1578new String[]{"objectclass"}, cont).get("objectclass");1579if (objectClassAttr == null || objectClassAttr.size() == 0) {1580return EMPTY_SCHEMA;1581}15821583// retrieve the root of the ObjectClass schema tree1584Context ocSchema = (Context) c_getSchema(name, cont).lookup(1585LdapSchemaParser.OBJECTCLASS_DEFINITION_NAME);15861587// create a context to hold the schema objects representing the object1588// classes1589HierMemDirCtx objectClassCtx = new HierMemDirCtx();1590DirContext objectClassDef;1591String objectClassName;1592for (Enumeration<?> objectClasses = objectClassAttr.getAll();1593objectClasses.hasMoreElements(); ) {1594objectClassName = (String)objectClasses.nextElement();1595// %%% Should we fail if not found, or just continue?1596objectClassDef = (DirContext)ocSchema.lookup(objectClassName);1597objectClassCtx.bind(objectClassName, objectClassDef);1598}15991600// Make context read-only1601objectClassCtx.setReadOnly(1602new SchemaViolationException("Cannot update schema object"));1603return (DirContext)objectClassCtx;16041605} catch (NamingException e) {1606throw cont.fillInException(e);1607}1608}16091610/*1611* getSchemaTree first looks to see if we have already built a1612* schema tree for the given entry. If not, it builds a new one and1613* stores it in our private hash table1614*/1615private DirContext getSchemaTree(Name name) throws NamingException {1616String subschemasubentry = getSchemaEntry(name, true);16171618DirContext schemaTree = schemaTrees.get(subschemasubentry);16191620if(schemaTree==null) {1621if(debug){System.err.println("LdapCtx: building new schema tree " + this);}1622schemaTree = buildSchemaTree(subschemasubentry);1623schemaTrees.put(subschemasubentry, schemaTree);1624}16251626return schemaTree;1627}16281629/*1630* buildSchemaTree builds the schema tree corresponding to the1631* given subschemasubentree1632*/1633private DirContext buildSchemaTree(String subschemasubentry)1634throws NamingException {16351636// get the schema entry itself1637// DO ask for return object here because we need it to1638// create context. Since asking for all attrs, we won't1639// be transmitting any specific attrIDs (like Java-specific ones).1640SearchControls constraints = new1641SearchControls(SearchControls.OBJECT_SCOPE,16420, 0, /* count and time limits */1643SCHEMA_ATTRIBUTES /* return schema attrs */,1644true /* return obj */,1645false /*deref link */ );16461647Name sse = (new CompositeName()).add(subschemasubentry);1648NamingEnumeration<SearchResult> results =1649searchAux(sse, "(objectClass=subschema)", constraints,1650false, true, new Continuation());16511652if(!results.hasMore()) {1653throw new OperationNotSupportedException(1654"Cannot get read subschemasubentry: " + subschemasubentry);1655}1656SearchResult result = results.next();1657results.close();16581659Object obj = result.getObject();1660if(!(obj instanceof LdapCtx)) {1661throw new NamingException(1662"Cannot get schema object as DirContext: " + subschemasubentry);1663}16641665return LdapSchemaCtx.createSchemaTree(envprops, subschemasubentry,1666(LdapCtx)obj /* schema entry */,1667result.getAttributes() /* schema attributes */,1668netscapeSchemaBug);1669}16701671/*1672* getSchemaEntree returns the DN of the subschemasubentree for the1673* given entree. It first looks to see if the given entry has1674* a subschema different from that of the root DIT (by looking for1675* a "subschemasubentry" attribute). If it doesn't find one, it returns1676* the one for the root of the DIT (by looking for the root's1677* "subschemasubentry" attribute).1678*1679* This function is called regardless of the server's version, since1680* an administrator may have setup the server to support client schema1681* queries. If this function trys a serarch on a v2 server that1682* doesn't support schema, one of these two things will happen:1683* 1) It will get an exception when querying the root DSE1684* 2) It will not find a subschemasubentry on the root DSE1685* If either of these things occur and the server is not v3, we1686* throw OperationNotSupported.1687*1688* the relative flag tells whether the given name is relative to this1689* context.1690*/1691private String getSchemaEntry(Name name, boolean relative)1692throws NamingException {16931694// Asks for operational attribute "subschemasubentry"1695SearchControls constraints = new SearchControls(SearchControls.OBJECT_SCOPE,16960, 0, /* count and time limits */1697new String[]{"subschemasubentry"} /* attr to return */,1698false /* returning obj */,1699false /* deref link */);17001701NamingEnumeration<SearchResult> results;1702try {1703results = searchAux(name, "objectclass=*", constraints, relative,1704true, new Continuation());17051706} catch (NamingException ne) {1707if (!clnt.isLdapv3 && currentDN.length() == 0 && name.isEmpty()) {1708// we got an error looking for a root entry on an ldapv21709// server. The server must not support schema.1710throw new OperationNotSupportedException(1711"Cannot get schema information from server");1712} else {1713throw ne;1714}1715}17161717if (!results.hasMoreElements()) {1718throw new ConfigurationException(1719"Requesting schema of nonexistent entry: " + name);1720}17211722SearchResult result = results.next();1723results.close();17241725Attribute schemaEntryAttr =1726result.getAttributes().get("subschemasubentry");1727//System.err.println("schema entry attrs: " + schemaEntryAttr);17281729if (schemaEntryAttr == null || schemaEntryAttr.size() < 0) {1730if (currentDN.length() == 0 && name.isEmpty()) {1731// the server doesn't have a subschemasubentry in its root DSE.1732// therefore, it doesn't support schema.1733throw new OperationNotSupportedException(1734"Cannot read subschemasubentry of root DSE");1735} else {1736return getSchemaEntry(new CompositeName(), false);1737}1738}17391740return (String)(schemaEntryAttr.get()); // return schema entry name1741}17421743// package-private; used by search enum.1744// Set attributes to point to this context in case some one1745// asked for their schema1746void setParents(Attributes attrs, Name name) throws NamingException {1747NamingEnumeration<? extends Attribute> ae = attrs.getAll();1748while(ae.hasMore()) {1749((LdapAttribute) ae.next()).setParent(this, name);1750}1751}17521753/*1754* Returns the URL associated with this context; used by LdapAttribute1755* after deserialization to get pointer to this context.1756*/1757String getURL() {1758if (url == null) {1759url = LdapURL.toUrlString(hostname, port_number, currentDN,1760hasLdapsScheme);1761}17621763return url;1764}17651766// --------------------- Searches -----------------------------1767protected NamingEnumeration<SearchResult> c_search(Name name,1768Attributes matchingAttributes,1769Continuation cont)1770throws NamingException {1771return c_search(name, matchingAttributes, null, cont);1772}17731774protected NamingEnumeration<SearchResult> c_search(Name name,1775Attributes matchingAttributes,1776String[] attributesToReturn,1777Continuation cont)1778throws NamingException {1779SearchControls cons = new SearchControls();1780cons.setReturningAttributes(attributesToReturn);1781String filter;1782try {1783filter = SearchFilter.format(matchingAttributes);1784} catch (NamingException e) {1785cont.setError(this, name);1786throw cont.fillInException(e);1787}1788return c_search(name, filter, cons, cont);1789}17901791protected NamingEnumeration<SearchResult> c_search(Name name,1792String filter,1793SearchControls cons,1794Continuation cont)1795throws NamingException {1796return searchAux(name, filter, cloneSearchControls(cons), true,1797waitForReply, cont);1798}17991800protected NamingEnumeration<SearchResult> c_search(Name name,1801String filterExpr,1802Object[] filterArgs,1803SearchControls cons,1804Continuation cont)1805throws NamingException {1806String strfilter;1807try {1808strfilter = SearchFilter.format(filterExpr, filterArgs);1809} catch (NamingException e) {1810cont.setError(this, name);1811throw cont.fillInException(e);1812}1813return c_search(name, strfilter, cons, cont);1814}18151816// Used by NamingNotifier1817NamingEnumeration<SearchResult> searchAux(Name name,1818String filter,1819SearchControls cons,1820boolean relative,1821boolean waitForReply, Continuation cont) throws NamingException {18221823LdapResult answer = null;1824String[] tokens = new String[2]; // stores ldap compare op. values1825String[] reqAttrs; // remember what was asked18261827if (cons == null) {1828cons = new SearchControls();1829}1830reqAttrs = cons.getReturningAttributes();18311832// if objects are requested then request the Java attributes too1833// so that the objects can be constructed1834if (cons.getReturningObjFlag()) {1835if (reqAttrs != null) {18361837// check for presence of "*" (user attributes wildcard)1838boolean hasWildcard = false;1839for (int i = reqAttrs.length - 1; i >= 0; i--) {1840if (reqAttrs[i].equals("*")) {1841hasWildcard = true;1842break;1843}1844}1845if (! hasWildcard) {1846String[] totalAttrs =1847new String[reqAttrs.length +Obj.JAVA_ATTRIBUTES.length];1848System.arraycopy(reqAttrs, 0, totalAttrs, 0,1849reqAttrs.length);1850System.arraycopy(Obj.JAVA_ATTRIBUTES, 0, totalAttrs,1851reqAttrs.length, Obj.JAVA_ATTRIBUTES.length);18521853cons.setReturningAttributes(totalAttrs);1854}1855}1856}18571858LdapCtx.SearchArgs args =1859new LdapCtx.SearchArgs(name, filter, cons, reqAttrs);18601861cont.setError(this, name);1862try {1863// see if this can be done as a compare, otherwise do a search1864if (searchToCompare(filter, cons, tokens)){1865//System.err.println("compare triggered");1866answer = compare(name, tokens[0], tokens[1]);1867if (! (answer.compareToSearchResult(fullyQualifiedName(name)))){1868processReturnCode(answer, name);1869}1870} else {1871answer = doSearch(name, filter, cons, relative, waitForReply);1872// search result may contain referrals1873processReturnCode(answer, name);1874}1875return new LdapSearchEnumeration(this, answer,1876fullyQualifiedName(name),1877args, cont);18781879} catch (LdapReferralException e) {1880if (handleReferrals == LdapClient.LDAP_REF_THROW)1881throw cont.fillInException(e);18821883// process the referrals sequentially1884while (true) {18851886@SuppressWarnings("unchecked")1887LdapReferralContext refCtx = (LdapReferralContext)1888e.getReferralContext(envprops, bindCtls);18891890// repeat the original operation at the new context1891try {18921893return refCtx.search(name, filter, cons);18941895} catch (LdapReferralException re) {1896e = re;1897continue;18981899} finally {1900// Make sure we close referral context1901refCtx.close();1902}1903}19041905} catch (LimitExceededException e) {1906LdapSearchEnumeration res =1907new LdapSearchEnumeration(this, answer, fullyQualifiedName(name),1908args, cont);1909res.setNamingException(e);1910return res;19111912} catch (PartialResultException e) {1913LdapSearchEnumeration res =1914new LdapSearchEnumeration(this, answer, fullyQualifiedName(name),1915args, cont);19161917res.setNamingException(e);1918return res;19191920} catch (IOException e) {1921NamingException e2 = new CommunicationException(e.getMessage());1922e2.setRootCause(e);1923throw cont.fillInException(e2);19241925} catch (NamingException e) {1926throw cont.fillInException(e);1927}1928}192919301931LdapResult getSearchReply(LdapClient eClnt, LdapResult res)1932throws NamingException {1933// ensureOpen() won't work here because1934// session was associated with previous connection19351936// %%% RL: we can actually allow the enumeration to continue1937// using the old handle but other weird things might happen1938// when we hit a referral1939if (clnt != eClnt) {1940throw new CommunicationException(1941"Context's connection changed; unable to continue enumeration");1942}19431944try {1945return eClnt.getSearchReply(batchSize, res, binaryAttrs);1946} catch (IOException e) {1947NamingException e2 = new CommunicationException(e.getMessage());1948e2.setRootCause(e);1949throw e2;1950}1951}19521953// Perform a search. Expect 1 SearchResultEntry and the SearchResultDone.1954private LdapResult doSearchOnce(Name name, String filter,1955SearchControls cons, boolean relative) throws NamingException {19561957int savedBatchSize = batchSize;1958batchSize = 2; // 2 protocol elements19591960LdapResult answer = doSearch(name, filter, cons, relative, true);19611962batchSize = savedBatchSize;1963return answer;1964}19651966private LdapResult doSearch(Name name, String filter, SearchControls cons,1967boolean relative, boolean waitForReply) throws NamingException {1968ensureOpen();1969try {1970int scope;19711972switch (cons.getSearchScope()) {1973case SearchControls.OBJECT_SCOPE:1974scope = LdapClient.SCOPE_BASE_OBJECT;1975break;1976default:1977case SearchControls.ONELEVEL_SCOPE:1978scope = LdapClient.SCOPE_ONE_LEVEL;1979break;1980case SearchControls.SUBTREE_SCOPE:1981scope = LdapClient.SCOPE_SUBTREE;1982break;1983}19841985// If cons.getReturningObjFlag() then caller should already1986// have make sure to request the appropriate attrs19871988String[] retattrs = cons.getReturningAttributes();1989if (retattrs != null && retattrs.length == 0) {1990// Ldap treats null and empty array the same1991// need to replace with single element array1992retattrs = new String[1];1993retattrs[0] = "1.1";1994}19951996String nm = (relative1997? fullyQualifiedName(name)1998: (name.isEmpty()1999? ""2000: name.get(0)));20012002// JNDI unit is milliseconds, LDAP unit is seconds.2003// Zero means no limit.2004int msecLimit = cons.getTimeLimit();2005int secLimit = 0;20062007if (msecLimit > 0) {2008secLimit = (msecLimit / 1000) + 1;2009}20102011LdapResult answer =2012clnt.search(nm,2013scope,2014derefAliases,2015(int)cons.getCountLimit(),2016secLimit,2017cons.getReturningObjFlag() ? false : typesOnly,2018retattrs,2019filter,2020batchSize,2021reqCtls,2022binaryAttrs,2023waitForReply,2024replyQueueSize);2025respCtls = answer.resControls; // retrieve response controls2026return answer;20272028} catch (IOException e) {2029NamingException e2 = new CommunicationException(e.getMessage());2030e2.setRootCause(e);2031throw e2;2032}2033}203420352036/*2037* Certain simple JNDI searches are automatically converted to2038* LDAP compare operations by the LDAP service provider. A search2039* is converted to a compare iff:2040*2041* - the scope is set to OBJECT_SCOPE2042* - the filter string contains a simple assertion: "<type>=<value>"2043* - the returning attributes list is present but empty2044*/20452046// returns true if a search can be caried out as a compare, and sets2047// tokens[0] and tokens[1] to the type and value respectively.2048// e.g. filter "cn=Jon Ruiz" becomes, type "cn" and value "Jon Ruiz"2049// This function uses the documents JNDI Compare example as a model2050// for when to turn a search into a compare.20512052private static boolean searchToCompare(2053String filter,2054SearchControls cons,2055String tokens[]) {20562057// if scope is not object-scope, it's really a search2058if (cons.getSearchScope() != SearchControls.OBJECT_SCOPE) {2059return false;2060}20612062// if attributes are to be returned, it's really a search2063String[] attrs = cons.getReturningAttributes();2064if (attrs == null || attrs.length != 0) {2065return false;2066}20672068// if the filter not a simple assertion, it's really a search2069if (! filterToAssertion(filter, tokens)) {2070return false;2071}20722073// it can be converted to a compare2074return true;2075}20762077// If the supplied filter is a simple assertion i.e. "<type>=<value>"2078// (enclosing parentheses are permitted) then2079// filterToAssertion will return true and pass the type and value as2080// the first and second elements of tokens respectively.2081// precondition: tokens[] must be initialized and be at least of size 2.20822083private static boolean filterToAssertion(String filter, String tokens[]) {20842085// find the left and right half of the assertion2086StringTokenizer assertionTokenizer = new StringTokenizer(filter, "=");20872088if (assertionTokenizer.countTokens() != 2) {2089return false;2090}20912092tokens[0] = assertionTokenizer.nextToken();2093tokens[1] = assertionTokenizer.nextToken();20942095// make sure the value does not contain a wildcard2096if (tokens[1].indexOf('*') != -1) {2097return false;2098}20992100// test for enclosing parenthesis2101boolean hasParens = false;2102int len = tokens[1].length();21032104if ((tokens[0].charAt(0) == '(') &&2105(tokens[1].charAt(len - 1) == ')')) {2106hasParens = true;21072108} else if ((tokens[0].charAt(0) == '(') ||2109(tokens[1].charAt(len - 1) == ')')) {2110return false; // unbalanced2111}21122113// make sure the left and right half are not expresions themselves2114StringTokenizer illegalCharsTokenizer =2115new StringTokenizer(tokens[0], "()&|!=~><*", true);21162117if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {2118return false;2119}21202121illegalCharsTokenizer =2122new StringTokenizer(tokens[1], "()&|!=~><*", true);21232124if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {2125return false;2126}21272128// strip off enclosing parenthesis, if present2129if (hasParens) {2130tokens[0] = tokens[0].substring(1);2131tokens[1] = tokens[1].substring(0, len - 1);2132}21332134return true;2135}21362137private LdapResult compare(Name name, String type, String value)2138throws IOException, NamingException {21392140ensureOpen();2141String nm = fullyQualifiedName(name);21422143LdapResult answer = clnt.compare(nm, type, value, reqCtls);2144respCtls = answer.resControls; // retrieve response controls21452146return answer;2147}21482149private static SearchControls cloneSearchControls(SearchControls cons) {2150if (cons == null) {2151return null;2152}2153String[] retAttrs = cons.getReturningAttributes();2154if (retAttrs != null) {2155String[] attrs = new String[retAttrs.length];2156System.arraycopy(retAttrs, 0, attrs, 0, retAttrs.length);2157retAttrs = attrs;2158}2159return new SearchControls(cons.getSearchScope(),2160cons.getCountLimit(),2161cons.getTimeLimit(),2162retAttrs,2163cons.getReturningObjFlag(),2164cons.getDerefLinkFlag());2165}21662167// -------------- Environment Properties ------------------21682169/**2170* Override with noncloning version.2171*/2172protected Hashtable<String, Object> p_getEnvironment() {2173return envprops;2174}21752176@SuppressWarnings("unchecked") // clone()2177public Hashtable<String, Object> getEnvironment() throws NamingException {2178return (envprops == null2179? new Hashtable<String, Object>(5, 0.75f)2180: (Hashtable<String, Object>)envprops.clone());2181}21822183@SuppressWarnings("unchecked") // clone()2184public Object removeFromEnvironment(String propName)2185throws NamingException {21862187// not there; just return2188if (envprops == null || envprops.get(propName) == null) {2189return null;2190}2191switch (propName) {2192case REF_SEPARATOR:2193addrEncodingSeparator = DEFAULT_REF_SEPARATOR;2194break;2195case TYPES_ONLY:2196typesOnly = DEFAULT_TYPES_ONLY;2197break;2198case DELETE_RDN:2199deleteRDN = DEFAULT_DELETE_RDN;2200break;2201case DEREF_ALIASES:2202derefAliases = DEFAULT_DEREF_ALIASES;2203break;2204case Context.BATCHSIZE:2205batchSize = DEFAULT_BATCH_SIZE;2206break;2207case REFERRAL_LIMIT:2208referralHopLimit = DEFAULT_REFERRAL_LIMIT;2209break;2210case Context.REFERRAL:2211setReferralMode(null, true);2212break;2213case BINARY_ATTRIBUTES:2214setBinaryAttributes(null);2215break;2216case CONNECT_TIMEOUT:2217connectTimeout = -1;2218break;2219case READ_TIMEOUT:2220readTimeout = -1;2221break;2222case WAIT_FOR_REPLY:2223waitForReply = true;2224break;2225case REPLY_QUEUE_SIZE:2226replyQueueSize = -1;2227break;22282229// The following properties affect the connection22302231case Context.SECURITY_PROTOCOL:2232closeConnection(SOFT_CLOSE);2233// De-activate SSL and reset the context's url and port number2234if (useSsl && !hasLdapsScheme) {2235useSsl = false;2236url = null;2237if (useDefaultPortNumber) {2238port_number = DEFAULT_PORT;2239}2240}2241break;2242case VERSION:2243case SOCKET_FACTORY:2244closeConnection(SOFT_CLOSE);2245break;2246case Context.SECURITY_AUTHENTICATION:2247case Context.SECURITY_PRINCIPAL:2248case Context.SECURITY_CREDENTIALS:2249sharable = false;2250break;2251}22522253// Update environment; reconnection will use new props2254envprops = (Hashtable<String, Object>)envprops.clone();2255return envprops.remove(propName);2256}22572258@SuppressWarnings("unchecked") // clone()2259public Object addToEnvironment(String propName, Object propVal)2260throws NamingException {22612262// If adding null, call remove2263if (propVal == null) {2264return removeFromEnvironment(propName);2265}2266switch (propName) {2267case REF_SEPARATOR:2268setRefSeparator((String)propVal);2269break;2270case TYPES_ONLY:2271setTypesOnly((String)propVal);2272break;2273case DELETE_RDN:2274setDeleteRDN((String)propVal);2275break;2276case DEREF_ALIASES:2277setDerefAliases((String)propVal);2278break;2279case Context.BATCHSIZE:2280setBatchSize((String)propVal);2281break;2282case REFERRAL_LIMIT:2283setReferralLimit((String)propVal);2284break;2285case Context.REFERRAL:2286setReferralMode((String)propVal, true);2287break;2288case BINARY_ATTRIBUTES:2289setBinaryAttributes((String)propVal);2290break;2291case CONNECT_TIMEOUT:2292setConnectTimeout((String)propVal);2293break;2294case READ_TIMEOUT:2295setReadTimeout((String)propVal);2296break;2297case WAIT_FOR_REPLY:2298setWaitForReply((String)propVal);2299break;2300case REPLY_QUEUE_SIZE:2301setReplyQueueSize((String)propVal);2302break;23032304// The following properties affect the connection23052306case Context.SECURITY_PROTOCOL:2307closeConnection(SOFT_CLOSE);2308// Activate SSL and reset the context's url and port number2309if ("ssl".equals(propVal)) {2310useSsl = true;2311url = null;2312if (useDefaultPortNumber) {2313port_number = DEFAULT_SSL_PORT;2314}2315}2316break;2317case VERSION:2318case SOCKET_FACTORY:2319closeConnection(SOFT_CLOSE);2320break;2321case Context.SECURITY_AUTHENTICATION:2322case Context.SECURITY_PRINCIPAL:2323case Context.SECURITY_CREDENTIALS:2324sharable = false;2325break;2326}23272328// Update environment; reconnection will use new props2329envprops = (envprops == null2330? new Hashtable<String, Object>(5, 0.75f)2331: (Hashtable<String, Object>)envprops.clone());2332return envprops.put(propName, propVal);2333}23342335/**2336* Sets the URL that created the context in the java.naming.provider.url2337* property.2338*/2339void setProviderUrl(String providerUrl) { // called by LdapCtxFactory2340if (envprops != null) {2341envprops.put(Context.PROVIDER_URL, providerUrl);2342}2343}23442345/**2346* Sets the domain name for the context in the com.sun.jndi.ldap.domainname2347* property.2348* Used for hostname verification by Start TLS2349*/2350void setDomainName(String domainName) { // called by LdapCtxFactory2351if (envprops != null) {2352envprops.put(DOMAIN_NAME, domainName);2353}2354}23552356private void initEnv() throws NamingException {2357if (envprops == null) {2358// Make sure that referrals are to their default2359setReferralMode(null, false);2360return;2361}23622363// Set batch size2364setBatchSize((String)envprops.get(Context.BATCHSIZE));23652366// Set separator used for encoding RefAddr2367setRefSeparator((String)envprops.get(REF_SEPARATOR));23682369// Set whether RDN is removed when renaming object2370setDeleteRDN((String)envprops.get(DELETE_RDN));23712372// Set whether types are returned only2373setTypesOnly((String)envprops.get(TYPES_ONLY));23742375// Set how aliases are dereferenced2376setDerefAliases((String)envprops.get(DEREF_ALIASES));23772378// Set the limit on referral chains2379setReferralLimit((String)envprops.get(REFERRAL_LIMIT));23802381setBinaryAttributes((String)envprops.get(BINARY_ATTRIBUTES));23822383bindCtls = cloneControls((Control[]) envprops.get(BIND_CONTROLS));23842385// set referral handling2386setReferralMode((String)envprops.get(Context.REFERRAL), false);23872388// Set the connect timeout2389setConnectTimeout((String)envprops.get(CONNECT_TIMEOUT));23902391// Set the read timeout2392setReadTimeout((String)envprops.get(READ_TIMEOUT));23932394// Set the flag that controls whether to block until the first reply2395// is received2396setWaitForReply((String)envprops.get(WAIT_FOR_REPLY));23972398// Set the size of the queue of unprocessed search replies2399setReplyQueueSize((String)envprops.get(REPLY_QUEUE_SIZE));24002401// When connection is created, it will use these and other2402// properties from the environment2403}24042405private void setDeleteRDN(String deleteRDNProp) {2406if ((deleteRDNProp != null) &&2407(deleteRDNProp.equalsIgnoreCase("false"))) {2408deleteRDN = false;2409} else {2410deleteRDN = DEFAULT_DELETE_RDN;2411}2412}24132414private void setTypesOnly(String typesOnlyProp) {2415if ((typesOnlyProp != null) &&2416(typesOnlyProp.equalsIgnoreCase("true"))) {2417typesOnly = true;2418} else {2419typesOnly = DEFAULT_TYPES_ONLY;2420}2421}24222423/**2424* Sets the batch size of this context;2425*/2426private void setBatchSize(String batchSizeProp) {2427// set batchsize2428if (batchSizeProp != null) {2429batchSize = Integer.parseInt(batchSizeProp);2430} else {2431batchSize = DEFAULT_BATCH_SIZE;2432}2433}24342435/**2436* Sets the referral mode of this context to 'follow', 'throw' or 'ignore'.2437* If referral mode is 'ignore' then activate the manageReferral control.2438*/2439private void setReferralMode(String ref, boolean update) {2440// First determine the referral mode2441if (ref != null) {2442switch (ref) {2443case "follow-scheme":2444handleReferrals = LdapClient.LDAP_REF_FOLLOW_SCHEME;2445break;2446case "follow":2447handleReferrals = LdapClient.LDAP_REF_FOLLOW;2448break;2449case "throw":2450handleReferrals = LdapClient.LDAP_REF_THROW;2451break;2452case "ignore":2453handleReferrals = LdapClient.LDAP_REF_IGNORE;2454break;2455default:2456throw new IllegalArgumentException(2457"Illegal value for " + Context.REFERRAL + " property.");2458}2459} else {2460handleReferrals = DEFAULT_REFERRAL_MODE;2461}24622463if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {2464// If ignoring referrals, add manageReferralControl2465reqCtls = addControl(reqCtls, manageReferralControl);24662467} else if (update) {24682469// If we're update an existing context, remove the control2470reqCtls = removeControl(reqCtls, manageReferralControl);24712472} // else, leave alone; need not update2473}24742475/**2476* Set whether aliases are derefereced during resolution and searches.2477*/2478private void setDerefAliases(String deref) {2479if (deref != null) {2480switch (deref) {2481case "never":2482derefAliases = 0; // never de-reference aliases2483break;2484case "searching":2485derefAliases = 1; // de-reference aliases during searching2486break;2487case "finding":2488derefAliases = 2; // de-reference during name resolution2489break;2490case "always":2491derefAliases = 3; // always de-reference aliases2492break;2493default:2494throw new IllegalArgumentException("Illegal value for " +2495DEREF_ALIASES + " property.");2496}2497} else {2498derefAliases = DEFAULT_DEREF_ALIASES;2499}2500}25012502private void setRefSeparator(String sepStr) throws NamingException {2503if (sepStr != null && sepStr.length() > 0) {2504addrEncodingSeparator = sepStr.charAt(0);2505} else {2506addrEncodingSeparator = DEFAULT_REF_SEPARATOR;2507}2508}25092510/**2511* Sets the limit on referral chains2512*/2513private void setReferralLimit(String referralLimitProp) {2514// set referral limit2515if (referralLimitProp != null) {2516referralHopLimit = Integer.parseInt(referralLimitProp);25172518// a zero setting indicates no limit2519if (referralHopLimit == 0)2520referralHopLimit = Integer.MAX_VALUE;2521} else {2522referralHopLimit = DEFAULT_REFERRAL_LIMIT;2523}2524}25252526// For counting referral hops2527void setHopCount(int hopCount) {2528this.hopCount = hopCount;2529}25302531/**2532* Sets the connect timeout value2533*/2534private void setConnectTimeout(String connectTimeoutProp) {2535if (connectTimeoutProp != null) {2536connectTimeout = Integer.parseInt(connectTimeoutProp);2537} else {2538connectTimeout = -1;2539}2540}25412542/**2543* Sets the size of the queue of unprocessed search replies2544*/2545private void setReplyQueueSize(String replyQueueSizeProp) {2546if (replyQueueSizeProp != null) {2547replyQueueSize = Integer.parseInt(replyQueueSizeProp);2548// disallow an empty queue2549if (replyQueueSize <= 0) {2550replyQueueSize = -1; // unlimited2551}2552} else {2553replyQueueSize = -1; // unlimited2554}2555}25562557/**2558* Sets the flag that controls whether to block until the first search2559* reply is received2560*/2561private void setWaitForReply(String waitForReplyProp) {2562if (waitForReplyProp != null &&2563(waitForReplyProp.equalsIgnoreCase("false"))) {2564waitForReply = false;2565} else {2566waitForReply = true;2567}2568}25692570/**2571* Sets the read timeout value2572*/2573private void setReadTimeout(String readTimeoutProp) {2574if (readTimeoutProp != null) {2575readTimeout = Integer.parseInt(readTimeoutProp);2576} else {2577readTimeout = -1;2578}2579}25802581/*2582* Extract URLs from a string. The format of the string is:2583*2584* <urlstring > ::= "Referral:" <ldapurls>2585* <ldapurls> ::= <separator> <ldapurl> | <ldapurls>2586* <separator> ::= ASCII linefeed character (0x0a)2587* <ldapurl> ::= LDAP URL format (RFC 1959)2588*2589* Returns a Vector of single-String Vectors.2590*/2591private static Vector<Vector<String>> extractURLs(String refString) {25922593int separator = 0;2594int urlCount = 0;25952596// count the number of URLs2597while ((separator = refString.indexOf('\n', separator)) >= 0) {2598separator++;2599urlCount++;2600}26012602Vector<Vector<String>> referrals = new Vector<>(urlCount);2603int iURL;2604int i = 0;26052606separator = refString.indexOf('\n');2607iURL = separator + 1;2608while ((separator = refString.indexOf('\n', iURL)) >= 0) {2609Vector<String> referral = new Vector<>(1);2610referral.addElement(refString.substring(iURL, separator));2611referrals.addElement(referral);2612iURL = separator + 1;2613}2614Vector<String> referral = new Vector<>(1);2615referral.addElement(refString.substring(iURL));2616referrals.addElement(referral);26172618return referrals;2619}26202621/*2622* Argument is a space-separated list of attribute IDs2623* Converts attribute IDs to lowercase before adding to built-in list.2624*/2625private void setBinaryAttributes(String attrIds) {2626if (attrIds == null) {2627binaryAttrs = null;2628} else {2629binaryAttrs = new Hashtable<>(11, 0.75f);2630StringTokenizer tokens =2631new StringTokenizer(attrIds.toLowerCase(Locale.ENGLISH), " ");26322633while (tokens.hasMoreTokens()) {2634binaryAttrs.put(tokens.nextToken(), Boolean.TRUE);2635}2636}2637}26382639// ----------------- Connection ---------------------26402641protected void finalize() {2642try {2643close();2644} catch (NamingException e) {2645// ignore failures2646}2647}26482649synchronized public void close() throws NamingException {2650if (debug) {2651System.err.println("LdapCtx: close() called " + this);2652(new Throwable()).printStackTrace();2653}26542655// Event (normal and unsolicited)2656if (eventSupport != null) {2657eventSupport.cleanup(); // idempotent2658removeUnsolicited();2659}26602661// Enumerations that are keeping the connection alive2662if (enumCount > 0) {2663if (debug)2664System.err.println("LdapCtx: close deferred");2665closeRequested = true;2666return;2667}2668closeConnection(SOFT_CLOSE);26692670// %%%: RL: There is no need to set these to null, as they're just2671// variables whose contents and references will automatically2672// be cleaned up when they're no longer referenced.2673// Also, setting these to null creates problems for the attribute2674// schema-related methods, which need these to work.2675/*2676schemaTrees = null;2677envprops = null;2678*/2679}26802681@SuppressWarnings("unchecked") // clone()2682public void reconnect(Control[] connCtls) throws NamingException {2683// Update environment2684envprops = (envprops == null2685? new Hashtable<String, Object>(5, 0.75f)2686: (Hashtable<String, Object>)envprops.clone());26872688if (connCtls == null) {2689envprops.remove(BIND_CONTROLS);2690bindCtls = null;2691} else {2692envprops.put(BIND_CONTROLS, bindCtls = cloneControls(connCtls));2693}26942695sharable = false; // can't share with existing contexts2696ensureOpen(); // open or reauthenticated2697}26982699// Load 'mechsAllowedToSendCredentials' system property value2700private static String getMechsAllowedToSendCredentials() {2701PrivilegedAction<String> pa = () -> System.getProperty(ALLOWED_MECHS_SP);2702return System.getSecurityManager() == null ? pa.run() : AccessController.doPrivileged(pa);2703}27042705// Get set of allowed authentication mechanism names from the property value2706private static Set<String> getMechsFromPropertyValue(String propValue) {2707if (propValue == null || propValue.isEmpty()) {2708return Collections.emptySet();2709}27102711Set<String> s = new HashSet<>();2712for (String part : propValue.trim().split("\\s*,\\s*")) {2713if (!part.isEmpty()) {2714s.add(part);2715}2716}2717return Collections.unmodifiableSet(s);2718}27192720// Returns true if TLS connection opened using "ldaps" scheme, or using "ldap" and then upgraded with2721// startTLS extended operation, and startTLS is still active.2722private boolean isConnectionEncrypted() {2723return hasLdapsScheme || clnt.isUpgradedToStartTls();2724}27252726// Ensure connection and context are in a safe state to transmit credentials2727private void ensureCanTransmitCredentials(String authMechanism) throws NamingException {27282729// "none" and "anonumous" authentication mechanisms are allowed unconditionally2730if ("none".equalsIgnoreCase(authMechanism) || "anonymous".equalsIgnoreCase(authMechanism)) {2731return;2732}27332734// Check environment first2735String allowedMechanismsOrTrue = (String) envprops.get(ALLOWED_MECHS_SP);2736boolean useSpMechsCache = false;2737boolean anyPropertyIsSet = ALLOWED_MECHS_SP_VALUE != null || allowedMechanismsOrTrue != null;27382739// If current connection is not encrypted, and context seen to be secured with STARTTLS2740// or 'mechsAllowedToSendCredentials' is set to any value via system/context environment properties2741if (!isConnectionEncrypted() && (contextSeenStartTlsEnabled || anyPropertyIsSet)) {2742// First, check if security principal is provided in context environment for "simple"2743// authentication mechanism. There is no check for other SASL mechanisms since the credentials2744// can be specified via other properties2745if ("simple".equalsIgnoreCase(authMechanism) && !envprops.containsKey(SECURITY_PRINCIPAL)) {2746return;2747}27482749// If null - will use mechanism name cached from system property2750if (allowedMechanismsOrTrue == null) {2751useSpMechsCache = true;2752allowedMechanismsOrTrue = ALLOWED_MECHS_SP_VALUE;2753}27542755// If the property value (system or environment) is 'all':2756// any kind of authentication is allowed unconditionally - no check is needed2757if ("all".equalsIgnoreCase(allowedMechanismsOrTrue)) {2758return;2759}27602761// Get the set with allowed authentication mechanisms and check current mechanism2762Set<String> allowedAuthMechs = useSpMechsCache ?2763MECHS_ALLOWED_BY_SP : getMechsFromPropertyValue(allowedMechanismsOrTrue);2764if (!allowedAuthMechs.contains(authMechanism)) {2765throw new NamingException(UNSECURED_CRED_TRANSMIT_MSG);2766}2767}2768}27692770private void ensureOpen() throws NamingException {2771ensureOpen(false);2772}27732774private void ensureOpen(boolean startTLS) throws NamingException {27752776try {2777if (clnt == null) {2778if (debug) {2779System.err.println("LdapCtx: Reconnecting " + this);2780}27812782// reset the cache before a new connection is established2783schemaTrees = new Hashtable<>(11, 0.75f);2784connect(startTLS);27852786} else if (!sharable || startTLS) {27872788synchronized (clnt) {2789if (!clnt.isLdapv32790|| clnt.referenceCount > 12791|| clnt.usingSaslStreams()2792|| !clnt.conn.useable) {2793closeConnection(SOFT_CLOSE);2794}2795}2796// reset the cache before a new connection is established2797schemaTrees = new Hashtable<>(11, 0.75f);2798connect(startTLS);2799}28002801} finally {2802sharable = true; // connection is now either new or single-use2803// OK for others to start sharing again2804}2805}28062807private void connect(boolean startTLS) throws NamingException {2808if (debug) { System.err.println("LdapCtx: Connecting " + this); }28092810String user = null; // authenticating user2811Object passwd = null; // password for authenticating user2812String secProtocol = null; // security protocol (e.g. "ssl")2813String socketFactory = null; // socket factory2814String authMechanism = null; // authentication mechanism2815String ver = null;2816int ldapVersion; // LDAP protocol version2817boolean usePool = false; // enable connection pooling28182819if (envprops != null) {2820user = (String)envprops.get(Context.SECURITY_PRINCIPAL);2821passwd = envprops.get(Context.SECURITY_CREDENTIALS);2822ver = (String)envprops.get(VERSION);2823secProtocol =2824useSsl ? "ssl" : (String)envprops.get(Context.SECURITY_PROTOCOL);2825socketFactory = (String)envprops.get(SOCKET_FACTORY);2826authMechanism =2827(String)envprops.get(Context.SECURITY_AUTHENTICATION);28282829usePool = "true".equalsIgnoreCase((String)envprops.get(ENABLE_POOL));2830}28312832if (socketFactory == null) {2833socketFactory =2834"ssl".equals(secProtocol) ? DEFAULT_SSL_FACTORY : null;2835}28362837if (authMechanism == null) {2838authMechanism = (user == null) ? "none" : "simple";2839}28402841try {2842boolean initial = (clnt == null);28432844if (initial) {2845ldapVersion = (ver != null) ? Integer.parseInt(ver) :2846DEFAULT_LDAP_VERSION;28472848clnt = LdapClient.getInstance(2849usePool, // Whether to use connection pooling28502851// Required for LdapClient constructor2852hostname,2853port_number,2854socketFactory,2855connectTimeout,2856readTimeout,2857trace,28582859// Required for basic client identity2860ldapVersion,2861authMechanism,2862bindCtls,2863secProtocol,28642865// Required for simple client identity2866user,2867passwd,28682869// Required for SASL client identity2870envprops);28712872// Mark current context as secure if the connection is acquired2873// from the pool and it is secure.2874contextSeenStartTlsEnabled |= clnt.isUpgradedToStartTls();28752876/**2877* Pooled connections are preauthenticated;2878* newly created ones are not.2879*/2880if (clnt.authenticateCalled()) {2881return;2882}28832884} else if (sharable && startTLS) {2885return; // no authentication required28862887} else {2888// reauthenticating over existing connection;2889// only v3 supports this2890ldapVersion = LdapClient.LDAP_VERSION3;2891}28922893LdapResult answer;2894synchronized (clnt.conn.startTlsLock) {2895ensureCanTransmitCredentials(authMechanism);2896answer = clnt.authenticate(initial, user, passwd, ldapVersion,2897authMechanism, bindCtls, envprops);2898}28992900respCtls = answer.resControls; // retrieve (bind) response controls29012902if (answer.status != LdapClient.LDAP_SUCCESS) {2903if (initial) {2904closeConnection(HARD_CLOSE); // hard close2905}2906processReturnCode(answer);2907}29082909} catch (LdapReferralException e) {2910if (handleReferrals == LdapClient.LDAP_REF_THROW)2911throw e;29122913String referral;2914LdapURL url;2915NamingException saved_ex = null;29162917// Process the referrals sequentially (top level) and2918// recursively (per referral)2919while (true) {29202921if ((referral = e.getNextReferral()) == null) {2922// No more referrals to follow29232924if (saved_ex != null) {2925throw (NamingException)(saved_ex.fillInStackTrace());2926} else {2927// No saved exception, something must have gone wrong2928throw new NamingException(2929"Internal error processing referral during connection");2930}2931}29322933// Use host/port number from referral2934url = new LdapURL(referral);2935hostname = url.getHost();2936if ((hostname != null) && (hostname.charAt(0) == '[')) {2937hostname = hostname.substring(1, hostname.length() - 1);2938}2939port_number = url.getPort();29402941// Try to connect again using new host/port number2942try {2943connect(startTLS);2944break;29452946} catch (NamingException ne) {2947saved_ex = ne;2948continue; // follow another referral2949}2950}2951}2952}29532954private void closeConnection(boolean hardclose) {2955removeUnsolicited(); // idempotent29562957if (clnt != null) {2958if (debug) {2959System.err.println("LdapCtx: calling clnt.close() " + this);2960}2961clnt.close(reqCtls, hardclose);2962clnt = null;2963}2964}29652966// Used by Enum classes to track whether it still needs context2967private int enumCount = 0;2968private boolean closeRequested = false;29692970synchronized void incEnumCount() {2971++enumCount;2972if (debug) System.err.println("LdapCtx: " + this + " enum inc: " + enumCount);2973}29742975synchronized void decEnumCount() {2976--enumCount;2977if (debug) System.err.println("LdapCtx: " + this + " enum dec: " + enumCount);29782979if (enumCount == 0 && closeRequested) {2980try {2981close();2982} catch (NamingException e) {2983// ignore failures2984}2985}2986}298729882989// ------------ Return code and Error messages -----------------------29902991protected void processReturnCode(LdapResult answer) throws NamingException {2992processReturnCode(answer, null, this, null, envprops, null);2993}29942995void processReturnCode(LdapResult answer, Name remainName)2996throws NamingException {2997processReturnCode(answer,2998(new CompositeName()).add(currentDN),2999this,3000remainName,3001envprops,3002fullyQualifiedName(remainName));3003}30043005protected void processReturnCode(LdapResult res, Name resolvedName,3006Object resolvedObj, Name remainName, Hashtable<?,?> envprops, String fullDN)3007throws NamingException {30083009String msg = LdapClient.getErrorMessage(res.status, res.errorMessage);3010NamingException e;3011LdapReferralException r = null;30123013switch (res.status) {30143015case LdapClient.LDAP_SUCCESS:30163017// handle Search continuation references3018if (res.referrals != null) {30193020msg = "Unprocessed Continuation Reference(s)";30213022if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {3023e = new PartialResultException(msg);3024break;3025}30263027// handle multiple sets of URLs3028int contRefCount = res.referrals.size();3029LdapReferralException head = null;3030LdapReferralException ptr = null;30313032msg = "Continuation Reference";30333034// make a chain of LdapReferralExceptions3035for (int i = 0; i < contRefCount; i++) {30363037r = new LdapReferralException(resolvedName, resolvedObj,3038remainName, msg, envprops, fullDN, handleReferrals,3039reqCtls);3040r.setReferralInfo(res.referrals.elementAt(i), true);30413042if (hopCount > 1) {3043r.setHopCount(hopCount);3044}30453046if (head == null) {3047head = ptr = r;3048} else {3049ptr.nextReferralEx = r; // append ex. to end of chain3050ptr = r;3051}3052}3053res.referrals = null; // reset30543055if (res.refEx == null) {3056res.refEx = head;30573058} else {3059ptr = res.refEx;30603061while (ptr.nextReferralEx != null) {3062ptr = ptr.nextReferralEx;3063}3064ptr.nextReferralEx = head;3065}30663067// check the hop limit3068if (hopCount > referralHopLimit) {3069NamingException lee =3070new LimitExceededException("Referral limit exceeded");3071lee.setRootCause(r);3072throw lee;3073}3074}3075return;30763077case LdapClient.LDAP_REFERRAL:30783079if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {3080e = new PartialResultException(msg);3081break;3082}30833084r = new LdapReferralException(resolvedName, resolvedObj, remainName,3085msg, envprops, fullDN, handleReferrals, reqCtls);3086// only one set of URLs is present3087Vector<String> refs;3088if (res.referrals == null) {3089refs = null;3090} else if (handleReferrals == LdapClient.LDAP_REF_FOLLOW_SCHEME) {3091refs = new Vector<>();3092for (String s : res.referrals.elementAt(0)) {3093if (s.startsWith("ldap:")) {3094refs.add(s);3095}3096}3097if (refs.isEmpty()) {3098refs = null;3099}3100} else {3101refs = res.referrals.elementAt(0);3102}3103r.setReferralInfo(refs, false);31043105if (hopCount > 1) {3106r.setHopCount(hopCount);3107}31083109// check the hop limit3110if (hopCount > referralHopLimit) {3111NamingException lee =3112new LimitExceededException("Referral limit exceeded");3113lee.setRootCause(r);3114e = lee;31153116} else {3117e = r;3118}3119break;31203121/*3122* Handle SLAPD-style referrals.3123*3124* Referrals received during name resolution should be followed3125* until one succeeds - the target entry is located. An exception3126* is thrown now to handle these.3127*3128* Referrals received during a search operation point to unexplored3129* parts of the directory and each should be followed. An exception3130* is thrown later (during results enumeration) to handle these.3131*/31323133case LdapClient.LDAP_PARTIAL_RESULTS:31343135if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {3136e = new PartialResultException(msg);3137break;3138}31393140// extract SLAPD-style referrals from errorMessage3141if ((res.errorMessage != null) && (!res.errorMessage.equals(""))) {3142res.referrals = extractURLs(res.errorMessage);3143} else {3144e = new PartialResultException(msg);3145break;3146}31473148// build exception3149r = new LdapReferralException(resolvedName,3150resolvedObj,3151remainName,3152msg,3153envprops,3154fullDN,3155handleReferrals,3156reqCtls);31573158if (hopCount > 1) {3159r.setHopCount(hopCount);3160}3161/*3162* %%%3163* SLAPD-style referrals received during name resolution3164* cannot be distinguished from those received during a3165* search operation. Since both must be handled differently3166* the following rule is applied:3167*3168* If 1 referral and 0 entries is received then3169* assume name resolution has not yet completed.3170*/3171if (((res.entries == null) || (res.entries.isEmpty())) &&3172((res.referrals != null) && (res.referrals.size() == 1))) {31733174r.setReferralInfo(res.referrals, false);31753176// check the hop limit3177if (hopCount > referralHopLimit) {3178NamingException lee =3179new LimitExceededException("Referral limit exceeded");3180lee.setRootCause(r);3181e = lee;31823183} else {3184e = r;3185}31863187} else {3188r.setReferralInfo(res.referrals, true);3189res.refEx = r;3190return;3191}3192break;31933194case LdapClient.LDAP_INVALID_DN_SYNTAX:3195case LdapClient.LDAP_NAMING_VIOLATION:31963197if (remainName != null) {3198e = new3199InvalidNameException(remainName.toString() + ": " + msg);3200} else {3201e = new InvalidNameException(msg);3202}3203break;32043205default:3206e = mapErrorCode(res.status, res.errorMessage);3207break;3208}3209e.setResolvedName(resolvedName);3210e.setResolvedObj(resolvedObj);3211e.setRemainingName(remainName);3212throw e;3213}32143215/**3216* Maps an LDAP error code to an appropriate NamingException.3217* %%% public; used by controls3218*3219* @param errorCode numeric LDAP error code3220* @param errorMessage textual description of the LDAP error. May be null.3221*3222* @return A NamingException or null if the error code indicates success.3223*/3224public static NamingException mapErrorCode(int errorCode,3225String errorMessage) {32263227if (errorCode == LdapClient.LDAP_SUCCESS)3228return null;32293230NamingException e = null;3231String message = LdapClient.getErrorMessage(errorCode, errorMessage);32323233switch (errorCode) {32343235case LdapClient.LDAP_ALIAS_DEREFERENCING_PROBLEM:3236e = new NamingException(message);3237break;32383239case LdapClient.LDAP_ALIAS_PROBLEM:3240e = new NamingException(message);3241break;32423243case LdapClient.LDAP_ATTRIBUTE_OR_VALUE_EXISTS:3244e = new AttributeInUseException(message);3245break;32463247case LdapClient.LDAP_AUTH_METHOD_NOT_SUPPORTED:3248case LdapClient.LDAP_CONFIDENTIALITY_REQUIRED:3249case LdapClient.LDAP_STRONG_AUTH_REQUIRED:3250case LdapClient.LDAP_INAPPROPRIATE_AUTHENTICATION:3251e = new AuthenticationNotSupportedException(message);3252break;32533254case LdapClient.LDAP_ENTRY_ALREADY_EXISTS:3255e = new NameAlreadyBoundException(message);3256break;32573258case LdapClient.LDAP_INVALID_CREDENTIALS:3259case LdapClient.LDAP_SASL_BIND_IN_PROGRESS:3260e = new AuthenticationException(message);3261break;32623263case LdapClient.LDAP_INAPPROPRIATE_MATCHING:3264e = new InvalidSearchFilterException(message);3265break;32663267case LdapClient.LDAP_INSUFFICIENT_ACCESS_RIGHTS:3268e = new NoPermissionException(message);3269break;32703271case LdapClient.LDAP_INVALID_ATTRIBUTE_SYNTAX:3272case LdapClient.LDAP_CONSTRAINT_VIOLATION:3273e = new InvalidAttributeValueException(message);3274break;32753276case LdapClient.LDAP_LOOP_DETECT:3277e = new NamingException(message);3278break;32793280case LdapClient.LDAP_NO_SUCH_ATTRIBUTE:3281e = new NoSuchAttributeException(message);3282break;32833284case LdapClient.LDAP_NO_SUCH_OBJECT:3285e = new NameNotFoundException(message);3286break;32873288case LdapClient.LDAP_OBJECT_CLASS_MODS_PROHIBITED:3289case LdapClient.LDAP_OBJECT_CLASS_VIOLATION:3290case LdapClient.LDAP_NOT_ALLOWED_ON_RDN:3291e = new SchemaViolationException(message);3292break;32933294case LdapClient.LDAP_NOT_ALLOWED_ON_NON_LEAF:3295e = new ContextNotEmptyException(message);3296break;32973298case LdapClient.LDAP_OPERATIONS_ERROR:3299// %%% need new exception ?3300e = new NamingException(message);3301break;33023303case LdapClient.LDAP_OTHER:3304e = new NamingException(message);3305break;33063307case LdapClient.LDAP_PROTOCOL_ERROR:3308e = new CommunicationException(message);3309break;33103311case LdapClient.LDAP_SIZE_LIMIT_EXCEEDED:3312e = new SizeLimitExceededException(message);3313break;33143315case LdapClient.LDAP_TIME_LIMIT_EXCEEDED:3316e = new TimeLimitExceededException(message);3317break;33183319case LdapClient.LDAP_UNAVAILABLE_CRITICAL_EXTENSION:3320e = new OperationNotSupportedException(message);3321break;33223323case LdapClient.LDAP_UNAVAILABLE:3324case LdapClient.LDAP_BUSY:3325e = new ServiceUnavailableException(message);3326break;33273328case LdapClient.LDAP_UNDEFINED_ATTRIBUTE_TYPE:3329e = new InvalidAttributeIdentifierException(message);3330break;33313332case LdapClient.LDAP_UNWILLING_TO_PERFORM:3333e = new OperationNotSupportedException(message);3334break;33353336case LdapClient.LDAP_COMPARE_FALSE:3337case LdapClient.LDAP_COMPARE_TRUE:3338case LdapClient.LDAP_IS_LEAF:3339// these are really not exceptions and this code probably3340// never gets executed3341e = new NamingException(message);3342break;33433344case LdapClient.LDAP_ADMIN_LIMIT_EXCEEDED:3345e = new LimitExceededException(message);3346break;33473348case LdapClient.LDAP_REFERRAL:3349e = new NamingException(message);3350break;33513352case LdapClient.LDAP_PARTIAL_RESULTS:3353e = new NamingException(message);3354break;33553356case LdapClient.LDAP_INVALID_DN_SYNTAX:3357case LdapClient.LDAP_NAMING_VIOLATION:3358e = new InvalidNameException(message);3359break;33603361default:3362e = new NamingException(message);3363break;3364}33653366return e;3367}33683369// ----------------- Extensions and Controls -------------------33703371public ExtendedResponse extendedOperation(ExtendedRequest request)3372throws NamingException {33733374boolean startTLS = (request.getID().equals(STARTTLS_REQ_OID));3375ensureOpen(startTLS);33763377try {33783379LdapResult answer =3380clnt.extendedOp(request.getID(), request.getEncodedValue(),3381reqCtls, startTLS);3382respCtls = answer.resControls; // retrieve response controls33833384if (answer.status != LdapClient.LDAP_SUCCESS) {3385processReturnCode(answer, new CompositeName());3386}3387// %%% verify request.getID() == answer.extensionId33883389int len = (answer.extensionValue == null) ?33900 :3391answer.extensionValue.length;33923393ExtendedResponse er =3394request.createExtendedResponse(answer.extensionId,3395answer.extensionValue, 0, len);33963397if (er instanceof StartTlsResponseImpl) {3398// Pass the connection handle to StartTlsResponseImpl3399String domainName = (String)3400(envprops != null ? envprops.get(DOMAIN_NAME) : null);3401((StartTlsResponseImpl)er).setConnection(clnt.conn, domainName);3402contextSeenStartTlsEnabled |= startTLS;3403}3404return er;34053406} catch (LdapReferralException e) {34073408if (handleReferrals == LdapClient.LDAP_REF_THROW)3409throw e;34103411// process the referrals sequentially3412while (true) {34133414LdapReferralContext refCtx =3415(LdapReferralContext)e.getReferralContext(envprops, bindCtls);34163417// repeat the original operation at the new context3418try {34193420return refCtx.extendedOperation(request);34213422} catch (LdapReferralException re) {3423e = re;3424continue;34253426} finally {3427// Make sure we close referral context3428refCtx.close();3429}3430}34313432} catch (IOException e) {3433NamingException e2 = new CommunicationException(e.getMessage());3434e2.setRootCause(e);3435throw e2;3436}3437}34383439public void setRequestControls(Control[] reqCtls) throws NamingException {3440if (handleReferrals == LdapClient.LDAP_REF_IGNORE) {3441this.reqCtls = addControl(reqCtls, manageReferralControl);3442} else {3443this.reqCtls = cloneControls(reqCtls);3444}3445}34463447public Control[] getRequestControls() throws NamingException {3448return cloneControls(reqCtls);3449}34503451public Control[] getConnectControls() throws NamingException {3452return cloneControls(bindCtls);3453}34543455public Control[] getResponseControls() throws NamingException {3456return (respCtls != null)? convertControls(respCtls) : null;3457}34583459/**3460* Narrow controls using own default factory and ControlFactory.3461* @param ctls A non-null Vector<Control>3462*/3463Control[] convertControls(Vector<Control> ctls) throws NamingException {3464int count = ctls.size();34653466if (count == 0) {3467return null;3468}34693470Control[] controls = new Control[count];34713472for (int i = 0; i < count; i++) {3473// Try own factory first3474controls[i] = myResponseControlFactory.getControlInstance(3475ctls.elementAt(i));34763477// Try assigned factories if own produced null3478if (controls[i] == null) {3479controls[i] = ControlFactory.getControlInstance(3480ctls.elementAt(i), this, envprops);3481}3482}3483return controls;3484}34853486private static Control[] addControl(Control[] prevCtls, Control addition) {3487if (prevCtls == null) {3488return new Control[]{addition};3489}34903491// Find it3492int found = findControl(prevCtls, addition);3493if (found != -1) {3494return prevCtls; // no need to do it again3495}34963497Control[] newCtls = new Control[prevCtls.length+1];3498System.arraycopy(prevCtls, 0, newCtls, 0, prevCtls.length);3499newCtls[prevCtls.length] = addition;3500return newCtls;3501}35023503private static int findControl(Control[] ctls, Control target) {3504for (int i = 0; i < ctls.length; i++) {3505if (ctls[i] == target) {3506return i;3507}3508}3509return -1;3510}35113512private static Control[] removeControl(Control[] prevCtls, Control target) {3513if (prevCtls == null) {3514return null;3515}35163517// Find it3518int found = findControl(prevCtls, target);3519if (found == -1) {3520return prevCtls; // not there3521}35223523// Remove it3524Control[] newCtls = new Control[prevCtls.length-1];3525System.arraycopy(prevCtls, 0, newCtls, 0, found);3526System.arraycopy(prevCtls, found+1, newCtls, found,3527prevCtls.length-found-1);3528return newCtls;3529}35303531private static Control[] cloneControls(Control[] ctls) {3532if (ctls == null) {3533return null;3534}3535Control[] copiedCtls = new Control[ctls.length];3536System.arraycopy(ctls, 0, copiedCtls, 0, ctls.length);3537return copiedCtls;3538}35393540// -------------------- Events ------------------------3541/*3542* Access to eventSupport need not be synchronized even though the3543* Connection thread can access it asynchronously. It is3544* impossible for a race condition to occur because3545* eventSupport.addNamingListener() must have been called before3546* the Connection thread can call back to this ctx.3547*/3548public void addNamingListener(Name nm, int scope, NamingListener l)3549throws NamingException {3550addNamingListener(getTargetName(nm), scope, l);3551}35523553public void addNamingListener(String nm, int scope, NamingListener l)3554throws NamingException {3555if (eventSupport == null)3556eventSupport = new EventSupport(this);3557eventSupport.addNamingListener(getTargetName(new CompositeName(nm)),3558scope, l);35593560// If first time asking for unsol3561if (l instanceof UnsolicitedNotificationListener && !unsolicited) {3562addUnsolicited();3563}3564}35653566public void removeNamingListener(NamingListener l) throws NamingException {3567if (eventSupport == null)3568return; // no activity before, so just return35693570eventSupport.removeNamingListener(l);35713572// If removing an Unsol listener and it is the last one, let clnt know3573if (l instanceof UnsolicitedNotificationListener &&3574!eventSupport.hasUnsolicited()) {3575removeUnsolicited();3576}3577}35783579public void addNamingListener(String nm, String filter, SearchControls ctls,3580NamingListener l) throws NamingException {3581if (eventSupport == null)3582eventSupport = new EventSupport(this);3583eventSupport.addNamingListener(getTargetName(new CompositeName(nm)),3584filter, cloneSearchControls(ctls), l);35853586// If first time asking for unsol3587if (l instanceof UnsolicitedNotificationListener && !unsolicited) {3588addUnsolicited();3589}3590}35913592public void addNamingListener(Name nm, String filter, SearchControls ctls,3593NamingListener l) throws NamingException {3594addNamingListener(getTargetName(nm), filter, ctls, l);3595}35963597public void addNamingListener(Name nm, String filter, Object[] filterArgs,3598SearchControls ctls, NamingListener l) throws NamingException {3599addNamingListener(getTargetName(nm), filter, filterArgs, ctls, l);3600}36013602public void addNamingListener(String nm, String filterExpr, Object[] filterArgs,3603SearchControls ctls, NamingListener l) throws NamingException {3604String strfilter = SearchFilter.format(filterExpr, filterArgs);3605addNamingListener(getTargetName(new CompositeName(nm)), strfilter, ctls, l);3606}36073608public boolean targetMustExist() {3609return true;3610}36113612/**3613* Retrieves the target name for which the listener is registering.3614* If nm is a CompositeName, use its first and only component. It3615* cannot have more than one components because a target be outside of3616* this namespace. If nm is not a CompositeName, then treat it as a3617* compound name.3618* @param nm The non-null target name.3619*/3620private static String getTargetName(Name nm) throws NamingException {3621if (nm instanceof CompositeName) {3622if (nm.size() > 1) {3623throw new InvalidNameException(3624"Target cannot span multiple namespaces: " + nm);3625} else if (nm.isEmpty()) {3626return "";3627} else {3628return nm.get(0);3629}3630} else {3631// treat as compound name3632return nm.toString();3633}3634}36353636// ------------------ Unsolicited Notification ---------------3637// package private methods for handling unsolicited notification36383639/**3640* Registers this context with the underlying LdapClient.3641* When the underlying LdapClient receives an unsolicited notification,3642* it will invoke LdapCtx.fireUnsolicited() so that this context3643* can (using EventSupport) notified any registered listeners.3644* This method is called by EventSupport when an unsolicited listener3645* first registers with this context (should be called just once).3646* @see #removeUnsolicited3647* @see #fireUnsolicited3648*/3649private void addUnsolicited() throws NamingException {3650if (debug) {3651System.out.println("LdapCtx.addUnsolicited: " + this);3652}36533654// addNamingListener must have created EventSupport already3655ensureOpen();3656synchronized (eventSupport) {3657clnt.addUnsolicited(this);3658unsolicited = true;3659}3660}36613662/**3663* Removes this context from registering interest in unsolicited3664* notifications from the underlying LdapClient. This method is called3665* under any one of the following conditions:3666* <ul>3667* <li>All unsolicited listeners have been removed. (see removingNamingListener)3668* <li>This context is closed.3669* <li>This context's underlying LdapClient changes.3670*</ul>3671* After this method has been called, this context will not pass3672* on any events related to unsolicited notifications to EventSupport and3673* and its listeners.3674*/36753676private void removeUnsolicited() {3677if (debug) {3678System.out.println("LdapCtx.removeUnsolicited: " + unsolicited);3679}3680if (eventSupport == null) {3681return;3682}36833684// addNamingListener must have created EventSupport already3685synchronized(eventSupport) {3686if (unsolicited && clnt != null) {3687clnt.removeUnsolicited(this);3688}3689unsolicited = false;3690}3691}36923693/**3694* Uses EventSupport to fire an event related to an unsolicited notification.3695* Called by LdapClient when LdapClient receives an unsolicited notification.3696*/3697void fireUnsolicited(Object obj) {3698if (debug) {3699System.out.println("LdapCtx.fireUnsolicited: " + obj);3700}3701// addNamingListener must have created EventSupport already3702synchronized(eventSupport) {3703if (unsolicited) {3704eventSupport.fireUnsolicited(obj);37053706if (obj instanceof NamingException) {3707unsolicited = false;3708// No need to notify clnt because clnt is the3709// only one that can fire a NamingException to3710// unsol listeners and it will handle its own cleanup3711}3712}3713}3714}3715}371637173718