Path: blob/master/src/java.naming/share/classes/com/sun/jndi/ldap/LdapClient.java
41161 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 java.io.*;28import java.util.Locale;29import java.util.Vector;30import java.util.Hashtable;3132import javax.naming.*;33import javax.naming.directory.*;34import javax.naming.ldap.*;3536import com.sun.jndi.ldap.pool.PooledConnection;37import com.sun.jndi.ldap.pool.PoolCallback;38import com.sun.jndi.ldap.sasl.LdapSasl;39import com.sun.jndi.ldap.sasl.SaslInputStream;4041/**42* LDAP (RFC-1777) and LDAPv3 (RFC-2251) compliant client43*44* This class represents a connection to an LDAP client.45* Callers interact with this class at an LDAP operation level.46* That is, the caller invokes a method to do a SEARCH or MODRDN47* operation and gets back the result.48* The caller uses the constructor to create a connection to the server.49* It then needs to use authenticate() to perform an LDAP BIND.50* Note that for v3, BIND is optional so authenticate() might not51* actually send a BIND. authenticate() can be used later on to issue52* a BIND, for example, for a v3 client that wants to change the connection's53* credentials.54*<p>55* Multiple LdapCtx might share the same LdapClient. For example, contexts56* derived from the same initial context would share the same LdapClient57* until changes to a context's properties necessitates its own LdapClient.58* LdapClient methods that access shared data are thread-safe (i.e., caller59* does not have to sync).60*<p>61* Fields:62* isLdapv3 - no sync; initialized and updated within sync authenticate();63* always updated when connection is "quiet" and not shared;64* read access from outside LdapClient not sync65* referenceCount - sync within LdapClient; exception is forceClose() which66* is used by Connection thread to close connection upon receiving67* an Unsolicited Notification.68* access from outside LdapClient must sync;69* conn - no sync; Connection takes care of its own sync70* unsolicited - sync Vector; multiple operations sync'ed71*72* @author Vincent Ryan73* @author Jagane Sundar74* @author Rosanna Lee75*/7677public final class LdapClient implements PooledConnection {78// ---------------------- Constants ----------------------------------79private static final int debug = 0;80static final boolean caseIgnore = true;8182// Default list of binary attributes83private static final Hashtable<String, Boolean> defaultBinaryAttrs =84new Hashtable<>(23,0.75f);85static {86defaultBinaryAttrs.put("userpassword", Boolean.TRUE); //2.5.4.3587defaultBinaryAttrs.put("javaserializeddata", Boolean.TRUE);88//1.3.6.1.4.1.42.2.27.4.1.889defaultBinaryAttrs.put("javaserializedobject", Boolean.TRUE);90// 1.3.6.1.4.1.42.2.27.4.1.291defaultBinaryAttrs.put("jpegphoto", Boolean.TRUE);92//0.9.2342.19200300.100.1.6093defaultBinaryAttrs.put("audio", Boolean.TRUE); //0.9.2342.19200300.100.1.5594defaultBinaryAttrs.put("thumbnailphoto", Boolean.TRUE);95//1.3.6.1.4.1.1466.101.120.3596defaultBinaryAttrs.put("thumbnaillogo", Boolean.TRUE);97//1.3.6.1.4.1.1466.101.120.3698defaultBinaryAttrs.put("usercertificate", Boolean.TRUE); //2.5.4.3699defaultBinaryAttrs.put("cacertificate", Boolean.TRUE); //2.5.4.37100defaultBinaryAttrs.put("certificaterevocationlist", Boolean.TRUE);101//2.5.4.39102defaultBinaryAttrs.put("authorityrevocationlist", Boolean.TRUE); //2.5.4.38103defaultBinaryAttrs.put("crosscertificatepair", Boolean.TRUE); //2.5.4.40104defaultBinaryAttrs.put("photo", Boolean.TRUE); //0.9.2342.19200300.100.1.7105defaultBinaryAttrs.put("personalsignature", Boolean.TRUE);106//0.9.2342.19200300.100.1.53107defaultBinaryAttrs.put("x500uniqueidentifier", Boolean.TRUE); //2.5.4.45108}109110private static final String DISCONNECT_OID = "1.3.6.1.4.1.1466.20036";111112113// ----------------------- instance fields ------------------------114boolean isLdapv3; // Used by LdapCtx115int referenceCount = 1; // Used by LdapCtx for check for sharing116117final Connection conn; // Connection to server; has reader thread118// used by LdapCtx for StartTLS119120private final PoolCallback pcb;121private final boolean pooled;122private boolean authenticateCalled = false;123124////////////////////////////////////////////////////////////////////////////125//126// constructor: Create an authenticated connection to server127//128////////////////////////////////////////////////////////////////////////////129130LdapClient(String host, int port, String socketFactory,131int connectTimeout, int readTimeout, OutputStream trace, PoolCallback pcb)132throws NamingException {133134if (debug > 0)135System.err.println("LdapClient: constructor called " + host + ":" + port );136conn = new Connection(this, host, port, socketFactory, connectTimeout, readTimeout,137trace);138139this.pcb = pcb;140pooled = (pcb != null);141}142143synchronized boolean authenticateCalled() {144return authenticateCalled;145}146147synchronized LdapResult148authenticate(boolean initial, String name, Object pw, int version,149String authMechanism, Control[] ctls, Hashtable<?,?> env)150throws NamingException {151152int readTimeout = conn.readTimeout;153conn.readTimeout = conn.connectTimeout;154LdapResult res = null;155156try {157authenticateCalled = true;158159try {160ensureOpen();161} catch (IOException e) {162NamingException ne = new CommunicationException();163ne.setRootCause(e);164throw ne;165}166167switch (version) {168case LDAP_VERSION3_VERSION2:169case LDAP_VERSION3:170isLdapv3 = true;171break;172case LDAP_VERSION2:173isLdapv3 = false;174break;175default:176throw new CommunicationException("Protocol version " + version +177" not supported");178}179180if (authMechanism.equalsIgnoreCase("none") ||181authMechanism.equalsIgnoreCase("anonymous")) {182183// Perform LDAP bind if we are reauthenticating, using LDAPv2,184// supporting failover to LDAPv2, or controls have been supplied.185if (!initial ||186(version == LDAP_VERSION2) ||187(version == LDAP_VERSION3_VERSION2) ||188((ctls != null) && (ctls.length > 0))) {189try {190// anonymous bind; update name/pw for LDAPv2 retry191res = ldapBind(name=null, (byte[])(pw=null), ctls, null,192false);193if (res.status == LdapClient.LDAP_SUCCESS) {194conn.setBound();195}196} catch (IOException e) {197NamingException ne =198new CommunicationException("anonymous bind failed: " +199conn.host + ":" + conn.port);200ne.setRootCause(e);201throw ne;202}203} else {204// Skip LDAP bind for LDAPv3 anonymous bind205res = new LdapResult();206res.status = LdapClient.LDAP_SUCCESS;207}208} else if (authMechanism.equalsIgnoreCase("simple")) {209// simple authentication210byte[] encodedPw = null;211try {212encodedPw = encodePassword(pw, isLdapv3);213res = ldapBind(name, encodedPw, ctls, null, false);214if (res.status == LdapClient.LDAP_SUCCESS) {215conn.setBound();216}217} catch (IOException e) {218NamingException ne =219new CommunicationException("simple bind failed: " +220conn.host + ":" + conn.port);221ne.setRootCause(e);222throw ne;223} finally {224// If pw was copied to a new array, clear that array as225// a security precaution.226if (encodedPw != pw && encodedPw != null) {227for (int i = 0; i < encodedPw.length; i++) {228encodedPw[i] = 0;229}230}231}232} else if (isLdapv3) {233// SASL authentication234try {235res = LdapSasl.saslBind(this, conn, conn.host, name, pw,236authMechanism, env, ctls);237if (res.status == LdapClient.LDAP_SUCCESS) {238conn.setBound();239}240} catch (IOException e) {241NamingException ne =242new CommunicationException("SASL bind failed: " +243conn.host + ":" + conn.port);244ne.setRootCause(e);245throw ne;246}247} else {248throw new AuthenticationNotSupportedException(authMechanism);249}250251//252// re-try login using v2 if failing over253//254if (initial &&255(res.status == LdapClient.LDAP_PROTOCOL_ERROR) &&256(version == LdapClient.LDAP_VERSION3_VERSION2) &&257(authMechanism.equalsIgnoreCase("none") ||258authMechanism.equalsIgnoreCase("anonymous") ||259authMechanism.equalsIgnoreCase("simple"))) {260261byte[] encodedPw = null;262try {263isLdapv3 = false;264encodedPw = encodePassword(pw, false);265res = ldapBind(name, encodedPw, ctls, null, false);266if (res.status == LdapClient.LDAP_SUCCESS) {267conn.setBound();268}269} catch (IOException e) {270NamingException ne =271new CommunicationException(authMechanism + ":" +272conn.host + ":" + conn.port);273ne.setRootCause(e);274throw ne;275} finally {276// If pw was copied to a new array, clear that array as277// a security precaution.278if (encodedPw != pw && encodedPw != null) {279for (int i = 0; i < encodedPw.length; i++) {280encodedPw[i] = 0;281}282}283}284}285286// principal name not found287// (map NameNotFoundException to AuthenticationException)288// %%% This is a workaround for Netscape servers returning289// %%% no such object when the principal name is not found290// %%% Note that when this workaround is applied, it does not allow291// %%% response controls to be recorded by the calling context292if (res.status == LdapClient.LDAP_NO_SUCH_OBJECT) {293throw new AuthenticationException(294getErrorMessage(res.status, res.errorMessage));295}296conn.setV3(isLdapv3);297return res;298} finally {299conn.readTimeout = readTimeout;300}301}302303/**304* Sends an LDAP Bind request.305* Cannot be private; called by LdapSasl306* @param dn The possibly null DN to use in the BIND request. null if anonymous.307* @param toServer The possibly null array of bytes to send to the server.308* @param auth The authentication mechanism309*310*/311synchronized public LdapResult ldapBind(String dn, byte[]toServer,312Control[] bindCtls, String auth, boolean pauseAfterReceipt)313throws java.io.IOException, NamingException {314315ensureOpen();316317// flush outstanding requests318conn.abandonOutstandingReqs(null);319320BerEncoder ber = new BerEncoder();321int curMsgId = conn.getMsgId();322LdapResult res = new LdapResult();323res.status = LDAP_OPERATIONS_ERROR;324325//326// build the bind request.327//328ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);329ber.encodeInt(curMsgId);330ber.beginSeq(LdapClient.LDAP_REQ_BIND);331ber.encodeInt(isLdapv3 ? LDAP_VERSION3 : LDAP_VERSION2);332ber.encodeString(dn, isLdapv3);333334// if authentication mechanism specified, it is SASL335if (auth != null) {336ber.beginSeq(Ber.ASN_CONTEXT | Ber.ASN_CONSTRUCTOR | 3);337ber.encodeString(auth, isLdapv3); // SASL mechanism338if (toServer != null) {339ber.encodeOctetString(toServer,340Ber.ASN_OCTET_STR);341}342ber.endSeq();343} else {344if (toServer != null) {345ber.encodeOctetString(toServer, Ber.ASN_CONTEXT);346} else {347ber.encodeOctetString(null, Ber.ASN_CONTEXT, 0, 0);348}349}350ber.endSeq();351352// Encode controls353if (isLdapv3) {354encodeControls(ber, bindCtls);355}356ber.endSeq();357358LdapRequest req = conn.writeRequest(ber, curMsgId, pauseAfterReceipt);359if (toServer != null) {360ber.reset(); // clear internally-stored password361}362363// Read reply364BerDecoder rber = conn.readReply(req);365366rber.parseSeq(null); // init seq367rber.parseInt(); // msg id368if (rber.parseByte() != LDAP_REP_BIND) {369return res;370}371372rber.parseLength();373parseResult(rber, res, isLdapv3);374375// handle server's credentials (if present)376if (isLdapv3 &&377(rber.bytesLeft() > 0) &&378(rber.peekByte() == (Ber.ASN_CONTEXT | 7))) {379res.serverCreds = rber.parseOctetString((Ber.ASN_CONTEXT | 7), null);380}381382res.resControls = isLdapv3 ? parseControls(rber) : null;383384conn.removeRequest(req);385return res;386}387388/**389* Determines whether SASL encryption/integrity is in progress.390* This check is made prior to reauthentication. You cannot reauthenticate391* over an encrypted/integrity-protected SASL channel. You must392* close the channel and open a new one.393*/394boolean usingSaslStreams() {395return (conn.inStream instanceof SaslInputStream);396}397398// Returns true if client connection was upgraded399// with STARTTLS extended operation on the server side400boolean isUpgradedToStartTls() {401return conn.isUpgradedToStartTls();402}403404synchronized void incRefCount() {405++referenceCount;406if (debug > 1) {407System.err.println("LdapClient.incRefCount: " + referenceCount + " " + this);408}409410}411412/**413* Returns the encoded password.414*/415private static byte[] encodePassword(Object pw, boolean v3) throws IOException {416417if (pw instanceof char[]) {418pw = new String((char[])pw);419}420421if (pw instanceof String) {422if (v3) {423return ((String)pw).getBytes("UTF8");424} else {425return ((String)pw).getBytes("8859_1");426}427} else {428return (byte[])pw;429}430}431432synchronized void close(Control[] reqCtls, boolean hardClose) {433--referenceCount;434435if (debug > 1) {436System.err.println("LdapClient: " + this);437System.err.println("LdapClient: close() called: " + referenceCount);438(new Throwable()).printStackTrace();439}440441if (referenceCount <= 0) {442if (debug > 0) System.err.println("LdapClient: closed connection " + this);443if (!pooled) {444// Not being pooled; continue with closing445conn.cleanup(reqCtls, false);446} else {447// Pooled448// Is this a real close or a request to return conn to pool449if (hardClose) {450conn.cleanup(reqCtls, false);451pcb.removePooledConnection(this);452} else {453pcb.releasePooledConnection(this);454}455}456}457}458459// NOTE: Should NOT be synchronized otherwise won't be able to close460private void forceClose(boolean cleanPool) {461referenceCount = 0; // force closing of connection462463if (debug > 1) {464System.err.println("LdapClient: forceClose() of " + this);465}466if (debug > 0) {467System.err.println(468"LdapClient: forced close of connection " + this);469}470conn.cleanup(null, false);471if (cleanPool) {472pcb.removePooledConnection(this);473}474}475476@SuppressWarnings("deprecation")477protected void finalize() {478if (debug > 0) System.err.println("LdapClient: finalize " + this);479forceClose(pooled);480}481482/*483* Used by connection pooling to close physical connection.484*/485synchronized public void closeConnection() {486forceClose(false); // this is a pool callback so no need to clean pool487}488489/**490* Called by Connection.cleanup(). LdapClient should491* notify any unsolicited listeners and removing itself from any pool.492* This is almost like forceClose(), except it doesn't call493* Connection.cleanup() (because this is called from cleanup()).494*/495void processConnectionClosure() {496// Notify listeners497if (unsolicited.size() > 0) {498String msg;499if (conn != null) {500msg = conn.host + ":" + conn.port + " connection closed";501} else {502msg = "Connection closed";503}504notifyUnsolicited(new CommunicationException(msg));505}506507// Remove from pool508if (pooled) {509pcb.removePooledConnection(this);510}511}512513////////////////////////////////////////////////////////////////////////////514//515// LDAP search. also includes methods to encode rfc 1558 compliant filters516//517////////////////////////////////////////////////////////////////////////////518519static final int SCOPE_BASE_OBJECT = 0;520static final int SCOPE_ONE_LEVEL = 1;521static final int SCOPE_SUBTREE = 2;522523LdapResult search(String dn, int scope, int deref, int sizeLimit,524int timeLimit, boolean attrsOnly, String attrs[],525String filter, int batchSize, Control[] reqCtls,526Hashtable<String, Boolean> binaryAttrs,527boolean waitFirstReply, int replyQueueCapacity)528throws IOException, NamingException {529530ensureOpen();531532LdapResult res = new LdapResult();533534BerEncoder ber = new BerEncoder();535int curMsgId = conn.getMsgId();536537ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);538ber.encodeInt(curMsgId);539ber.beginSeq(LDAP_REQ_SEARCH);540ber.encodeString(dn == null ? "" : dn, isLdapv3);541ber.encodeInt(scope, LBER_ENUMERATED);542ber.encodeInt(deref, LBER_ENUMERATED);543ber.encodeInt(sizeLimit);544ber.encodeInt(timeLimit);545ber.encodeBoolean(attrsOnly);546Filter.encodeFilterString(ber, filter, isLdapv3);547ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);548ber.encodeStringArray(attrs, isLdapv3);549ber.endSeq();550ber.endSeq();551if (isLdapv3) encodeControls(ber, reqCtls);552ber.endSeq();553554LdapRequest req =555conn.writeRequest(ber, curMsgId, false, replyQueueCapacity);556557res.msgId = curMsgId;558res.status = LdapClient.LDAP_SUCCESS; //optimistic559if (waitFirstReply) {560// get first reply561res = getSearchReply(req, batchSize, res, binaryAttrs);562}563return res;564}565566/*567* Abandon the search operation and remove it from the message queue.568*/569void clearSearchReply(LdapResult res, Control[] ctls) {570if (res != null) {571572// Only send an LDAP abandon operation when clearing the search573// reply from a one-level or subtree search.574LdapRequest req = conn.findRequest(res.msgId);575if (req == null) {576return;577}578579// OK if req got removed after check; double removal attempt580// but otherwise no harm done581582// Send an LDAP abandon only if the search operation has not yet583// completed.584if (req.hasSearchCompleted()) {585conn.removeRequest(req);586} else {587conn.abandonRequest(req, ctls);588}589}590}591592/*593* Retrieve the next batch of entries and/or referrals.594*/595LdapResult getSearchReply(int batchSize, LdapResult res,596Hashtable<String, Boolean> binaryAttrs) throws IOException, NamingException {597598ensureOpen();599600LdapRequest req;601602if ((req = conn.findRequest(res.msgId)) == null) {603return null;604}605606return getSearchReply(req, batchSize, res, binaryAttrs);607}608609private LdapResult getSearchReply(LdapRequest req,610int batchSize, LdapResult res, Hashtable<String, Boolean> binaryAttrs)611throws IOException, NamingException {612613if (batchSize == 0)614batchSize = Integer.MAX_VALUE;615616if (res.entries != null) {617res.entries.setSize(0); // clear the (previous) set of entries618} else {619res.entries =620new Vector<>(batchSize == Integer.MAX_VALUE ? 32 : batchSize);621}622623if (res.referrals != null) {624res.referrals.setSize(0); // clear the (previous) set of referrals625}626627BerDecoder replyBer; // Decoder for response628int seq; // Request id629630Attributes lattrs; // Attribute set read from response631Attribute la; // Attribute read from response632String DN; // DN read from response633LdapEntry le; // LDAP entry representing response634int[] seqlen; // Holder for response length635int endseq; // Position of end of response636637for (int i = 0; i < batchSize;) {638replyBer = conn.readReply(req);639640//641// process search reply642//643replyBer.parseSeq(null); // init seq644replyBer.parseInt(); // req id645seq = replyBer.parseSeq(null);646647if (seq == LDAP_REP_SEARCH) {648649// handle LDAPv3 search entries650lattrs = new BasicAttributes(caseIgnore);651DN = replyBer.parseString(isLdapv3);652le = new LdapEntry(DN, lattrs);653seqlen = new int[1];654655replyBer.parseSeq(seqlen);656endseq = replyBer.getParsePosition() + seqlen[0];657while ((replyBer.getParsePosition() < endseq) &&658(replyBer.bytesLeft() > 0)) {659la = parseAttribute(replyBer, binaryAttrs);660lattrs.put(la);661}662le.respCtls = isLdapv3 ? parseControls(replyBer) : null;663664res.entries.addElement(le);665i++;666667} else if ((seq == LDAP_REP_SEARCH_REF) && isLdapv3) {668669// handle LDAPv3 search reference670Vector<String> URLs = new Vector<>(4);671672// %%% Although not strictly correct, some LDAP servers673// encode the SEQUENCE OF tag in the SearchResultRef674if (replyBer.peekByte() ==675(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR)) {676replyBer.parseSeq(null);677}678679while ((replyBer.bytesLeft() > 0) &&680(replyBer.peekByte() == Ber.ASN_OCTET_STR)) {681682URLs.addElement(replyBer.parseString(isLdapv3));683}684685if (res.referrals == null) {686res.referrals = new Vector<>(4);687}688res.referrals.addElement(URLs);689res.resControls = isLdapv3 ? parseControls(replyBer) : null;690691// Save referral and continue to get next search result692693} else if (seq == LDAP_REP_EXTENSION) {694695parseExtResponse(replyBer, res); //%%% ignore for now696697} else if (seq == LDAP_REP_RESULT) {698699parseResult(replyBer, res, isLdapv3);700res.resControls = isLdapv3 ? parseControls(replyBer) : null;701702conn.removeRequest(req);703return res; // Done with search704}705}706707return res;708}709710private Attribute parseAttribute(BerDecoder ber,711Hashtable<String, Boolean> binaryAttrs)712throws IOException {713714int len[] = new int[1];715int seq = ber.parseSeq(null);716String attrid = ber.parseString(isLdapv3);717boolean hasBinaryValues = isBinaryValued(attrid, binaryAttrs);718Attribute la = new LdapAttribute(attrid);719720if ((seq = ber.parseSeq(len)) == LBER_SET) {721int attrlen = len[0];722while (ber.bytesLeft() > 0 && attrlen > 0) {723try {724attrlen -= parseAttributeValue(ber, la, hasBinaryValues);725} catch (IOException ex) {726ber.seek(attrlen);727break;728}729}730} else {731// Skip the rest of the sequence because it is not what we want732ber.seek(len[0]);733}734return la;735}736737//738// returns number of bytes that were parsed. Adds the values to attr739//740private int parseAttributeValue(BerDecoder ber, Attribute la,741boolean hasBinaryValues) throws IOException {742743int len[] = new int[1];744745if (hasBinaryValues) {746la.add(ber.parseOctetString(ber.peekByte(), len));747} else {748la.add(ber.parseStringWithTag(749Ber.ASN_SIMPLE_STRING, isLdapv3, len));750}751return len[0];752}753754private boolean isBinaryValued(String attrid,755Hashtable<String, Boolean> binaryAttrs) {756String id = attrid.toLowerCase(Locale.ENGLISH);757758return ((id.indexOf(";binary") != -1) ||759defaultBinaryAttrs.containsKey(id) ||760((binaryAttrs != null) && (binaryAttrs.containsKey(id))));761}762763// package entry point; used by Connection764static void parseResult(BerDecoder replyBer, LdapResult res,765boolean isLdapv3) throws IOException {766767res.status = replyBer.parseEnumeration();768res.matchedDN = replyBer.parseString(isLdapv3);769res.errorMessage = replyBer.parseString(isLdapv3);770771// handle LDAPv3 referrals (if present)772if (isLdapv3 &&773(replyBer.bytesLeft() > 0) &&774(replyBer.peekByte() == LDAP_REP_REFERRAL)) {775776Vector<String> URLs = new Vector<>(4);777int[] seqlen = new int[1];778779replyBer.parseSeq(seqlen);780int endseq = replyBer.getParsePosition() + seqlen[0];781while ((replyBer.getParsePosition() < endseq) &&782(replyBer.bytesLeft() > 0)) {783784URLs.addElement(replyBer.parseString(isLdapv3));785}786787if (res.referrals == null) {788res.referrals = new Vector<>(4);789}790res.referrals.addElement(URLs);791}792}793794// package entry point; used by Connection795static Vector<Control> parseControls(BerDecoder replyBer) throws IOException {796797// handle LDAPv3 controls (if present)798if ((replyBer.bytesLeft() > 0) && (replyBer.peekByte() == LDAP_CONTROLS)) {799Vector<Control> ctls = new Vector<>(4);800String controlOID;801boolean criticality = false; // default802byte[] controlValue = null; // optional803int[] seqlen = new int[1];804805replyBer.parseSeq(seqlen);806int endseq = replyBer.getParsePosition() + seqlen[0];807while ((replyBer.getParsePosition() < endseq) &&808(replyBer.bytesLeft() > 0)) {809810replyBer.parseSeq(null);811controlOID = replyBer.parseString(true);812813if ((replyBer.bytesLeft() > 0) &&814(replyBer.peekByte() == Ber.ASN_BOOLEAN)) {815criticality = replyBer.parseBoolean();816}817if ((replyBer.bytesLeft() > 0) &&818(replyBer.peekByte() == Ber.ASN_OCTET_STR)) {819controlValue =820replyBer.parseOctetString(Ber.ASN_OCTET_STR, null);821}822if (controlOID != null) {823ctls.addElement(824new BasicControl(controlOID, criticality, controlValue));825}826}827return ctls;828} else {829return null;830}831}832833private void parseExtResponse(BerDecoder replyBer, LdapResult res)834throws IOException {835836parseResult(replyBer, res, isLdapv3);837838if ((replyBer.bytesLeft() > 0) &&839(replyBer.peekByte() == LDAP_REP_EXT_OID)) {840res.extensionId =841replyBer.parseStringWithTag(LDAP_REP_EXT_OID, isLdapv3, null);842}843if ((replyBer.bytesLeft() > 0) &&844(replyBer.peekByte() == LDAP_REP_EXT_VAL)) {845res.extensionValue =846replyBer.parseOctetString(LDAP_REP_EXT_VAL, null);847}848849res.resControls = parseControls(replyBer);850}851852//853// Encode LDAPv3 controls854//855static void encodeControls(BerEncoder ber, Control[] reqCtls)856throws IOException {857858if ((reqCtls == null) || (reqCtls.length == 0)) {859return;860}861862byte[] controlVal;863864ber.beginSeq(LdapClient.LDAP_CONTROLS);865866for (int i = 0; i < reqCtls.length; i++) {867ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);868ber.encodeString(reqCtls[i].getID(), true); // control OID869if (reqCtls[i].isCritical()) {870ber.encodeBoolean(true); // critical control871}872if ((controlVal = reqCtls[i].getEncodedValue()) != null) {873ber.encodeOctetString(controlVal, Ber.ASN_OCTET_STR);874}875ber.endSeq();876}877ber.endSeq();878}879880/**881* Reads the next reply corresponding to msgId, outstanding on requestBer.882* Processes the result and any controls.883*/884private LdapResult processReply(LdapRequest req,885LdapResult res, int responseType) throws IOException, NamingException {886887BerDecoder rber = conn.readReply(req);888889rber.parseSeq(null); // init seq890rber.parseInt(); // msg id891if (rber.parseByte() != responseType) {892return res;893}894895rber.parseLength();896parseResult(rber, res, isLdapv3);897res.resControls = isLdapv3 ? parseControls(rber) : null;898899conn.removeRequest(req);900901return res; // Done with operation902}903904////////////////////////////////////////////////////////////////////////////905//906// LDAP modify:907// Modify the DN dn with the operations on attributes attrs.908// ie, operations[0] is the operation to be performed on909// attrs[0];910// dn - DN to modify911// operations - add, delete or replace912// attrs - array of Attribute913// reqCtls - array of request controls914//915////////////////////////////////////////////////////////////////////////////916917static final int ADD = 0;918static final int DELETE = 1;919static final int REPLACE = 2;920921LdapResult modify(String dn, int operations[], Attribute attrs[],922Control[] reqCtls)923throws IOException, NamingException {924925ensureOpen();926927LdapResult res = new LdapResult();928res.status = LDAP_OPERATIONS_ERROR;929930if (dn == null || operations.length != attrs.length)931return res;932933BerEncoder ber = new BerEncoder();934int curMsgId = conn.getMsgId();935936ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);937ber.encodeInt(curMsgId);938ber.beginSeq(LDAP_REQ_MODIFY);939ber.encodeString(dn, isLdapv3);940ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);941for (int i = 0; i < operations.length; i++) {942ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);943ber.encodeInt(operations[i], LBER_ENUMERATED);944945// zero values is not permitted for the add op.946if ((operations[i] == ADD) && hasNoValue(attrs[i])) {947throw new InvalidAttributeValueException(948"'" + attrs[i].getID() + "' has no values.");949} else {950encodeAttribute(ber, attrs[i]);951}952ber.endSeq();953}954ber.endSeq();955ber.endSeq();956if (isLdapv3) encodeControls(ber, reqCtls);957ber.endSeq();958959LdapRequest req = conn.writeRequest(ber, curMsgId);960961return processReply(req, res, LDAP_REP_MODIFY);962}963964private void encodeAttribute(BerEncoder ber, Attribute attr)965throws IOException, NamingException {966967ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);968ber.encodeString(attr.getID(), isLdapv3);969ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR | 1);970NamingEnumeration<?> enum_ = attr.getAll();971Object val;972while (enum_.hasMore()) {973val = enum_.next();974if (val instanceof String) {975ber.encodeString((String)val, isLdapv3);976} else if (val instanceof byte[]) {977ber.encodeOctetString((byte[])val, Ber.ASN_OCTET_STR);978} else if (val == null) {979// no attribute value980} else {981throw new InvalidAttributeValueException(982"Malformed '" + attr.getID() + "' attribute value");983}984}985ber.endSeq();986ber.endSeq();987}988989private static boolean hasNoValue(Attribute attr) throws NamingException {990return attr.size() == 0 || (attr.size() == 1 && attr.get() == null);991}992993////////////////////////////////////////////////////////////////////////////994//995// LDAP add996// Adds entry to the Directory997//998////////////////////////////////////////////////////////////////////////////9991000LdapResult add(LdapEntry entry, Control[] reqCtls)1001throws IOException, NamingException {10021003ensureOpen();10041005LdapResult res = new LdapResult();1006res.status = LDAP_OPERATIONS_ERROR;10071008if (entry == null || entry.DN == null)1009return res;10101011BerEncoder ber = new BerEncoder();1012int curMsgId = conn.getMsgId();1013Attribute attr;10141015ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1016ber.encodeInt(curMsgId);1017ber.beginSeq(LDAP_REQ_ADD);1018ber.encodeString(entry.DN, isLdapv3);1019ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1020NamingEnumeration<? extends Attribute> enum_ =1021entry.attributes.getAll();1022while (enum_.hasMore()) {1023attr = enum_.next();10241025// zero values is not permitted1026if (hasNoValue(attr)) {1027throw new InvalidAttributeValueException(1028"'" + attr.getID() + "' has no values.");1029} else {1030encodeAttribute(ber, attr);1031}1032}1033ber.endSeq();1034ber.endSeq();1035if (isLdapv3) encodeControls(ber, reqCtls);1036ber.endSeq();10371038LdapRequest req = conn.writeRequest(ber, curMsgId);1039return processReply(req, res, LDAP_REP_ADD);1040}10411042////////////////////////////////////////////////////////////////////////////1043//1044// LDAP delete1045// deletes entry from the Directory1046//1047////////////////////////////////////////////////////////////////////////////10481049LdapResult delete(String DN, Control[] reqCtls)1050throws IOException, NamingException {10511052ensureOpen();10531054LdapResult res = new LdapResult();1055res.status = LDAP_OPERATIONS_ERROR;10561057if (DN == null)1058return res;10591060BerEncoder ber = new BerEncoder();1061int curMsgId = conn.getMsgId();10621063ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1064ber.encodeInt(curMsgId);1065ber.encodeString(DN, LDAP_REQ_DELETE, isLdapv3);1066if (isLdapv3) encodeControls(ber, reqCtls);1067ber.endSeq();10681069LdapRequest req = conn.writeRequest(ber, curMsgId);10701071return processReply(req, res, LDAP_REP_DELETE);1072}10731074////////////////////////////////////////////////////////////////////////////1075//1076// LDAP modrdn1077// Changes the last element of DN to newrdn1078// dn - DN to change1079// newrdn - new RDN to rename to1080// deleteoldrdn - boolean whether to delete old attrs or not1081// newSuperior - new place to put the entry in the tree1082// (ignored if server is LDAPv2)1083// reqCtls - array of request controls1084//1085////////////////////////////////////////////////////////////////////////////10861087LdapResult moddn(String DN, String newrdn, boolean deleteOldRdn,1088String newSuperior, Control[] reqCtls)1089throws IOException, NamingException {10901091ensureOpen();10921093boolean changeSuperior = (newSuperior != null &&1094newSuperior.length() > 0);10951096LdapResult res = new LdapResult();1097res.status = LDAP_OPERATIONS_ERROR;10981099if (DN == null || newrdn == null)1100return res;11011102BerEncoder ber = new BerEncoder();1103int curMsgId = conn.getMsgId();11041105ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1106ber.encodeInt(curMsgId);1107ber.beginSeq(LDAP_REQ_MODRDN);1108ber.encodeString(DN, isLdapv3);1109ber.encodeString(newrdn, isLdapv3);1110ber.encodeBoolean(deleteOldRdn);1111if(isLdapv3 && changeSuperior) {1112//System.err.println("changin superior");1113ber.encodeString(newSuperior, LDAP_SUPERIOR_DN, isLdapv3);1114}1115ber.endSeq();1116if (isLdapv3) encodeControls(ber, reqCtls);1117ber.endSeq();111811191120LdapRequest req = conn.writeRequest(ber, curMsgId);11211122return processReply(req, res, LDAP_REP_MODRDN);1123}11241125////////////////////////////////////////////////////////////////////////////1126//1127// LDAP compare1128// Compare attribute->value pairs in dn1129//1130////////////////////////////////////////////////////////////////////////////11311132LdapResult compare(String DN, String type, String value, Control[] reqCtls)1133throws IOException, NamingException {11341135ensureOpen();11361137LdapResult res = new LdapResult();1138res.status = LDAP_OPERATIONS_ERROR;11391140if (DN == null || type == null || value == null)1141return res;11421143BerEncoder ber = new BerEncoder();1144int curMsgId = conn.getMsgId();11451146ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1147ber.encodeInt(curMsgId);1148ber.beginSeq(LDAP_REQ_COMPARE);1149ber.encodeString(DN, isLdapv3);1150ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1151ber.encodeString(type, isLdapv3);11521153// replace any escaped characters in the value1154byte[] val = isLdapv3 ?1155value.getBytes("UTF8") : value.getBytes("8859_1");1156ber.encodeOctetString(1157Filter.unescapeFilterValue(val, 0, val.length),1158Ber.ASN_OCTET_STR);11591160ber.endSeq();1161ber.endSeq();1162if (isLdapv3) encodeControls(ber, reqCtls);1163ber.endSeq();11641165LdapRequest req = conn.writeRequest(ber, curMsgId);11661167return processReply(req, res, LDAP_REP_COMPARE);1168}11691170////////////////////////////////////////////////////////////////////////////1171//1172// LDAP extended operation1173//1174////////////////////////////////////////////////////////////////////////////11751176LdapResult extendedOp(String id, byte[] request, Control[] reqCtls,1177boolean pauseAfterReceipt) throws IOException, NamingException {11781179ensureOpen();11801181LdapResult res = new LdapResult();1182res.status = LDAP_OPERATIONS_ERROR;11831184if (id == null)1185return res;11861187BerEncoder ber = new BerEncoder();1188int curMsgId = conn.getMsgId();11891190ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1191ber.encodeInt(curMsgId);1192ber.beginSeq(LDAP_REQ_EXTENSION);1193ber.encodeString(id,1194Ber.ASN_CONTEXT | 0, isLdapv3);//[0]1195if (request != null) {1196ber.encodeOctetString(request,1197Ber.ASN_CONTEXT | 1);//[1]1198}1199ber.endSeq();1200encodeControls(ber, reqCtls); // always v31201ber.endSeq();12021203LdapRequest req = conn.writeRequest(ber, curMsgId, pauseAfterReceipt);12041205BerDecoder rber = conn.readReply(req);12061207rber.parseSeq(null); // init seq1208rber.parseInt(); // msg id1209if (rber.parseByte() != LDAP_REP_EXTENSION) {1210return res;1211}12121213rber.parseLength();1214parseExtResponse(rber, res);1215conn.removeRequest(req);12161217return res; // Done with operation1218}1219122012211222////////////////////////////////////////////////////////////////////////////1223//1224// Some BER definitions convenient for LDAP1225//1226////////////////////////////////////////////////////////////////////////////12271228static final int LDAP_VERSION3_VERSION2 = 32;1229static final int LDAP_VERSION2 = 0x02;1230static final int LDAP_VERSION3 = 0x03; // LDAPv31231static final int LDAP_VERSION = LDAP_VERSION3;12321233static final int LDAP_REF_FOLLOW = 0x01; // follow referrals1234static final int LDAP_REF_THROW = 0x02; // throw referral ex.1235static final int LDAP_REF_IGNORE = 0x03; // ignore referrals1236static final int LDAP_REF_FOLLOW_SCHEME = 0x04; // follow referrals of the same scheme12371238static final String LDAP_URL = "ldap://"; // LDAPv31239static final String LDAPS_URL = "ldaps://"; // LDAPv312401241static final int LBER_BOOLEAN = 0x01;1242static final int LBER_INTEGER = 0x02;1243static final int LBER_BITSTRING = 0x03;1244static final int LBER_OCTETSTRING = 0x04;1245static final int LBER_NULL = 0x05;1246static final int LBER_ENUMERATED = 0x0a;1247static final int LBER_SEQUENCE = 0x30;1248static final int LBER_SET = 0x31;12491250static final int LDAP_SUPERIOR_DN = 0x80;12511252static final int LDAP_REQ_BIND = 0x60; // app + constructed1253static final int LDAP_REQ_UNBIND = 0x42; // app + primitive1254static final int LDAP_REQ_SEARCH = 0x63; // app + constructed1255static final int LDAP_REQ_MODIFY = 0x66; // app + constructed1256static final int LDAP_REQ_ADD = 0x68; // app + constructed1257static final int LDAP_REQ_DELETE = 0x4a; // app + primitive1258static final int LDAP_REQ_MODRDN = 0x6c; // app + constructed1259static final int LDAP_REQ_COMPARE = 0x6e; // app + constructed1260static final int LDAP_REQ_ABANDON = 0x50; // app + primitive1261static final int LDAP_REQ_EXTENSION = 0x77; // app + constructed (LDAPv3)12621263static final int LDAP_REP_BIND = 0x61; // app + constructed | 11264static final int LDAP_REP_SEARCH = 0x64; // app + constructed | 41265static final int LDAP_REP_SEARCH_REF = 0x73;// app + constructed (LDAPv3)1266static final int LDAP_REP_RESULT = 0x65; // app + constructed | 51267static final int LDAP_REP_MODIFY = 0x67; // app + constructed | 71268static final int LDAP_REP_ADD = 0x69; // app + constructed | 91269static final int LDAP_REP_DELETE = 0x6b; // app + primitive | b1270static final int LDAP_REP_MODRDN = 0x6d; // app + primitive | d1271static final int LDAP_REP_COMPARE = 0x6f; // app + primitive | f1272static final int LDAP_REP_EXTENSION = 0x78; // app + constructed (LDAPv3)12731274static final int LDAP_REP_REFERRAL = 0xa3; // ctx + constructed (LDAPv3)1275static final int LDAP_REP_EXT_OID = 0x8a; // ctx + primitive (LDAPv3)1276static final int LDAP_REP_EXT_VAL = 0x8b; // ctx + primitive (LDAPv3)12771278// LDAPv3 Controls12791280static final int LDAP_CONTROLS = 0xa0; // ctx + constructed (LDAPv3)1281static final String LDAP_CONTROL_MANAGE_DSA_IT = "2.16.840.1.113730.3.4.2";1282static final String LDAP_CONTROL_PREFERRED_LANG = "1.3.6.1.4.1.1466.20035";1283static final String LDAP_CONTROL_PAGED_RESULTS = "1.2.840.113556.1.4.319";1284static final String LDAP_CONTROL_SERVER_SORT_REQ = "1.2.840.113556.1.4.473";1285static final String LDAP_CONTROL_SERVER_SORT_RES = "1.2.840.113556.1.4.474";12861287////////////////////////////////////////////////////////////////////////////1288//1289// return codes1290//1291////////////////////////////////////////////////////////////////////////////12921293static final int LDAP_SUCCESS = 0;1294static final int LDAP_OPERATIONS_ERROR = 1;1295static final int LDAP_PROTOCOL_ERROR = 2;1296static final int LDAP_TIME_LIMIT_EXCEEDED = 3;1297static final int LDAP_SIZE_LIMIT_EXCEEDED = 4;1298static final int LDAP_COMPARE_FALSE = 5;1299static final int LDAP_COMPARE_TRUE = 6;1300static final int LDAP_AUTH_METHOD_NOT_SUPPORTED = 7;1301static final int LDAP_STRONG_AUTH_REQUIRED = 8;1302static final int LDAP_PARTIAL_RESULTS = 9; // Slapd1303static final int LDAP_REFERRAL = 10; // LDAPv31304static final int LDAP_ADMIN_LIMIT_EXCEEDED = 11; // LDAPv31305static final int LDAP_UNAVAILABLE_CRITICAL_EXTENSION = 12; // LDAPv31306static final int LDAP_CONFIDENTIALITY_REQUIRED = 13; // LDAPv31307static final int LDAP_SASL_BIND_IN_PROGRESS = 14; // LDAPv31308static final int LDAP_NO_SUCH_ATTRIBUTE = 16;1309static final int LDAP_UNDEFINED_ATTRIBUTE_TYPE = 17;1310static final int LDAP_INAPPROPRIATE_MATCHING = 18;1311static final int LDAP_CONSTRAINT_VIOLATION = 19;1312static final int LDAP_ATTRIBUTE_OR_VALUE_EXISTS = 20;1313static final int LDAP_INVALID_ATTRIBUTE_SYNTAX = 21;1314static final int LDAP_NO_SUCH_OBJECT = 32;1315static final int LDAP_ALIAS_PROBLEM = 33;1316static final int LDAP_INVALID_DN_SYNTAX = 34;1317static final int LDAP_IS_LEAF = 35;1318static final int LDAP_ALIAS_DEREFERENCING_PROBLEM = 36;1319static final int LDAP_INAPPROPRIATE_AUTHENTICATION = 48;1320static final int LDAP_INVALID_CREDENTIALS = 49;1321static final int LDAP_INSUFFICIENT_ACCESS_RIGHTS = 50;1322static final int LDAP_BUSY = 51;1323static final int LDAP_UNAVAILABLE = 52;1324static final int LDAP_UNWILLING_TO_PERFORM = 53;1325static final int LDAP_LOOP_DETECT = 54;1326static final int LDAP_NAMING_VIOLATION = 64;1327static final int LDAP_OBJECT_CLASS_VIOLATION = 65;1328static final int LDAP_NOT_ALLOWED_ON_NON_LEAF = 66;1329static final int LDAP_NOT_ALLOWED_ON_RDN = 67;1330static final int LDAP_ENTRY_ALREADY_EXISTS = 68;1331static final int LDAP_OBJECT_CLASS_MODS_PROHIBITED = 69;1332static final int LDAP_AFFECTS_MULTIPLE_DSAS = 71; // LDAPv31333static final int LDAP_OTHER = 80;13341335static final String[] ldap_error_message = {1336"Success", // 01337"Operations Error", // 11338"Protocol Error", // 21339"Timelimit Exceeded", // 31340"Sizelimit Exceeded", // 41341"Compare False", // 51342"Compare True", // 61343"Authentication Method Not Supported", // 71344"Strong Authentication Required", // 81345null,1346"Referral", // 101347"Administrative Limit Exceeded", // 111348"Unavailable Critical Extension", // 121349"Confidentiality Required", // 131350"SASL Bind In Progress", // 141351null,1352"No Such Attribute", // 161353"Undefined Attribute Type", // 171354"Inappropriate Matching", // 181355"Constraint Violation", // 191356"Attribute Or Value Exists", // 201357"Invalid Attribute Syntax", // 211358null,1359null,1360null,1361null,1362null,1363null,1364null,1365null,1366null,1367null,1368"No Such Object", // 321369"Alias Problem", // 331370"Invalid DN Syntax", // 341371null,1372"Alias Dereferencing Problem", // 361373null,1374null,1375null,1376null,1377null,1378null,1379null,1380null,1381null,1382null,1383null,1384"Inappropriate Authentication", // 481385"Invalid Credentials", // 491386"Insufficient Access Rights", // 501387"Busy", // 511388"Unavailable", // 521389"Unwilling To Perform", // 531390"Loop Detect", // 541391null,1392null,1393null,1394null,1395null,1396null,1397null,1398null,1399null,1400"Naming Violation", // 641401"Object Class Violation", // 651402"Not Allowed On Non-leaf", // 661403"Not Allowed On RDN", // 671404"Entry Already Exists", // 681405"Object Class Modifications Prohibited", // 691406null,1407"Affects Multiple DSAs", // 711408null,1409null,1410null,1411null,1412null,1413null,1414null,1415null,1416"Other", // 801417null,1418null,1419null,1420null,1421null,1422null,1423null,1424null,1425null,1426null1427};142814291430/*1431* Generate an error message from the LDAP error code and error diagnostic.1432* The message format is:1433*1434* "[LDAP: error code <errorCode> - <errorMessage>]"1435*1436* where <errorCode> is a numeric error code1437* and <errorMessage> is a textual description of the error (if available)1438*1439*/1440static String getErrorMessage(int errorCode, String errorMessage) {14411442String message = "[LDAP: error code " + errorCode;14431444if ((errorMessage != null) && (errorMessage.length() != 0)) {14451446// append error message from the server1447message = message + " - " + errorMessage + "]";14481449} else {14501451// append built-in error message1452try {1453if (ldap_error_message[errorCode] != null) {1454message = message + " - " + ldap_error_message[errorCode] +1455"]";1456}1457} catch (ArrayIndexOutOfBoundsException ex) {1458message = message + "]";1459}1460}1461return message;1462}146314641465////////////////////////////////////////////////////////////////////////////1466//1467// Unsolicited notification support.1468//1469// An LdapClient maintains a list of LdapCtx that have registered1470// for UnsolicitedNotifications. This is a list because a single1471// LdapClient might be shared among multiple contexts.1472//1473// When addUnsolicited() is invoked, the LdapCtx is added to the list.1474//1475// When Connection receives an unsolicited notification (msgid == 0),1476// it invokes LdapClient.processUnsolicited(). processUnsolicited()1477// parses the Extended Response. If there are registered listeners,1478// LdapClient creates an UnsolicitedNotification from the response1479// and informs each LdapCtx to fire an event for the notification.1480// If it is a DISCONNECT notification, the connection is closed and a1481// NamingExceptionEvent is fired to the listeners.1482//1483// When the connection is closed out-of-band like this, the next1484// time a method is invoked on LdapClient, an IOException is thrown.1485//1486// removeUnsolicited() is invoked to remove an LdapCtx from this client.1487//1488////////////////////////////////////////////////////////////////////////////1489private Vector<LdapCtx> unsolicited = new Vector<>(3);1490void addUnsolicited(LdapCtx ctx) {1491if (debug > 0) {1492System.err.println("LdapClient.addUnsolicited" + ctx);1493}1494unsolicited.addElement(ctx);1495}14961497void removeUnsolicited(LdapCtx ctx) {1498if (debug > 0) {1499System.err.println("LdapClient.removeUnsolicited" + ctx);1500}1501unsolicited.removeElement(ctx);1502}15031504// NOTE: Cannot be synchronized because this is called asynchronously1505// by the reader thread in Connection. Instead, sync on 'unsolicited' Vector.1506void processUnsolicited(BerDecoder ber) {1507if (debug > 0) {1508System.err.println("LdapClient.processUnsolicited");1509}1510try {1511// Parse the response1512LdapResult res = new LdapResult();15131514ber.parseSeq(null); // init seq1515ber.parseInt(); // msg id; should be 0; ignored1516if (ber.parseByte() != LDAP_REP_EXTENSION) {1517throw new IOException(1518"Unsolicited Notification must be an Extended Response");1519}1520ber.parseLength();1521parseExtResponse(ber, res);15221523if (DISCONNECT_OID.equals(res.extensionId)) {1524// force closing of connection1525forceClose(pooled);1526}15271528LdapCtx first = null;1529UnsolicitedNotification notice = null;15301531synchronized (unsolicited) {1532if (unsolicited.size() > 0) {1533first = unsolicited.elementAt(0);15341535// Create an UnsolicitedNotification using the parsed data1536// Need a 'ctx' object because we want to use the context's1537// list of provider control factories.1538notice = new UnsolicitedResponseImpl(1539res.extensionId,1540res.extensionValue,1541res.referrals,1542res.status,1543res.errorMessage,1544res.matchedDN,1545(res.resControls != null) ?1546first.convertControls(res.resControls) :1547null);1548}1549}15501551if (notice != null) {1552// Fire UnsolicitedNotification events to listeners1553notifyUnsolicited(notice);15541555// If "disconnect" notification,1556// notify unsolicited listeners via NamingException1557if (DISCONNECT_OID.equals(res.extensionId)) {1558notifyUnsolicited(1559new CommunicationException("Connection closed"));1560}1561}1562} catch (IOException e) {1563NamingException ne = new CommunicationException(1564"Problem parsing unsolicited notification");1565ne.setRootCause(e);15661567notifyUnsolicited(ne);15681569} catch (NamingException e) {1570notifyUnsolicited(e);1571}1572}157315741575private void notifyUnsolicited(Object e) {1576Vector<LdapCtx> unsolicitedCopy;1577synchronized (unsolicited) {1578unsolicitedCopy = new Vector<>(unsolicited);1579if (e instanceof NamingException) {1580unsolicited.setSize(0); // no more listeners after exception1581}1582}1583for (int i = 0; i < unsolicitedCopy.size(); i++) {1584unsolicitedCopy.elementAt(i).fireUnsolicited(e);1585}1586}15871588private void ensureOpen() throws IOException {1589if (conn == null || !conn.useable) {1590if (conn != null && conn.closureReason != null) {1591throw conn.closureReason;1592} else {1593throw new IOException("connection closed");1594}1595}1596}15971598// package private (used by LdapCtx)1599static LdapClient getInstance(boolean usePool, String hostname, int port,1600String factory, int connectTimeout, int readTimeout, OutputStream trace,1601int version, String authMechanism, Control[] ctls, String protocol,1602String user, Object passwd, Hashtable<?,?> env) throws NamingException {16031604if (usePool) {1605if (LdapPoolManager.isPoolingAllowed(factory, trace,1606authMechanism, protocol, env)) {1607LdapClient answer = LdapPoolManager.getLdapClient(1608hostname, port, factory, connectTimeout, readTimeout,1609trace, version, authMechanism, ctls, protocol, user,1610passwd, env);1611answer.referenceCount = 1; // always one when starting out1612return answer;1613}1614}1615return new LdapClient(hostname, port, factory, connectTimeout,1616readTimeout, trace, null);1617}1618}161916201621