Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/com/sun/jndi/ldap/LdapClient.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 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 StartTLS119120final private PoolCallback pcb;121final private 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// Pooled448449// Is this a real close or a request to return conn to pool450if (hardClose) {451conn.cleanup(reqCtls, false);452pcb.removePooledConnection(this);453} else {454pcb.releasePooledConnection(this);455}456}457}458}459460// NOTE: Should NOT be synchronized otherwise won't be able to close461private void forceClose(boolean cleanPool) {462referenceCount = 0; // force closing of connection463464if (debug > 1) {465System.err.println("LdapClient: forceClose() of " + this);466}467468if (debug > 0) System.err.println(469"LdapClient: forced close of connection " + this);470conn.cleanup(null, false);471if (cleanPool) {472pcb.removePooledConnection(this);473}474}475476protected void finalize() {477if (debug > 0) System.err.println("LdapClient: finalize " + this);478forceClose(pooled);479}480481/*482* Used by connection pooling to close physical connection.483*/484synchronized public void closeConnection() {485forceClose(false); // this is a pool callback so no need to clean pool486}487488/**489* Called by Connection.cleanup(). LdapClient should490* notify any unsolicited listeners and removing itself from any pool.491* This is almost like forceClose(), except it doesn't call492* Connection.cleanup() (because this is called from cleanup()).493*/494void processConnectionClosure() {495// Notify listeners496if (unsolicited.size() > 0) {497String msg;498if (conn != null) {499msg = conn.host + ":" + conn.port + " connection closed";500} else {501msg = "Connection closed";502}503notifyUnsolicited(new CommunicationException(msg));504}505506// Remove from pool507if (pooled) {508pcb.removePooledConnection(this);509}510}511512////////////////////////////////////////////////////////////////////////////513//514// LDAP search. also includes methods to encode rfc 1558 compliant filters515//516////////////////////////////////////////////////////////////////////////////517518static final int SCOPE_BASE_OBJECT = 0;519static final int SCOPE_ONE_LEVEL = 1;520static final int SCOPE_SUBTREE = 2;521522LdapResult search(String dn, int scope, int deref, int sizeLimit,523int timeLimit, boolean attrsOnly, String attrs[],524String filter, int batchSize, Control[] reqCtls,525Hashtable<String, Boolean> binaryAttrs,526boolean waitFirstReply, int replyQueueCapacity)527throws IOException, NamingException {528529ensureOpen();530531LdapResult res = new LdapResult();532533BerEncoder ber = new BerEncoder();534int curMsgId = conn.getMsgId();535536ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);537ber.encodeInt(curMsgId);538ber.beginSeq(LDAP_REQ_SEARCH);539ber.encodeString(dn == null ? "" : dn, isLdapv3);540ber.encodeInt(scope, LBER_ENUMERATED);541ber.encodeInt(deref, LBER_ENUMERATED);542ber.encodeInt(sizeLimit);543ber.encodeInt(timeLimit);544ber.encodeBoolean(attrsOnly);545Filter.encodeFilterString(ber, filter, isLdapv3);546ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);547ber.encodeStringArray(attrs, isLdapv3);548ber.endSeq();549ber.endSeq();550if (isLdapv3) encodeControls(ber, reqCtls);551ber.endSeq();552553LdapRequest req =554conn.writeRequest(ber, curMsgId, false, replyQueueCapacity);555556res.msgId = curMsgId;557res.status = LdapClient.LDAP_SUCCESS; //optimistic558if (waitFirstReply) {559// get first reply560res = getSearchReply(req, batchSize, res, binaryAttrs);561}562return res;563}564565/*566* Abandon the search operation and remove it from the message queue.567*/568void clearSearchReply(LdapResult res, Control[] ctls) {569if (res != null) {570571// Only send an LDAP abandon operation when clearing the search572// reply from a one-level or subtree search.573LdapRequest req = conn.findRequest(res.msgId);574if (req == null) {575return;576}577578// OK if req got removed after check; double removal attempt579// but otherwise no harm done580581// Send an LDAP abandon only if the search operation has not yet582// completed.583if (req.hasSearchCompleted()) {584conn.removeRequest(req);585} else {586conn.abandonRequest(req, ctls);587}588}589}590591/*592* Retrieve the next batch of entries and/or referrals.593*/594LdapResult getSearchReply(int batchSize, LdapResult res,595Hashtable<String, Boolean> binaryAttrs) throws IOException, NamingException {596597ensureOpen();598599LdapRequest req;600601if ((req = conn.findRequest(res.msgId)) == null) {602return null;603}604605return getSearchReply(req, batchSize, res, binaryAttrs);606}607608private LdapResult getSearchReply(LdapRequest req,609int batchSize, LdapResult res, Hashtable<String, Boolean> binaryAttrs)610throws IOException, NamingException {611612if (batchSize == 0)613batchSize = Integer.MAX_VALUE;614615if (res.entries != null) {616res.entries.setSize(0); // clear the (previous) set of entries617} else {618res.entries =619new Vector<>(batchSize == Integer.MAX_VALUE ? 32 : batchSize);620}621622if (res.referrals != null) {623res.referrals.setSize(0); // clear the (previous) set of referrals624}625626BerDecoder replyBer; // Decoder for response627int seq; // Request id628629Attributes lattrs; // Attribute set read from response630Attribute la; // Attribute read from response631String DN; // DN read from response632LdapEntry le; // LDAP entry representing response633int[] seqlen; // Holder for response length634int endseq; // Position of end of response635636for (int i = 0; i < batchSize;) {637replyBer = conn.readReply(req);638639//640// process search reply641//642replyBer.parseSeq(null); // init seq643replyBer.parseInt(); // req id644seq = replyBer.parseSeq(null);645646if (seq == LDAP_REP_SEARCH) {647648// handle LDAPv3 search entries649lattrs = new BasicAttributes(caseIgnore);650DN = replyBer.parseString(isLdapv3);651le = new LdapEntry(DN, lattrs);652seqlen = new int[1];653654replyBer.parseSeq(seqlen);655endseq = replyBer.getParsePosition() + seqlen[0];656while ((replyBer.getParsePosition() < endseq) &&657(replyBer.bytesLeft() > 0)) {658la = parseAttribute(replyBer, binaryAttrs);659lattrs.put(la);660}661le.respCtls = isLdapv3 ? parseControls(replyBer) : null;662663res.entries.addElement(le);664i++;665666} else if ((seq == LDAP_REP_SEARCH_REF) && isLdapv3) {667668// handle LDAPv3 search reference669Vector<String> URLs = new Vector<>(4);670671// %%% Although not strictly correct, some LDAP servers672// encode the SEQUENCE OF tag in the SearchResultRef673if (replyBer.peekByte() ==674(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR)) {675replyBer.parseSeq(null);676}677678while ((replyBer.bytesLeft() > 0) &&679(replyBer.peekByte() == Ber.ASN_OCTET_STR)) {680681URLs.addElement(replyBer.parseString(isLdapv3));682}683684if (res.referrals == null) {685res.referrals = new Vector<>(4);686}687res.referrals.addElement(URLs);688res.resControls = isLdapv3 ? parseControls(replyBer) : null;689690// Save referral and continue to get next search result691692} else if (seq == LDAP_REP_EXTENSION) {693694parseExtResponse(replyBer, res); //%%% ignore for now695696} else if (seq == LDAP_REP_RESULT) {697698parseResult(replyBer, res, isLdapv3);699res.resControls = isLdapv3 ? parseControls(replyBer) : null;700701conn.removeRequest(req);702return res; // Done with search703}704}705706return res;707}708709private Attribute parseAttribute(BerDecoder ber,710Hashtable<String, Boolean> binaryAttrs)711throws IOException {712713int len[] = new int[1];714int seq = ber.parseSeq(null);715String attrid = ber.parseString(isLdapv3);716boolean hasBinaryValues = isBinaryValued(attrid, binaryAttrs);717Attribute la = new LdapAttribute(attrid);718719if ((seq = ber.parseSeq(len)) == LBER_SET) {720int attrlen = len[0];721while (ber.bytesLeft() > 0 && attrlen > 0) {722try {723attrlen -= parseAttributeValue(ber, la, hasBinaryValues);724} catch (IOException ex) {725ber.seek(attrlen);726break;727}728}729} else {730// Skip the rest of the sequence because it is not what we want731ber.seek(len[0]);732}733return la;734}735736//737// returns number of bytes that were parsed. Adds the values to attr738//739private int parseAttributeValue(BerDecoder ber, Attribute la,740boolean hasBinaryValues) throws IOException {741742int len[] = new int[1];743744if (hasBinaryValues) {745la.add(ber.parseOctetString(ber.peekByte(), len));746} else {747la.add(ber.parseStringWithTag(748Ber.ASN_SIMPLE_STRING, isLdapv3, len));749}750return len[0];751}752753private boolean isBinaryValued(String attrid,754Hashtable<String, Boolean> binaryAttrs) {755String id = attrid.toLowerCase(Locale.ENGLISH);756757return ((id.indexOf(";binary") != -1) ||758defaultBinaryAttrs.containsKey(id) ||759((binaryAttrs != null) && (binaryAttrs.containsKey(id))));760}761762// package entry point; used by Connection763static void parseResult(BerDecoder replyBer, LdapResult res,764boolean isLdapv3) throws IOException {765766res.status = replyBer.parseEnumeration();767res.matchedDN = replyBer.parseString(isLdapv3);768res.errorMessage = replyBer.parseString(isLdapv3);769770// handle LDAPv3 referrals (if present)771if (isLdapv3 &&772(replyBer.bytesLeft() > 0) &&773(replyBer.peekByte() == LDAP_REP_REFERRAL)) {774775Vector<String> URLs = new Vector<>(4);776int[] seqlen = new int[1];777778replyBer.parseSeq(seqlen);779int endseq = replyBer.getParsePosition() + seqlen[0];780while ((replyBer.getParsePosition() < endseq) &&781(replyBer.bytesLeft() > 0)) {782783URLs.addElement(replyBer.parseString(isLdapv3));784}785786if (res.referrals == null) {787res.referrals = new Vector<>(4);788}789res.referrals.addElement(URLs);790}791}792793// package entry point; used by Connection794static Vector<Control> parseControls(BerDecoder replyBer) throws IOException {795796// handle LDAPv3 controls (if present)797if ((replyBer.bytesLeft() > 0) && (replyBer.peekByte() == LDAP_CONTROLS)) {798Vector<Control> ctls = new Vector<>(4);799String controlOID;800boolean criticality = false; // default801byte[] controlValue = null; // optional802int[] seqlen = new int[1];803804replyBer.parseSeq(seqlen);805int endseq = replyBer.getParsePosition() + seqlen[0];806while ((replyBer.getParsePosition() < endseq) &&807(replyBer.bytesLeft() > 0)) {808809replyBer.parseSeq(null);810controlOID = replyBer.parseString(true);811812if ((replyBer.bytesLeft() > 0) &&813(replyBer.peekByte() == Ber.ASN_BOOLEAN)) {814criticality = replyBer.parseBoolean();815}816if ((replyBer.bytesLeft() > 0) &&817(replyBer.peekByte() == Ber.ASN_OCTET_STR)) {818controlValue =819replyBer.parseOctetString(Ber.ASN_OCTET_STR, null);820}821if (controlOID != null) {822ctls.addElement(823new BasicControl(controlOID, criticality, controlValue));824}825}826return ctls;827} else {828return null;829}830}831832private void parseExtResponse(BerDecoder replyBer, LdapResult res)833throws IOException {834835parseResult(replyBer, res, isLdapv3);836837if ((replyBer.bytesLeft() > 0) &&838(replyBer.peekByte() == LDAP_REP_EXT_OID)) {839res.extensionId =840replyBer.parseStringWithTag(LDAP_REP_EXT_OID, isLdapv3, null);841}842if ((replyBer.bytesLeft() > 0) &&843(replyBer.peekByte() == LDAP_REP_EXT_VAL)) {844res.extensionValue =845replyBer.parseOctetString(LDAP_REP_EXT_VAL, null);846}847848res.resControls = parseControls(replyBer);849}850851//852// Encode LDAPv3 controls853//854static void encodeControls(BerEncoder ber, Control[] reqCtls)855throws IOException {856857if ((reqCtls == null) || (reqCtls.length == 0)) {858return;859}860861byte[] controlVal;862863ber.beginSeq(LdapClient.LDAP_CONTROLS);864865for (int i = 0; i < reqCtls.length; i++) {866ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);867ber.encodeString(reqCtls[i].getID(), true); // control OID868if (reqCtls[i].isCritical()) {869ber.encodeBoolean(true); // critical control870}871if ((controlVal = reqCtls[i].getEncodedValue()) != null) {872ber.encodeOctetString(controlVal, Ber.ASN_OCTET_STR);873}874ber.endSeq();875}876ber.endSeq();877}878879/**880* Reads the next reply corresponding to msgId, outstanding on requestBer.881* Processes the result and any controls.882*/883private LdapResult processReply(LdapRequest req,884LdapResult res, int responseType) throws IOException, NamingException {885886BerDecoder rber = conn.readReply(req);887888rber.parseSeq(null); // init seq889rber.parseInt(); // msg id890if (rber.parseByte() != responseType) {891return res;892}893894rber.parseLength();895parseResult(rber, res, isLdapv3);896res.resControls = isLdapv3 ? parseControls(rber) : null;897898conn.removeRequest(req);899900return res; // Done with operation901}902903////////////////////////////////////////////////////////////////////////////904//905// LDAP modify:906// Modify the DN dn with the operations on attributes attrs.907// ie, operations[0] is the operation to be performed on908// attrs[0];909// dn - DN to modify910// operations - add, delete or replace911// attrs - array of Attribute912// reqCtls - array of request controls913//914////////////////////////////////////////////////////////////////////////////915916static final int ADD = 0;917static final int DELETE = 1;918static final int REPLACE = 2;919920LdapResult modify(String dn, int operations[], Attribute attrs[],921Control[] reqCtls)922throws IOException, NamingException {923924ensureOpen();925926LdapResult res = new LdapResult();927res.status = LDAP_OPERATIONS_ERROR;928929if (dn == null || operations.length != attrs.length)930return res;931932BerEncoder ber = new BerEncoder();933int curMsgId = conn.getMsgId();934935ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);936ber.encodeInt(curMsgId);937ber.beginSeq(LDAP_REQ_MODIFY);938ber.encodeString(dn, isLdapv3);939ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);940for (int i = 0; i < operations.length; i++) {941ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);942ber.encodeInt(operations[i], LBER_ENUMERATED);943944// zero values is not permitted for the add op.945if ((operations[i] == ADD) && hasNoValue(attrs[i])) {946throw new InvalidAttributeValueException(947"'" + attrs[i].getID() + "' has no values.");948} else {949encodeAttribute(ber, attrs[i]);950}951ber.endSeq();952}953ber.endSeq();954ber.endSeq();955if (isLdapv3) encodeControls(ber, reqCtls);956ber.endSeq();957958LdapRequest req = conn.writeRequest(ber, curMsgId);959960return processReply(req, res, LDAP_REP_MODIFY);961}962963private void encodeAttribute(BerEncoder ber, Attribute attr)964throws IOException, NamingException {965966ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);967ber.encodeString(attr.getID(), isLdapv3);968ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR | 1);969NamingEnumeration<?> enum_ = attr.getAll();970Object val;971while (enum_.hasMore()) {972val = enum_.next();973if (val instanceof String) {974ber.encodeString((String)val, isLdapv3);975} else if (val instanceof byte[]) {976ber.encodeOctetString((byte[])val, Ber.ASN_OCTET_STR);977} else if (val == null) {978// no attribute value979} else {980throw new InvalidAttributeValueException(981"Malformed '" + attr.getID() + "' attribute value");982}983}984ber.endSeq();985ber.endSeq();986}987988private static boolean hasNoValue(Attribute attr) throws NamingException {989return attr.size() == 0 || (attr.size() == 1 && attr.get() == null);990}991992////////////////////////////////////////////////////////////////////////////993//994// LDAP add995// Adds entry to the Directory996//997////////////////////////////////////////////////////////////////////////////998999LdapResult add(LdapEntry entry, Control[] reqCtls)1000throws IOException, NamingException {10011002ensureOpen();10031004LdapResult res = new LdapResult();1005res.status = LDAP_OPERATIONS_ERROR;10061007if (entry == null || entry.DN == null)1008return res;10091010BerEncoder ber = new BerEncoder();1011int curMsgId = conn.getMsgId();1012Attribute attr;10131014ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1015ber.encodeInt(curMsgId);1016ber.beginSeq(LDAP_REQ_ADD);1017ber.encodeString(entry.DN, isLdapv3);1018ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1019NamingEnumeration<? extends Attribute> enum_ =1020entry.attributes.getAll();1021while (enum_.hasMore()) {1022attr = enum_.next();10231024// zero values is not permitted1025if (hasNoValue(attr)) {1026throw new InvalidAttributeValueException(1027"'" + attr.getID() + "' has no values.");1028} else {1029encodeAttribute(ber, attr);1030}1031}1032ber.endSeq();1033ber.endSeq();1034if (isLdapv3) encodeControls(ber, reqCtls);1035ber.endSeq();10361037LdapRequest req = conn.writeRequest(ber, curMsgId);1038return processReply(req, res, LDAP_REP_ADD);1039}10401041////////////////////////////////////////////////////////////////////////////1042//1043// LDAP delete1044// deletes entry from the Directory1045//1046////////////////////////////////////////////////////////////////////////////10471048LdapResult delete(String DN, Control[] reqCtls)1049throws IOException, NamingException {10501051ensureOpen();10521053LdapResult res = new LdapResult();1054res.status = LDAP_OPERATIONS_ERROR;10551056if (DN == null)1057return res;10581059BerEncoder ber = new BerEncoder();1060int curMsgId = conn.getMsgId();10611062ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1063ber.encodeInt(curMsgId);1064ber.encodeString(DN, LDAP_REQ_DELETE, isLdapv3);1065if (isLdapv3) encodeControls(ber, reqCtls);1066ber.endSeq();10671068LdapRequest req = conn.writeRequest(ber, curMsgId);10691070return processReply(req, res, LDAP_REP_DELETE);1071}10721073////////////////////////////////////////////////////////////////////////////1074//1075// LDAP modrdn1076// Changes the last element of DN to newrdn1077// dn - DN to change1078// newrdn - new RDN to rename to1079// deleteoldrdn - boolean whether to delete old attrs or not1080// newSuperior - new place to put the entry in the tree1081// (ignored if server is LDAPv2)1082// reqCtls - array of request controls1083//1084////////////////////////////////////////////////////////////////////////////10851086LdapResult moddn(String DN, String newrdn, boolean deleteOldRdn,1087String newSuperior, Control[] reqCtls)1088throws IOException, NamingException {10891090ensureOpen();10911092boolean changeSuperior = (newSuperior != null &&1093newSuperior.length() > 0);10941095LdapResult res = new LdapResult();1096res.status = LDAP_OPERATIONS_ERROR;10971098if (DN == null || newrdn == null)1099return res;11001101BerEncoder ber = new BerEncoder();1102int curMsgId = conn.getMsgId();11031104ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1105ber.encodeInt(curMsgId);1106ber.beginSeq(LDAP_REQ_MODRDN);1107ber.encodeString(DN, isLdapv3);1108ber.encodeString(newrdn, isLdapv3);1109ber.encodeBoolean(deleteOldRdn);1110if(isLdapv3 && changeSuperior) {1111//System.err.println("changin superior");1112ber.encodeString(newSuperior, LDAP_SUPERIOR_DN, isLdapv3);1113}1114ber.endSeq();1115if (isLdapv3) encodeControls(ber, reqCtls);1116ber.endSeq();111711181119LdapRequest req = conn.writeRequest(ber, curMsgId);11201121return processReply(req, res, LDAP_REP_MODRDN);1122}11231124////////////////////////////////////////////////////////////////////////////1125//1126// LDAP compare1127// Compare attribute->value pairs in dn1128//1129////////////////////////////////////////////////////////////////////////////11301131LdapResult compare(String DN, String type, String value, Control[] reqCtls)1132throws IOException, NamingException {11331134ensureOpen();11351136LdapResult res = new LdapResult();1137res.status = LDAP_OPERATIONS_ERROR;11381139if (DN == null || type == null || value == null)1140return res;11411142BerEncoder ber = new BerEncoder();1143int curMsgId = conn.getMsgId();11441145ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1146ber.encodeInt(curMsgId);1147ber.beginSeq(LDAP_REQ_COMPARE);1148ber.encodeString(DN, isLdapv3);1149ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1150ber.encodeString(type, isLdapv3);11511152// replace any escaped characters in the value1153byte[] val = isLdapv3 ?1154value.getBytes("UTF8") : value.getBytes("8859_1");1155ber.encodeOctetString(1156Filter.unescapeFilterValue(val, 0, val.length),1157Ber.ASN_OCTET_STR);11581159ber.endSeq();1160ber.endSeq();1161if (isLdapv3) encodeControls(ber, reqCtls);1162ber.endSeq();11631164LdapRequest req = conn.writeRequest(ber, curMsgId);11651166return processReply(req, res, LDAP_REP_COMPARE);1167}11681169////////////////////////////////////////////////////////////////////////////1170//1171// LDAP extended operation1172//1173////////////////////////////////////////////////////////////////////////////11741175LdapResult extendedOp(String id, byte[] request, Control[] reqCtls,1176boolean pauseAfterReceipt) throws IOException, NamingException {11771178ensureOpen();11791180LdapResult res = new LdapResult();1181res.status = LDAP_OPERATIONS_ERROR;11821183if (id == null)1184return res;11851186BerEncoder ber = new BerEncoder();1187int curMsgId = conn.getMsgId();11881189ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);1190ber.encodeInt(curMsgId);1191ber.beginSeq(LDAP_REQ_EXTENSION);1192ber.encodeString(id,1193Ber.ASN_CONTEXT | 0, isLdapv3);//[0]1194if (request != null) {1195ber.encodeOctetString(request,1196Ber.ASN_CONTEXT | 1);//[1]1197}1198ber.endSeq();1199encodeControls(ber, reqCtls); // always v31200ber.endSeq();12011202LdapRequest req = conn.writeRequest(ber, curMsgId, pauseAfterReceipt);12031204BerDecoder rber = conn.readReply(req);12051206rber.parseSeq(null); // init seq1207rber.parseInt(); // msg id1208if (rber.parseByte() != LDAP_REP_EXTENSION) {1209return res;1210}12111212rber.parseLength();1213parseExtResponse(rber, res);1214conn.removeRequest(req);12151216return res; // Done with operation1217}1218121912201221////////////////////////////////////////////////////////////////////////////1222//1223// Some BER definitions convenient for LDAP1224//1225////////////////////////////////////////////////////////////////////////////12261227static final int LDAP_VERSION3_VERSION2 = 32;1228static final int LDAP_VERSION2 = 0x02;1229static final int LDAP_VERSION3 = 0x03; // LDAPv31230static final int LDAP_VERSION = LDAP_VERSION3;12311232static final int LDAP_REF_FOLLOW = 0x01; // follow referrals1233static final int LDAP_REF_THROW = 0x02; // throw referral ex.1234static final int LDAP_REF_IGNORE = 0x03; // ignore referrals1235static final int LDAP_REF_FOLLOW_SCHEME = 0x04; // follow referrals of the same scheme12361237static final String LDAP_URL = "ldap://"; // LDAPv31238static final String LDAPS_URL = "ldaps://"; // LDAPv312391240static final int LBER_BOOLEAN = 0x01;1241static final int LBER_INTEGER = 0x02;1242static final int LBER_BITSTRING = 0x03;1243static final int LBER_OCTETSTRING = 0x04;1244static final int LBER_NULL = 0x05;1245static final int LBER_ENUMERATED = 0x0a;1246static final int LBER_SEQUENCE = 0x30;1247static final int LBER_SET = 0x31;12481249static final int LDAP_SUPERIOR_DN = 0x80;12501251static final int LDAP_REQ_BIND = 0x60; // app + constructed1252static final int LDAP_REQ_UNBIND = 0x42; // app + primitive1253static final int LDAP_REQ_SEARCH = 0x63; // app + constructed1254static final int LDAP_REQ_MODIFY = 0x66; // app + constructed1255static final int LDAP_REQ_ADD = 0x68; // app + constructed1256static final int LDAP_REQ_DELETE = 0x4a; // app + primitive1257static final int LDAP_REQ_MODRDN = 0x6c; // app + constructed1258static final int LDAP_REQ_COMPARE = 0x6e; // app + constructed1259static final int LDAP_REQ_ABANDON = 0x50; // app + primitive1260static final int LDAP_REQ_EXTENSION = 0x77; // app + constructed (LDAPv3)12611262static final int LDAP_REP_BIND = 0x61; // app + constructed | 11263static final int LDAP_REP_SEARCH = 0x64; // app + constructed | 41264static final int LDAP_REP_SEARCH_REF = 0x73;// app + constructed (LDAPv3)1265static final int LDAP_REP_RESULT = 0x65; // app + constructed | 51266static final int LDAP_REP_MODIFY = 0x67; // app + constructed | 71267static final int LDAP_REP_ADD = 0x69; // app + constructed | 91268static final int LDAP_REP_DELETE = 0x6b; // app + primitive | b1269static final int LDAP_REP_MODRDN = 0x6d; // app + primitive | d1270static final int LDAP_REP_COMPARE = 0x6f; // app + primitive | f1271static final int LDAP_REP_EXTENSION = 0x78; // app + constructed (LDAPv3)12721273static final int LDAP_REP_REFERRAL = 0xa3; // ctx + constructed (LDAPv3)1274static final int LDAP_REP_EXT_OID = 0x8a; // ctx + primitive (LDAPv3)1275static final int LDAP_REP_EXT_VAL = 0x8b; // ctx + primitive (LDAPv3)12761277// LDAPv3 Controls12781279static final int LDAP_CONTROLS = 0xa0; // ctx + constructed (LDAPv3)1280static final String LDAP_CONTROL_MANAGE_DSA_IT = "2.16.840.1.113730.3.4.2";1281static final String LDAP_CONTROL_PREFERRED_LANG = "1.3.6.1.4.1.1466.20035";1282static final String LDAP_CONTROL_PAGED_RESULTS = "1.2.840.113556.1.4.319";1283static final String LDAP_CONTROL_SERVER_SORT_REQ = "1.2.840.113556.1.4.473";1284static final String LDAP_CONTROL_SERVER_SORT_RES = "1.2.840.113556.1.4.474";12851286////////////////////////////////////////////////////////////////////////////1287//1288// return codes1289//1290////////////////////////////////////////////////////////////////////////////12911292static final int LDAP_SUCCESS = 0;1293static final int LDAP_OPERATIONS_ERROR = 1;1294static final int LDAP_PROTOCOL_ERROR = 2;1295static final int LDAP_TIME_LIMIT_EXCEEDED = 3;1296static final int LDAP_SIZE_LIMIT_EXCEEDED = 4;1297static final int LDAP_COMPARE_FALSE = 5;1298static final int LDAP_COMPARE_TRUE = 6;1299static final int LDAP_AUTH_METHOD_NOT_SUPPORTED = 7;1300static final int LDAP_STRONG_AUTH_REQUIRED = 8;1301static final int LDAP_PARTIAL_RESULTS = 9; // Slapd1302static final int LDAP_REFERRAL = 10; // LDAPv31303static final int LDAP_ADMIN_LIMIT_EXCEEDED = 11; // LDAPv31304static final int LDAP_UNAVAILABLE_CRITICAL_EXTENSION = 12; // LDAPv31305static final int LDAP_CONFIDENTIALITY_REQUIRED = 13; // LDAPv31306static final int LDAP_SASL_BIND_IN_PROGRESS = 14; // LDAPv31307static final int LDAP_NO_SUCH_ATTRIBUTE = 16;1308static final int LDAP_UNDEFINED_ATTRIBUTE_TYPE = 17;1309static final int LDAP_INAPPROPRIATE_MATCHING = 18;1310static final int LDAP_CONSTRAINT_VIOLATION = 19;1311static final int LDAP_ATTRIBUTE_OR_VALUE_EXISTS = 20;1312static final int LDAP_INVALID_ATTRIBUTE_SYNTAX = 21;1313static final int LDAP_NO_SUCH_OBJECT = 32;1314static final int LDAP_ALIAS_PROBLEM = 33;1315static final int LDAP_INVALID_DN_SYNTAX = 34;1316static final int LDAP_IS_LEAF = 35;1317static final int LDAP_ALIAS_DEREFERENCING_PROBLEM = 36;1318static final int LDAP_INAPPROPRIATE_AUTHENTICATION = 48;1319static final int LDAP_INVALID_CREDENTIALS = 49;1320static final int LDAP_INSUFFICIENT_ACCESS_RIGHTS = 50;1321static final int LDAP_BUSY = 51;1322static final int LDAP_UNAVAILABLE = 52;1323static final int LDAP_UNWILLING_TO_PERFORM = 53;1324static final int LDAP_LOOP_DETECT = 54;1325static final int LDAP_NAMING_VIOLATION = 64;1326static final int LDAP_OBJECT_CLASS_VIOLATION = 65;1327static final int LDAP_NOT_ALLOWED_ON_NON_LEAF = 66;1328static final int LDAP_NOT_ALLOWED_ON_RDN = 67;1329static final int LDAP_ENTRY_ALREADY_EXISTS = 68;1330static final int LDAP_OBJECT_CLASS_MODS_PROHIBITED = 69;1331static final int LDAP_AFFECTS_MULTIPLE_DSAS = 71; // LDAPv31332static final int LDAP_OTHER = 80;13331334static final String[] ldap_error_message = {1335"Success", // 01336"Operations Error", // 11337"Protocol Error", // 21338"Timelimit Exceeded", // 31339"Sizelimit Exceeded", // 41340"Compare False", // 51341"Compare True", // 61342"Authentication Method Not Supported", // 71343"Strong Authentication Required", // 81344null,1345"Referral", // 101346"Administrative Limit Exceeded", // 111347"Unavailable Critical Extension", // 121348"Confidentiality Required", // 131349"SASL Bind In Progress", // 141350null,1351"No Such Attribute", // 161352"Undefined Attribute Type", // 171353"Inappropriate Matching", // 181354"Constraint Violation", // 191355"Attribute Or Value Exists", // 201356"Invalid Attribute Syntax", // 211357null,1358null,1359null,1360null,1361null,1362null,1363null,1364null,1365null,1366null,1367"No Such Object", // 321368"Alias Problem", // 331369"Invalid DN Syntax", // 341370null,1371"Alias Dereferencing Problem", // 361372null,1373null,1374null,1375null,1376null,1377null,1378null,1379null,1380null,1381null,1382null,1383"Inappropriate Authentication", // 481384"Invalid Credentials", // 491385"Insufficient Access Rights", // 501386"Busy", // 511387"Unavailable", // 521388"Unwilling To Perform", // 531389"Loop Detect", // 541390null,1391null,1392null,1393null,1394null,1395null,1396null,1397null,1398null,1399"Naming Violation", // 641400"Object Class Violation", // 651401"Not Allowed On Non-leaf", // 661402"Not Allowed On RDN", // 671403"Entry Already Exists", // 681404"Object Class Modifications Prohibited", // 691405null,1406"Affects Multiple DSAs", // 711407null,1408null,1409null,1410null,1411null,1412null,1413null,1414null,1415"Other", // 801416null,1417null,1418null,1419null,1420null,1421null,1422null,1423null,1424null,1425null1426};142714281429/*1430* Generate an error message from the LDAP error code and error diagnostic.1431* The message format is:1432*1433* "[LDAP: error code <errorCode> - <errorMessage>]"1434*1435* where <errorCode> is a numeric error code1436* and <errorMessage> is a textual description of the error (if available)1437*1438*/1439static String getErrorMessage(int errorCode, String errorMessage) {14401441String message = "[LDAP: error code " + errorCode;14421443if ((errorMessage != null) && (errorMessage.length() != 0)) {14441445// append error message from the server1446message = message + " - " + errorMessage + "]";14471448} else {14491450// append built-in error message1451try {1452if (ldap_error_message[errorCode] != null) {1453message = message + " - " + ldap_error_message[errorCode] +1454"]";1455}1456} catch (ArrayIndexOutOfBoundsException ex) {1457message = message + "]";1458}1459}1460return message;1461}146214631464////////////////////////////////////////////////////////////////////////////1465//1466// Unsolicited notification support.1467//1468// An LdapClient maintains a list of LdapCtx that have registered1469// for UnsolicitedNotifications. This is a list because a single1470// LdapClient might be shared among multiple contexts.1471//1472// When addUnsolicited() is invoked, the LdapCtx is added to the list.1473//1474// When Connection receives an unsolicited notification (msgid == 0),1475// it invokes LdapClient.processUnsolicited(). processUnsolicited()1476// parses the Extended Response. If there are registered listeners,1477// LdapClient creates an UnsolicitedNotification from the response1478// and informs each LdapCtx to fire an event for the notification.1479// If it is a DISCONNECT notification, the connection is closed and a1480// NamingExceptionEvent is fired to the listeners.1481//1482// When the connection is closed out-of-band like this, the next1483// time a method is invoked on LdapClient, an IOException is thrown.1484//1485// removeUnsolicited() is invoked to remove an LdapCtx from this client.1486//1487////////////////////////////////////////////////////////////////////////////1488private Vector<LdapCtx> unsolicited = new Vector<>(3);1489void addUnsolicited(LdapCtx ctx) {1490if (debug > 0) {1491System.err.println("LdapClient.addUnsolicited" + ctx);1492}1493unsolicited.addElement(ctx);1494}14951496void removeUnsolicited(LdapCtx ctx) {1497if (debug > 0) {1498System.err.println("LdapClient.removeUnsolicited" + ctx);1499}1500unsolicited.removeElement(ctx);1501}15021503// NOTE: Cannot be synchronized because this is called asynchronously1504// by the reader thread in Connection. Instead, sync on 'unsolicited' Vector.1505void processUnsolicited(BerDecoder ber) {1506if (debug > 0) {1507System.err.println("LdapClient.processUnsolicited");1508}1509try {1510// Parse the response1511LdapResult res = new LdapResult();15121513ber.parseSeq(null); // init seq1514ber.parseInt(); // msg id; should be 0; ignored1515if (ber.parseByte() != LDAP_REP_EXTENSION) {1516throw new IOException(1517"Unsolicited Notification must be an Extended Response");1518}1519ber.parseLength();1520parseExtResponse(ber, res);15211522if (DISCONNECT_OID.equals(res.extensionId)) {1523// force closing of connection1524forceClose(pooled);1525}15261527LdapCtx first = null;1528UnsolicitedNotification notice = null;15291530synchronized (unsolicited) {1531if (unsolicited.size() > 0) {1532first = unsolicited.elementAt(0);15331534// Create an UnsolicitedNotification using the parsed data1535// Need a 'ctx' object because we want to use the context's1536// list of provider control factories.1537notice = new UnsolicitedResponseImpl(1538res.extensionId,1539res.extensionValue,1540res.referrals,1541res.status,1542res.errorMessage,1543res.matchedDN,1544(res.resControls != null) ?1545first.convertControls(res.resControls) :1546null);1547}1548}15491550if (notice != null) {1551// Fire UnsolicitedNotification events to listeners1552notifyUnsolicited(notice);15531554// If "disconnect" notification,1555// notify unsolicited listeners via NamingException1556if (DISCONNECT_OID.equals(res.extensionId)) {1557notifyUnsolicited(1558new CommunicationException("Connection closed"));1559}1560}1561} catch (IOException e) {1562NamingException ne = new CommunicationException(1563"Problem parsing unsolicited notification");1564ne.setRootCause(e);15651566notifyUnsolicited(ne);15671568} catch (NamingException e) {1569notifyUnsolicited(e);1570}1571}157215731574private void notifyUnsolicited(Object e) {1575Vector<LdapCtx> unsolicitedCopy;1576synchronized (unsolicited) {1577unsolicitedCopy = new Vector<>(unsolicited);1578if (e instanceof NamingException) {1579unsolicited.setSize(0); // no more listeners after exception1580}1581}1582for (int i = 0; i < unsolicitedCopy.size(); i++) {1583unsolicitedCopy.elementAt(i).fireUnsolicited(e);1584}1585}15861587private void ensureOpen() throws IOException {1588if (conn == null || !conn.useable) {1589if (conn != null && conn.closureReason != null) {1590throw conn.closureReason;1591} else {1592throw new IOException("connection closed");1593}1594}1595}15961597// package private (used by LdapCtx)1598static LdapClient getInstance(boolean usePool, String hostname, int port,1599String factory, int connectTimeout, int readTimeout, OutputStream trace,1600int version, String authMechanism, Control[] ctls, String protocol,1601String user, Object passwd, Hashtable<?,?> env) throws NamingException {16021603if (usePool) {1604if (LdapPoolManager.isPoolingAllowed(factory, trace,1605authMechanism, protocol, env)) {1606LdapClient answer = LdapPoolManager.getLdapClient(1607hostname, port, factory, connectTimeout, readTimeout,1608trace, version, authMechanism, ctls, protocol, user,1609passwd, env);1610answer.referenceCount = 1; // always one when starting out1611return answer;1612}1613}1614return new LdapClient(hostname, port, factory, connectTimeout,1615readTimeout, trace, null);1616}1617}161816191620