mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-18 06:15:16 +00:00
1624 lines
60 KiB
Java
1624 lines
60 KiB
Java
/*
|
|
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package com.sun.jndi.ldap;
|
|
|
|
import java.io.*;
|
|
import java.util.ArrayList;
|
|
import java.util.Locale;
|
|
import java.util.Vector;
|
|
import java.util.Hashtable;
|
|
|
|
import javax.naming.*;
|
|
import javax.naming.directory.*;
|
|
import javax.naming.ldap.*;
|
|
|
|
import com.sun.jndi.ldap.pool.PooledConnection;
|
|
import com.sun.jndi.ldap.pool.PoolCallback;
|
|
import com.sun.jndi.ldap.sasl.LdapSasl;
|
|
import com.sun.jndi.ldap.sasl.SaslInputStream;
|
|
|
|
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
|
|
/**
|
|
* LDAP (RFC-1777) and LDAPv3 (RFC-2251) compliant client
|
|
*
|
|
* This class represents a connection to an LDAP client.
|
|
* Callers interact with this class at an LDAP operation level.
|
|
* That is, the caller invokes a method to do a SEARCH or MODRDN
|
|
* operation and gets back the result.
|
|
* The caller uses the constructor to create a connection to the server.
|
|
* It then needs to use authenticate() to perform an LDAP BIND.
|
|
* Note that for v3, BIND is optional so authenticate() might not
|
|
* actually send a BIND. authenticate() can be used later on to issue
|
|
* a BIND, for example, for a v3 client that wants to change the connection's
|
|
* credentials.
|
|
*<p>
|
|
* Multiple LdapCtx might share the same LdapClient. For example, contexts
|
|
* derived from the same initial context would share the same LdapClient
|
|
* until changes to a context's properties necessitates its own LdapClient.
|
|
* LdapClient methods that access shared data are thread-safe (i.e., caller
|
|
* does not have to sync).
|
|
*<p>
|
|
* Fields:
|
|
* isLdapv3 - no sync; initialized and updated within sync authenticate();
|
|
* always updated when connection is "quiet" and not shared;
|
|
* read access from outside LdapClient not sync
|
|
* referenceCount - sync within LdapClient; exception is forceClose() which
|
|
* is used by Connection thread to close connection upon receiving
|
|
* an Unsolicited Notification.
|
|
* access from outside LdapClient must sync;
|
|
* conn - no sync; Connection takes care of its own sync
|
|
* unsolicited - sync Vector; multiple operations sync'ed
|
|
*
|
|
* @author Vincent Ryan
|
|
* @author Jagane Sundar
|
|
* @author Rosanna Lee
|
|
*/
|
|
|
|
public final class LdapClient implements PooledConnection {
|
|
// ---------------------- Constants ----------------------------------
|
|
private static final int debug = 0;
|
|
static final boolean caseIgnore = true;
|
|
|
|
// Default list of binary attributes
|
|
private static final Hashtable<String, Boolean> defaultBinaryAttrs =
|
|
new Hashtable<>(23,0.75f);
|
|
static {
|
|
defaultBinaryAttrs.put("userpassword", Boolean.TRUE); //2.5.4.35
|
|
defaultBinaryAttrs.put("javaserializeddata", Boolean.TRUE);
|
|
//1.3.6.1.4.1.42.2.27.4.1.8
|
|
defaultBinaryAttrs.put("javaserializedobject", Boolean.TRUE);
|
|
// 1.3.6.1.4.1.42.2.27.4.1.2
|
|
defaultBinaryAttrs.put("jpegphoto", Boolean.TRUE);
|
|
//0.9.2342.19200300.100.1.60
|
|
defaultBinaryAttrs.put("audio", Boolean.TRUE); //0.9.2342.19200300.100.1.55
|
|
defaultBinaryAttrs.put("thumbnailphoto", Boolean.TRUE);
|
|
//1.3.6.1.4.1.1466.101.120.35
|
|
defaultBinaryAttrs.put("thumbnaillogo", Boolean.TRUE);
|
|
//1.3.6.1.4.1.1466.101.120.36
|
|
defaultBinaryAttrs.put("usercertificate", Boolean.TRUE); //2.5.4.36
|
|
defaultBinaryAttrs.put("cacertificate", Boolean.TRUE); //2.5.4.37
|
|
defaultBinaryAttrs.put("certificaterevocationlist", Boolean.TRUE);
|
|
//2.5.4.39
|
|
defaultBinaryAttrs.put("authorityrevocationlist", Boolean.TRUE); //2.5.4.38
|
|
defaultBinaryAttrs.put("crosscertificatepair", Boolean.TRUE); //2.5.4.40
|
|
defaultBinaryAttrs.put("photo", Boolean.TRUE); //0.9.2342.19200300.100.1.7
|
|
defaultBinaryAttrs.put("personalsignature", Boolean.TRUE);
|
|
//0.9.2342.19200300.100.1.53
|
|
defaultBinaryAttrs.put("x500uniqueidentifier", Boolean.TRUE); //2.5.4.45
|
|
}
|
|
|
|
private static final String DISCONNECT_OID = "1.3.6.1.4.1.1466.20036";
|
|
|
|
|
|
// ----------------------- instance fields ------------------------
|
|
boolean isLdapv3; // Used by LdapCtx
|
|
int referenceCount = 1; // Used by LdapCtx for check for sharing
|
|
|
|
final Connection conn; // Connection to server; has reader thread
|
|
// used by LdapCtx for StartTLS
|
|
|
|
private final PoolCallback pcb;
|
|
private final boolean pooled;
|
|
private boolean authenticateCalled = false;
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// constructor: Create an authenticated connection to server
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
LdapClient(String host, int port, String socketFactory,
|
|
int connectTimeout, int readTimeout, OutputStream trace, PoolCallback pcb)
|
|
throws NamingException {
|
|
|
|
if (debug > 0)
|
|
System.err.println("LdapClient: constructor called " + host + ":" + port );
|
|
conn = new Connection(this, host, port, socketFactory, connectTimeout, readTimeout,
|
|
trace);
|
|
|
|
this.pcb = pcb;
|
|
pooled = (pcb != null);
|
|
}
|
|
|
|
synchronized boolean authenticateCalled() {
|
|
return authenticateCalled;
|
|
}
|
|
|
|
synchronized LdapResult
|
|
authenticate(boolean initial, String name, Object pw, int version,
|
|
String authMechanism, Control[] ctls, Hashtable<?,?> env)
|
|
throws NamingException {
|
|
|
|
int readTimeout = conn.readTimeout;
|
|
conn.readTimeout = conn.connectTimeout;
|
|
LdapResult res = null;
|
|
|
|
try {
|
|
authenticateCalled = true;
|
|
|
|
try {
|
|
ensureOpen();
|
|
} catch (IOException e) {
|
|
NamingException ne = new CommunicationException();
|
|
ne.setRootCause(e);
|
|
throw ne;
|
|
}
|
|
|
|
switch (version) {
|
|
case LDAP_VERSION3_VERSION2:
|
|
case LDAP_VERSION3:
|
|
isLdapv3 = true;
|
|
break;
|
|
case LDAP_VERSION2:
|
|
isLdapv3 = false;
|
|
break;
|
|
default:
|
|
throw new CommunicationException("Protocol version " + version +
|
|
" not supported");
|
|
}
|
|
|
|
if (authMechanism.equalsIgnoreCase("none") ||
|
|
authMechanism.equalsIgnoreCase("anonymous")) {
|
|
|
|
// Perform LDAP bind if we are reauthenticating, using LDAPv2,
|
|
// supporting failover to LDAPv2, or controls have been supplied.
|
|
if (!initial ||
|
|
(version == LDAP_VERSION2) ||
|
|
(version == LDAP_VERSION3_VERSION2) ||
|
|
((ctls != null) && (ctls.length > 0))) {
|
|
try {
|
|
// anonymous bind; update name/pw for LDAPv2 retry
|
|
res = ldapBind(name=null, (byte[])(pw=null), ctls, null,
|
|
false);
|
|
if (res.status == LdapClient.LDAP_SUCCESS) {
|
|
conn.setBound();
|
|
}
|
|
} catch (IOException e) {
|
|
NamingException ne =
|
|
new CommunicationException("anonymous bind failed: " +
|
|
conn.host + ":" + conn.port);
|
|
ne.setRootCause(e);
|
|
throw ne;
|
|
}
|
|
} else {
|
|
// Skip LDAP bind for LDAPv3 anonymous bind
|
|
res = new LdapResult();
|
|
res.status = LdapClient.LDAP_SUCCESS;
|
|
}
|
|
} else if (authMechanism.equalsIgnoreCase("simple")) {
|
|
// simple authentication
|
|
byte[] encodedPw = null;
|
|
try {
|
|
encodedPw = encodePassword(pw, isLdapv3);
|
|
res = ldapBind(name, encodedPw, ctls, null, false);
|
|
if (res.status == LdapClient.LDAP_SUCCESS) {
|
|
conn.setBound();
|
|
}
|
|
} catch (IOException e) {
|
|
NamingException ne =
|
|
new CommunicationException("simple bind failed: " +
|
|
conn.host + ":" + conn.port);
|
|
ne.setRootCause(e);
|
|
throw ne;
|
|
} finally {
|
|
// If pw was copied to a new array, clear that array as
|
|
// a security precaution.
|
|
if (encodedPw != pw && encodedPw != null) {
|
|
for (int i = 0; i < encodedPw.length; i++) {
|
|
encodedPw[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
} else if (isLdapv3) {
|
|
// SASL authentication
|
|
try {
|
|
res = LdapSasl.saslBind(this, conn, conn.host, name, pw,
|
|
authMechanism, env, ctls);
|
|
if (res.status == LdapClient.LDAP_SUCCESS) {
|
|
conn.setBound();
|
|
}
|
|
} catch (IOException e) {
|
|
NamingException ne =
|
|
new CommunicationException("SASL bind failed: " +
|
|
conn.host + ":" + conn.port);
|
|
ne.setRootCause(e);
|
|
throw ne;
|
|
}
|
|
} else {
|
|
throw new AuthenticationNotSupportedException(authMechanism);
|
|
}
|
|
|
|
//
|
|
// re-try login using v2 if failing over
|
|
//
|
|
if (initial &&
|
|
(res.status == LdapClient.LDAP_PROTOCOL_ERROR) &&
|
|
(version == LdapClient.LDAP_VERSION3_VERSION2) &&
|
|
(authMechanism.equalsIgnoreCase("none") ||
|
|
authMechanism.equalsIgnoreCase("anonymous") ||
|
|
authMechanism.equalsIgnoreCase("simple"))) {
|
|
|
|
byte[] encodedPw = null;
|
|
try {
|
|
isLdapv3 = false;
|
|
encodedPw = encodePassword(pw, false);
|
|
res = ldapBind(name, encodedPw, ctls, null, false);
|
|
if (res.status == LdapClient.LDAP_SUCCESS) {
|
|
conn.setBound();
|
|
}
|
|
} catch (IOException e) {
|
|
NamingException ne =
|
|
new CommunicationException(authMechanism + ":" +
|
|
conn.host + ":" + conn.port);
|
|
ne.setRootCause(e);
|
|
throw ne;
|
|
} finally {
|
|
// If pw was copied to a new array, clear that array as
|
|
// a security precaution.
|
|
if (encodedPw != pw && encodedPw != null) {
|
|
for (int i = 0; i < encodedPw.length; i++) {
|
|
encodedPw[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// principal name not found
|
|
// (map NameNotFoundException to AuthenticationException)
|
|
// %%% This is a workaround for Netscape servers returning
|
|
// %%% no such object when the principal name is not found
|
|
// %%% Note that when this workaround is applied, it does not allow
|
|
// %%% response controls to be recorded by the calling context
|
|
if (res.status == LdapClient.LDAP_NO_SUCH_OBJECT) {
|
|
throw new AuthenticationException(
|
|
getErrorMessage(res.status, res.errorMessage));
|
|
}
|
|
conn.setV3(isLdapv3);
|
|
return res;
|
|
} finally {
|
|
conn.readTimeout = readTimeout;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends an LDAP Bind request.
|
|
* Cannot be private; called by LdapSasl
|
|
* @param dn The possibly null DN to use in the BIND request. null if anonymous.
|
|
* @param toServer The possibly null array of bytes to send to the server.
|
|
* @param auth The authentication mechanism
|
|
*
|
|
*/
|
|
public synchronized LdapResult ldapBind(String dn, byte[]toServer,
|
|
Control[] bindCtls, String auth, boolean pauseAfterReceipt)
|
|
throws java.io.IOException, NamingException {
|
|
|
|
ensureOpen();
|
|
|
|
// flush outstanding requests
|
|
conn.abandonOutstandingReqs(null);
|
|
|
|
BerEncoder ber = new BerEncoder();
|
|
int curMsgId = conn.getMsgId();
|
|
LdapResult res = new LdapResult();
|
|
res.status = LDAP_OPERATIONS_ERROR;
|
|
|
|
//
|
|
// build the bind request.
|
|
//
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeInt(curMsgId);
|
|
ber.beginSeq(LdapClient.LDAP_REQ_BIND);
|
|
ber.encodeInt(isLdapv3 ? LDAP_VERSION3 : LDAP_VERSION2);
|
|
ber.encodeString(dn, isLdapv3);
|
|
|
|
// if authentication mechanism specified, it is SASL
|
|
if (auth != null) {
|
|
ber.beginSeq(Ber.ASN_CONTEXT | Ber.ASN_CONSTRUCTOR | 3);
|
|
ber.encodeString(auth, isLdapv3); // SASL mechanism
|
|
if (toServer != null) {
|
|
ber.encodeOctetString(toServer,
|
|
Ber.ASN_OCTET_STR);
|
|
}
|
|
ber.endSeq();
|
|
} else {
|
|
if (toServer != null) {
|
|
ber.encodeOctetString(toServer, Ber.ASN_CONTEXT);
|
|
} else {
|
|
ber.encodeOctetString(null, Ber.ASN_CONTEXT, 0, 0);
|
|
}
|
|
}
|
|
ber.endSeq();
|
|
|
|
// Encode controls
|
|
if (isLdapv3) {
|
|
encodeControls(ber, bindCtls);
|
|
}
|
|
ber.endSeq();
|
|
|
|
LdapRequest req = conn.writeRequest(ber, curMsgId, pauseAfterReceipt);
|
|
if (toServer != null) {
|
|
ber.reset(); // clear internally-stored password
|
|
}
|
|
|
|
// Read reply
|
|
BerDecoder rber = conn.readReply(req);
|
|
|
|
rber.parseSeq(null); // init seq
|
|
rber.parseInt(); // msg id
|
|
if (rber.parseByte() != LDAP_REP_BIND) {
|
|
return res;
|
|
}
|
|
|
|
rber.parseLength();
|
|
parseResult(rber, res, isLdapv3);
|
|
|
|
// handle server's credentials (if present)
|
|
if (isLdapv3 &&
|
|
(rber.bytesLeft() > 0) &&
|
|
(rber.peekByte() == (Ber.ASN_CONTEXT | 7))) {
|
|
res.serverCreds = rber.parseOctetString((Ber.ASN_CONTEXT | 7), null);
|
|
}
|
|
|
|
res.resControls = isLdapv3 ? parseControls(rber) : null;
|
|
|
|
conn.removeRequest(req);
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Determines whether SASL encryption/integrity is in progress.
|
|
* This check is made prior to reauthentication. You cannot reauthenticate
|
|
* over an encrypted/integrity-protected SASL channel. You must
|
|
* close the channel and open a new one.
|
|
*/
|
|
boolean usingSaslStreams() {
|
|
return (conn.inStream instanceof SaslInputStream);
|
|
}
|
|
|
|
// Returns true if client connection was upgraded
|
|
// with STARTTLS extended operation on the server side
|
|
boolean isUpgradedToStartTls() {
|
|
return conn.isUpgradedToStartTls();
|
|
}
|
|
|
|
synchronized void incRefCount() {
|
|
++referenceCount;
|
|
if (debug > 1) {
|
|
System.err.println("LdapClient.incRefCount: " + referenceCount + " " + this);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the encoded password.
|
|
*/
|
|
private static byte[] encodePassword(Object pw, boolean v3) throws IOException {
|
|
|
|
if (pw instanceof char[]) {
|
|
pw = new String((char[])pw);
|
|
}
|
|
|
|
if (pw instanceof String) {
|
|
if (v3) {
|
|
return ((String)pw).getBytes(UTF_8);
|
|
} else {
|
|
return ((String)pw).getBytes(ISO_8859_1);
|
|
}
|
|
} else {
|
|
return (byte[])pw;
|
|
}
|
|
}
|
|
|
|
synchronized void close(Control[] reqCtls, boolean hardClose) {
|
|
--referenceCount;
|
|
|
|
if (debug > 1) {
|
|
System.err.println("LdapClient: " + this);
|
|
System.err.println("LdapClient: close() called: " + referenceCount);
|
|
(new Throwable()).printStackTrace();
|
|
}
|
|
|
|
if (referenceCount <= 0) {
|
|
if (debug > 0) System.err.println("LdapClient: closed connection " + this);
|
|
if (!pooled) {
|
|
// Not being pooled; continue with closing
|
|
conn.cleanup(reqCtls, false);
|
|
} else {
|
|
// Pooled
|
|
// Is this a real close or a request to return conn to pool
|
|
if (hardClose) {
|
|
conn.cleanup(reqCtls, false);
|
|
pcb.removePooledConnection(this);
|
|
} else {
|
|
pcb.releasePooledConnection(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: Should NOT be synchronized otherwise won't be able to close
|
|
private void forceClose(boolean cleanPool) {
|
|
referenceCount = 0; // force closing of connection
|
|
|
|
if (debug > 1) {
|
|
System.err.println("LdapClient: forceClose() of " + this);
|
|
}
|
|
if (debug > 0) {
|
|
System.err.println(
|
|
"LdapClient: forced close of connection " + this);
|
|
}
|
|
conn.cleanup(null, false);
|
|
if (cleanPool) {
|
|
pcb.removePooledConnection(this);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("removal")
|
|
protected void finalize() {
|
|
if (debug > 0) System.err.println("LdapClient: finalize " + this);
|
|
forceClose(pooled);
|
|
}
|
|
|
|
/*
|
|
* Used by connection pooling to close physical connection.
|
|
*/
|
|
public synchronized void closeConnection() {
|
|
forceClose(false); // this is a pool callback so no need to clean pool
|
|
}
|
|
|
|
/**
|
|
* Called by Connection.cleanup(). LdapClient should
|
|
* notify any unsolicited listeners and removing itself from any pool.
|
|
* This is almost like forceClose(), except it doesn't call
|
|
* Connection.cleanup() (because this is called from cleanup()).
|
|
*/
|
|
void processConnectionClosure() {
|
|
// Notify listeners
|
|
if (unsolicited.size() > 0) {
|
|
String msg;
|
|
if (conn != null) {
|
|
msg = conn.host + ":" + conn.port + " connection closed";
|
|
} else {
|
|
msg = "Connection closed";
|
|
}
|
|
notifyUnsolicited(new CommunicationException(msg));
|
|
}
|
|
|
|
// Remove from pool
|
|
if (pooled) {
|
|
pcb.removePooledConnection(this);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// LDAP search. also includes methods to encode rfc 1558 compliant filters
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
static final int SCOPE_BASE_OBJECT = 0;
|
|
static final int SCOPE_ONE_LEVEL = 1;
|
|
static final int SCOPE_SUBTREE = 2;
|
|
|
|
LdapResult search(String dn, int scope, int deref, int sizeLimit,
|
|
int timeLimit, boolean attrsOnly, String attrs[],
|
|
String filter, int batchSize, Control[] reqCtls,
|
|
Hashtable<String, Boolean> binaryAttrs,
|
|
boolean waitFirstReply, int replyQueueCapacity)
|
|
throws IOException, NamingException {
|
|
|
|
ensureOpen();
|
|
|
|
LdapResult res = new LdapResult();
|
|
|
|
BerEncoder ber = new BerEncoder();
|
|
int curMsgId = conn.getMsgId();
|
|
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeInt(curMsgId);
|
|
ber.beginSeq(LDAP_REQ_SEARCH);
|
|
ber.encodeString(dn == null ? "" : dn, isLdapv3);
|
|
ber.encodeInt(scope, LBER_ENUMERATED);
|
|
ber.encodeInt(deref, LBER_ENUMERATED);
|
|
ber.encodeInt(sizeLimit);
|
|
ber.encodeInt(timeLimit);
|
|
ber.encodeBoolean(attrsOnly);
|
|
Filter.encodeFilterString(ber, filter, isLdapv3);
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeStringArray(attrs, isLdapv3);
|
|
ber.endSeq();
|
|
ber.endSeq();
|
|
if (isLdapv3) encodeControls(ber, reqCtls);
|
|
ber.endSeq();
|
|
|
|
LdapRequest req =
|
|
conn.writeRequest(ber, curMsgId, false, replyQueueCapacity);
|
|
|
|
res.msgId = curMsgId;
|
|
res.status = LdapClient.LDAP_SUCCESS; //optimistic
|
|
if (waitFirstReply) {
|
|
// get first reply
|
|
res = getSearchReply(req, batchSize, res, binaryAttrs);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Abandon the search operation and remove it from the message queue.
|
|
*/
|
|
void clearSearchReply(LdapResult res, Control[] ctls) {
|
|
if (res != null) {
|
|
|
|
// Only send an LDAP abandon operation when clearing the search
|
|
// reply from a one-level or subtree search.
|
|
LdapRequest req = conn.findRequest(res.msgId);
|
|
if (req == null) {
|
|
return;
|
|
}
|
|
|
|
// OK if req got removed after check; double removal attempt
|
|
// but otherwise no harm done
|
|
|
|
// Send an LDAP abandon only if the search operation has not yet
|
|
// completed.
|
|
if (req.hasSearchCompleted()) {
|
|
conn.removeRequest(req);
|
|
} else {
|
|
conn.abandonRequest(req, ctls);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Retrieve the next batch of entries and/or referrals.
|
|
*/
|
|
LdapResult getSearchReply(int batchSize, LdapResult res,
|
|
Hashtable<String, Boolean> binaryAttrs) throws IOException, NamingException {
|
|
|
|
ensureOpen();
|
|
|
|
LdapRequest req;
|
|
|
|
if ((req = conn.findRequest(res.msgId)) == null) {
|
|
return null;
|
|
}
|
|
|
|
return getSearchReply(req, batchSize, res, binaryAttrs);
|
|
}
|
|
|
|
private LdapResult getSearchReply(LdapRequest req,
|
|
int batchSize, LdapResult res, Hashtable<String, Boolean> binaryAttrs)
|
|
throws IOException, NamingException {
|
|
|
|
if (batchSize == 0)
|
|
batchSize = Integer.MAX_VALUE;
|
|
|
|
if (res.entries != null) {
|
|
res.entries.setSize(0); // clear the (previous) set of entries
|
|
} else {
|
|
res.entries =
|
|
new Vector<>(batchSize == Integer.MAX_VALUE ? 32 : batchSize);
|
|
}
|
|
|
|
if (res.referrals != null) {
|
|
res.referrals.setSize(0); // clear the (previous) set of referrals
|
|
}
|
|
|
|
BerDecoder replyBer; // Decoder for response
|
|
int seq; // Request id
|
|
|
|
Attributes lattrs; // Attribute set read from response
|
|
Attribute la; // Attribute read from response
|
|
String DN; // DN read from response
|
|
LdapEntry le; // LDAP entry representing response
|
|
int[] seqlen; // Holder for response length
|
|
int endseq; // Position of end of response
|
|
|
|
for (int i = 0; i < batchSize;) {
|
|
replyBer = conn.readReply(req);
|
|
|
|
//
|
|
// process search reply
|
|
//
|
|
replyBer.parseSeq(null); // init seq
|
|
replyBer.parseInt(); // req id
|
|
seq = replyBer.parseSeq(null);
|
|
|
|
if (seq == LDAP_REP_SEARCH) {
|
|
|
|
// handle LDAPv3 search entries
|
|
lattrs = new BasicAttributes(caseIgnore);
|
|
DN = replyBer.parseString(isLdapv3);
|
|
le = new LdapEntry(DN, lattrs);
|
|
seqlen = new int[1];
|
|
|
|
replyBer.parseSeq(seqlen);
|
|
endseq = replyBer.getParsePosition() + seqlen[0];
|
|
while ((replyBer.getParsePosition() < endseq) &&
|
|
(replyBer.bytesLeft() > 0)) {
|
|
la = parseAttribute(replyBer, binaryAttrs);
|
|
lattrs.put(la);
|
|
}
|
|
le.respCtls = isLdapv3 ? parseControls(replyBer) : null;
|
|
|
|
res.entries.addElement(le);
|
|
i++;
|
|
|
|
} else if ((seq == LDAP_REP_SEARCH_REF) && isLdapv3) {
|
|
|
|
// handle LDAPv3 search reference
|
|
Vector<String> URLs = new Vector<>(4);
|
|
|
|
// %%% Although not strictly correct, some LDAP servers
|
|
// encode the SEQUENCE OF tag in the SearchResultRef
|
|
if (replyBer.peekByte() ==
|
|
(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR)) {
|
|
replyBer.parseSeq(null);
|
|
}
|
|
|
|
while ((replyBer.bytesLeft() > 0) &&
|
|
(replyBer.peekByte() == Ber.ASN_OCTET_STR)) {
|
|
|
|
URLs.addElement(replyBer.parseString(isLdapv3));
|
|
}
|
|
|
|
if (res.referrals == null) {
|
|
res.referrals = new Vector<>(4);
|
|
}
|
|
res.referrals.addElement(URLs);
|
|
res.resControls = isLdapv3 ? parseControls(replyBer) : null;
|
|
|
|
// Save referral and continue to get next search result
|
|
|
|
} else if (seq == LDAP_REP_EXTENSION) {
|
|
|
|
parseExtResponse(replyBer, res); //%%% ignore for now
|
|
|
|
} else if (seq == LDAP_REP_RESULT) {
|
|
|
|
parseResult(replyBer, res, isLdapv3);
|
|
res.resControls = isLdapv3 ? parseControls(replyBer) : null;
|
|
|
|
conn.removeRequest(req);
|
|
return res; // Done with search
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
private Attribute parseAttribute(BerDecoder ber,
|
|
Hashtable<String, Boolean> binaryAttrs)
|
|
throws IOException {
|
|
|
|
int len[] = new int[1];
|
|
int seq = ber.parseSeq(null);
|
|
String attrid = ber.parseString(isLdapv3);
|
|
boolean hasBinaryValues = isBinaryValued(attrid, binaryAttrs);
|
|
Attribute la = new LdapAttribute(attrid);
|
|
|
|
if ((seq = ber.parseSeq(len)) == LBER_SET) {
|
|
int attrlen = len[0];
|
|
while (ber.bytesLeft() > 0 && attrlen > 0) {
|
|
try {
|
|
attrlen -= parseAttributeValue(ber, la, hasBinaryValues);
|
|
} catch (IOException ex) {
|
|
ber.seek(attrlen);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
// Skip the rest of the sequence because it is not what we want
|
|
ber.seek(len[0]);
|
|
}
|
|
return la;
|
|
}
|
|
|
|
//
|
|
// returns number of bytes that were parsed. Adds the values to attr
|
|
//
|
|
private int parseAttributeValue(BerDecoder ber, Attribute la,
|
|
boolean hasBinaryValues) throws IOException {
|
|
|
|
int len[] = new int[1];
|
|
|
|
if (hasBinaryValues) {
|
|
la.add(ber.parseOctetString(ber.peekByte(), len));
|
|
} else {
|
|
la.add(ber.parseStringWithTag(
|
|
Ber.ASN_SIMPLE_STRING, isLdapv3, len));
|
|
}
|
|
return len[0];
|
|
}
|
|
|
|
private boolean isBinaryValued(String attrid,
|
|
Hashtable<String, Boolean> binaryAttrs) {
|
|
String id = attrid.toLowerCase(Locale.ENGLISH);
|
|
|
|
return id.contains(";binary") ||
|
|
defaultBinaryAttrs.containsKey(id) ||
|
|
((binaryAttrs != null) && (binaryAttrs.containsKey(id)));
|
|
}
|
|
|
|
// package entry point; used by Connection
|
|
static void parseResult(BerDecoder replyBer, LdapResult res,
|
|
boolean isLdapv3) throws IOException {
|
|
|
|
res.status = replyBer.parseEnumeration();
|
|
res.matchedDN = replyBer.parseString(isLdapv3);
|
|
res.errorMessage = replyBer.parseString(isLdapv3);
|
|
|
|
// handle LDAPv3 referrals (if present)
|
|
if (isLdapv3 &&
|
|
(replyBer.bytesLeft() > 0) &&
|
|
(replyBer.peekByte() == LDAP_REP_REFERRAL)) {
|
|
|
|
Vector<String> URLs = new Vector<>(4);
|
|
int[] seqlen = new int[1];
|
|
|
|
replyBer.parseSeq(seqlen);
|
|
int endseq = replyBer.getParsePosition() + seqlen[0];
|
|
while ((replyBer.getParsePosition() < endseq) &&
|
|
(replyBer.bytesLeft() > 0)) {
|
|
|
|
URLs.addElement(replyBer.parseString(isLdapv3));
|
|
}
|
|
|
|
if (res.referrals == null) {
|
|
res.referrals = new Vector<>(4);
|
|
}
|
|
res.referrals.addElement(URLs);
|
|
}
|
|
}
|
|
|
|
// package entry point; used by Connection
|
|
static Vector<Control> parseControls(BerDecoder replyBer) throws IOException {
|
|
|
|
// handle LDAPv3 controls (if present)
|
|
if ((replyBer.bytesLeft() > 0) && (replyBer.peekByte() == LDAP_CONTROLS)) {
|
|
Vector<Control> ctls = new Vector<>(4);
|
|
String controlOID;
|
|
boolean criticality = false; // default
|
|
byte[] controlValue = null; // optional
|
|
int[] seqlen = new int[1];
|
|
|
|
replyBer.parseSeq(seqlen);
|
|
int endseq = replyBer.getParsePosition() + seqlen[0];
|
|
while ((replyBer.getParsePosition() < endseq) &&
|
|
(replyBer.bytesLeft() > 0)) {
|
|
|
|
replyBer.parseSeq(null);
|
|
controlOID = replyBer.parseString(true);
|
|
|
|
if ((replyBer.bytesLeft() > 0) &&
|
|
(replyBer.peekByte() == Ber.ASN_BOOLEAN)) {
|
|
criticality = replyBer.parseBoolean();
|
|
}
|
|
if ((replyBer.bytesLeft() > 0) &&
|
|
(replyBer.peekByte() == Ber.ASN_OCTET_STR)) {
|
|
controlValue =
|
|
replyBer.parseOctetString(Ber.ASN_OCTET_STR, null);
|
|
}
|
|
if (controlOID != null) {
|
|
ctls.addElement(
|
|
new BasicControl(controlOID, criticality, controlValue));
|
|
}
|
|
}
|
|
return ctls;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void parseExtResponse(BerDecoder replyBer, LdapResult res)
|
|
throws IOException {
|
|
|
|
parseResult(replyBer, res, isLdapv3);
|
|
|
|
if ((replyBer.bytesLeft() > 0) &&
|
|
(replyBer.peekByte() == LDAP_REP_EXT_OID)) {
|
|
res.extensionId =
|
|
replyBer.parseStringWithTag(LDAP_REP_EXT_OID, isLdapv3, null);
|
|
}
|
|
if ((replyBer.bytesLeft() > 0) &&
|
|
(replyBer.peekByte() == LDAP_REP_EXT_VAL)) {
|
|
res.extensionValue =
|
|
replyBer.parseOctetString(LDAP_REP_EXT_VAL, null);
|
|
}
|
|
|
|
res.resControls = parseControls(replyBer);
|
|
}
|
|
|
|
//
|
|
// Encode LDAPv3 controls
|
|
//
|
|
static void encodeControls(BerEncoder ber, Control[] reqCtls)
|
|
throws IOException {
|
|
|
|
if ((reqCtls == null) || (reqCtls.length == 0)) {
|
|
return;
|
|
}
|
|
|
|
byte[] controlVal;
|
|
|
|
ber.beginSeq(LdapClient.LDAP_CONTROLS);
|
|
|
|
for (int i = 0; i < reqCtls.length; i++) {
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeString(reqCtls[i].getID(), true); // control OID
|
|
if (reqCtls[i].isCritical()) {
|
|
ber.encodeBoolean(true); // critical control
|
|
}
|
|
if ((controlVal = reqCtls[i].getEncodedValue()) != null) {
|
|
ber.encodeOctetString(controlVal, Ber.ASN_OCTET_STR);
|
|
}
|
|
ber.endSeq();
|
|
}
|
|
ber.endSeq();
|
|
}
|
|
|
|
/**
|
|
* Reads the next reply corresponding to msgId, outstanding on requestBer.
|
|
* Processes the result and any controls.
|
|
*/
|
|
private LdapResult processReply(LdapRequest req,
|
|
LdapResult res, int responseType) throws IOException, NamingException {
|
|
|
|
BerDecoder rber = conn.readReply(req);
|
|
|
|
rber.parseSeq(null); // init seq
|
|
rber.parseInt(); // msg id
|
|
if (rber.parseByte() != responseType) {
|
|
return res;
|
|
}
|
|
|
|
rber.parseLength();
|
|
parseResult(rber, res, isLdapv3);
|
|
res.resControls = isLdapv3 ? parseControls(rber) : null;
|
|
|
|
conn.removeRequest(req);
|
|
|
|
return res; // Done with operation
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// LDAP modify:
|
|
// Modify the DN dn with the operations on attributes attrs.
|
|
// ie, operations[0] is the operation to be performed on
|
|
// attrs[0];
|
|
// dn - DN to modify
|
|
// operations - add, delete or replace
|
|
// attrs - array of Attribute
|
|
// reqCtls - array of request controls
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
static final int ADD = 0;
|
|
static final int DELETE = 1;
|
|
static final int REPLACE = 2;
|
|
|
|
LdapResult modify(String dn, int operations[], Attribute attrs[],
|
|
Control[] reqCtls)
|
|
throws IOException, NamingException {
|
|
|
|
ensureOpen();
|
|
|
|
LdapResult res = new LdapResult();
|
|
res.status = LDAP_OPERATIONS_ERROR;
|
|
|
|
if (dn == null || operations.length != attrs.length)
|
|
return res;
|
|
|
|
BerEncoder ber = new BerEncoder();
|
|
int curMsgId = conn.getMsgId();
|
|
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeInt(curMsgId);
|
|
ber.beginSeq(LDAP_REQ_MODIFY);
|
|
ber.encodeString(dn, isLdapv3);
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
for (int i = 0; i < operations.length; i++) {
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeInt(operations[i], LBER_ENUMERATED);
|
|
|
|
// zero values is not permitted for the add op.
|
|
if ((operations[i] == ADD) && hasNoValue(attrs[i])) {
|
|
throw new InvalidAttributeValueException(
|
|
"'" + attrs[i].getID() + "' has no values.");
|
|
} else {
|
|
encodeAttribute(ber, attrs[i]);
|
|
}
|
|
ber.endSeq();
|
|
}
|
|
ber.endSeq();
|
|
ber.endSeq();
|
|
if (isLdapv3) encodeControls(ber, reqCtls);
|
|
ber.endSeq();
|
|
|
|
LdapRequest req = conn.writeRequest(ber, curMsgId);
|
|
|
|
return processReply(req, res, LDAP_REP_MODIFY);
|
|
}
|
|
|
|
private void encodeAttribute(BerEncoder ber, Attribute attr)
|
|
throws IOException, NamingException {
|
|
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeString(attr.getID(), isLdapv3);
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR | 1);
|
|
NamingEnumeration<?> enum_ = attr.getAll();
|
|
Object val;
|
|
while (enum_.hasMore()) {
|
|
val = enum_.next();
|
|
if (val instanceof String) {
|
|
ber.encodeString((String)val, isLdapv3);
|
|
} else if (val instanceof byte[]) {
|
|
ber.encodeOctetString((byte[])val, Ber.ASN_OCTET_STR);
|
|
} else if (val == null) {
|
|
// no attribute value
|
|
} else {
|
|
throw new InvalidAttributeValueException(
|
|
"Malformed '" + attr.getID() + "' attribute value");
|
|
}
|
|
}
|
|
ber.endSeq();
|
|
ber.endSeq();
|
|
}
|
|
|
|
private static boolean hasNoValue(Attribute attr) throws NamingException {
|
|
return attr.size() == 0 || (attr.size() == 1 && attr.get() == null);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// LDAP add
|
|
// Adds entry to the Directory
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
LdapResult add(LdapEntry entry, Control[] reqCtls)
|
|
throws IOException, NamingException {
|
|
|
|
ensureOpen();
|
|
|
|
LdapResult res = new LdapResult();
|
|
res.status = LDAP_OPERATIONS_ERROR;
|
|
|
|
if (entry == null || entry.DN == null)
|
|
return res;
|
|
|
|
BerEncoder ber = new BerEncoder();
|
|
int curMsgId = conn.getMsgId();
|
|
Attribute attr;
|
|
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeInt(curMsgId);
|
|
ber.beginSeq(LDAP_REQ_ADD);
|
|
ber.encodeString(entry.DN, isLdapv3);
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
NamingEnumeration<? extends Attribute> enum_ =
|
|
entry.attributes.getAll();
|
|
while (enum_.hasMore()) {
|
|
attr = enum_.next();
|
|
|
|
// zero values is not permitted
|
|
if (hasNoValue(attr)) {
|
|
throw new InvalidAttributeValueException(
|
|
"'" + attr.getID() + "' has no values.");
|
|
} else {
|
|
encodeAttribute(ber, attr);
|
|
}
|
|
}
|
|
ber.endSeq();
|
|
ber.endSeq();
|
|
if (isLdapv3) encodeControls(ber, reqCtls);
|
|
ber.endSeq();
|
|
|
|
LdapRequest req = conn.writeRequest(ber, curMsgId);
|
|
return processReply(req, res, LDAP_REP_ADD);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// LDAP delete
|
|
// deletes entry from the Directory
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
LdapResult delete(String DN, Control[] reqCtls)
|
|
throws IOException, NamingException {
|
|
|
|
ensureOpen();
|
|
|
|
LdapResult res = new LdapResult();
|
|
res.status = LDAP_OPERATIONS_ERROR;
|
|
|
|
if (DN == null)
|
|
return res;
|
|
|
|
BerEncoder ber = new BerEncoder();
|
|
int curMsgId = conn.getMsgId();
|
|
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeInt(curMsgId);
|
|
ber.encodeString(DN, LDAP_REQ_DELETE, isLdapv3);
|
|
if (isLdapv3) encodeControls(ber, reqCtls);
|
|
ber.endSeq();
|
|
|
|
LdapRequest req = conn.writeRequest(ber, curMsgId);
|
|
|
|
return processReply(req, res, LDAP_REP_DELETE);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// LDAP modrdn
|
|
// Changes the last element of DN to newrdn
|
|
// dn - DN to change
|
|
// newrdn - new RDN to rename to
|
|
// deleteoldrdn - boolean whether to delete old attrs or not
|
|
// newSuperior - new place to put the entry in the tree
|
|
// (ignored if server is LDAPv2)
|
|
// reqCtls - array of request controls
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
LdapResult moddn(String DN, String newrdn, boolean deleteOldRdn,
|
|
String newSuperior, Control[] reqCtls)
|
|
throws IOException, NamingException {
|
|
|
|
ensureOpen();
|
|
|
|
boolean changeSuperior = (newSuperior != null &&
|
|
newSuperior.length() > 0);
|
|
|
|
LdapResult res = new LdapResult();
|
|
res.status = LDAP_OPERATIONS_ERROR;
|
|
|
|
if (DN == null || newrdn == null)
|
|
return res;
|
|
|
|
BerEncoder ber = new BerEncoder();
|
|
int curMsgId = conn.getMsgId();
|
|
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeInt(curMsgId);
|
|
ber.beginSeq(LDAP_REQ_MODRDN);
|
|
ber.encodeString(DN, isLdapv3);
|
|
ber.encodeString(newrdn, isLdapv3);
|
|
ber.encodeBoolean(deleteOldRdn);
|
|
if(isLdapv3 && changeSuperior) {
|
|
//System.err.println("changin superior");
|
|
ber.encodeString(newSuperior, LDAP_SUPERIOR_DN, isLdapv3);
|
|
}
|
|
ber.endSeq();
|
|
if (isLdapv3) encodeControls(ber, reqCtls);
|
|
ber.endSeq();
|
|
|
|
|
|
LdapRequest req = conn.writeRequest(ber, curMsgId);
|
|
|
|
return processReply(req, res, LDAP_REP_MODRDN);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// LDAP compare
|
|
// Compare attribute->value pairs in dn
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
LdapResult compare(String DN, String type, String value, Control[] reqCtls)
|
|
throws IOException, NamingException {
|
|
|
|
ensureOpen();
|
|
|
|
LdapResult res = new LdapResult();
|
|
res.status = LDAP_OPERATIONS_ERROR;
|
|
|
|
if (DN == null || type == null || value == null)
|
|
return res;
|
|
|
|
BerEncoder ber = new BerEncoder();
|
|
int curMsgId = conn.getMsgId();
|
|
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeInt(curMsgId);
|
|
ber.beginSeq(LDAP_REQ_COMPARE);
|
|
ber.encodeString(DN, isLdapv3);
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeString(type, isLdapv3);
|
|
|
|
// replace any escaped characters in the value
|
|
byte[] val = isLdapv3 ?
|
|
value.getBytes(UTF_8) : value.getBytes(ISO_8859_1);
|
|
ber.encodeOctetString(
|
|
Filter.unescapeFilterValue(val, 0, val.length),
|
|
Ber.ASN_OCTET_STR);
|
|
|
|
ber.endSeq();
|
|
ber.endSeq();
|
|
if (isLdapv3) encodeControls(ber, reqCtls);
|
|
ber.endSeq();
|
|
|
|
LdapRequest req = conn.writeRequest(ber, curMsgId);
|
|
|
|
return processReply(req, res, LDAP_REP_COMPARE);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// LDAP extended operation
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
LdapResult extendedOp(String id, byte[] request, Control[] reqCtls,
|
|
boolean pauseAfterReceipt) throws IOException, NamingException {
|
|
|
|
ensureOpen();
|
|
|
|
LdapResult res = new LdapResult();
|
|
res.status = LDAP_OPERATIONS_ERROR;
|
|
|
|
if (id == null)
|
|
return res;
|
|
|
|
BerEncoder ber = new BerEncoder();
|
|
int curMsgId = conn.getMsgId();
|
|
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
|
|
ber.encodeInt(curMsgId);
|
|
ber.beginSeq(LDAP_REQ_EXTENSION);
|
|
ber.encodeString(id,
|
|
Ber.ASN_CONTEXT | 0, isLdapv3);//[0]
|
|
if (request != null) {
|
|
ber.encodeOctetString(request,
|
|
Ber.ASN_CONTEXT | 1);//[1]
|
|
}
|
|
ber.endSeq();
|
|
encodeControls(ber, reqCtls); // always v3
|
|
ber.endSeq();
|
|
|
|
LdapRequest req = conn.writeRequest(ber, curMsgId, pauseAfterReceipt);
|
|
|
|
BerDecoder rber = conn.readReply(req);
|
|
|
|
rber.parseSeq(null); // init seq
|
|
rber.parseInt(); // msg id
|
|
if (rber.parseByte() != LDAP_REP_EXTENSION) {
|
|
return res;
|
|
}
|
|
|
|
rber.parseLength();
|
|
parseExtResponse(rber, res);
|
|
conn.removeRequest(req);
|
|
|
|
return res; // Done with operation
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Some BER definitions convenient for LDAP
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
static final int LDAP_VERSION3_VERSION2 = 32;
|
|
static final int LDAP_VERSION2 = 0x02;
|
|
static final int LDAP_VERSION3 = 0x03; // LDAPv3
|
|
static final int LDAP_VERSION = LDAP_VERSION3;
|
|
|
|
static final int LDAP_REF_FOLLOW = 0x01; // follow referrals
|
|
static final int LDAP_REF_THROW = 0x02; // throw referral ex.
|
|
static final int LDAP_REF_IGNORE = 0x03; // ignore referrals
|
|
static final int LDAP_REF_FOLLOW_SCHEME = 0x04; // follow referrals of the same scheme
|
|
|
|
static final String LDAP_URL = "ldap://"; // LDAPv3
|
|
static final String LDAPS_URL = "ldaps://"; // LDAPv3
|
|
|
|
static final int LBER_BOOLEAN = 0x01;
|
|
static final int LBER_INTEGER = 0x02;
|
|
static final int LBER_BITSTRING = 0x03;
|
|
static final int LBER_OCTETSTRING = 0x04;
|
|
static final int LBER_NULL = 0x05;
|
|
static final int LBER_ENUMERATED = 0x0a;
|
|
static final int LBER_SEQUENCE = 0x30;
|
|
static final int LBER_SET = 0x31;
|
|
|
|
static final int LDAP_SUPERIOR_DN = 0x80;
|
|
|
|
static final int LDAP_REQ_BIND = 0x60; // app + constructed
|
|
static final int LDAP_REQ_UNBIND = 0x42; // app + primitive
|
|
static final int LDAP_REQ_SEARCH = 0x63; // app + constructed
|
|
static final int LDAP_REQ_MODIFY = 0x66; // app + constructed
|
|
static final int LDAP_REQ_ADD = 0x68; // app + constructed
|
|
static final int LDAP_REQ_DELETE = 0x4a; // app + primitive
|
|
static final int LDAP_REQ_MODRDN = 0x6c; // app + constructed
|
|
static final int LDAP_REQ_COMPARE = 0x6e; // app + constructed
|
|
static final int LDAP_REQ_ABANDON = 0x50; // app + primitive
|
|
static final int LDAP_REQ_EXTENSION = 0x77; // app + constructed (LDAPv3)
|
|
|
|
static final int LDAP_REP_BIND = 0x61; // app + constructed | 1
|
|
static final int LDAP_REP_SEARCH = 0x64; // app + constructed | 4
|
|
static final int LDAP_REP_SEARCH_REF = 0x73;// app + constructed (LDAPv3)
|
|
static final int LDAP_REP_RESULT = 0x65; // app + constructed | 5
|
|
static final int LDAP_REP_MODIFY = 0x67; // app + constructed | 7
|
|
static final int LDAP_REP_ADD = 0x69; // app + constructed | 9
|
|
static final int LDAP_REP_DELETE = 0x6b; // app + primitive | b
|
|
static final int LDAP_REP_MODRDN = 0x6d; // app + primitive | d
|
|
static final int LDAP_REP_COMPARE = 0x6f; // app + primitive | f
|
|
static final int LDAP_REP_EXTENSION = 0x78; // app + constructed (LDAPv3)
|
|
|
|
static final int LDAP_REP_REFERRAL = 0xa3; // ctx + constructed (LDAPv3)
|
|
static final int LDAP_REP_EXT_OID = 0x8a; // ctx + primitive (LDAPv3)
|
|
static final int LDAP_REP_EXT_VAL = 0x8b; // ctx + primitive (LDAPv3)
|
|
|
|
// LDAPv3 Controls
|
|
|
|
static final int LDAP_CONTROLS = 0xa0; // ctx + constructed (LDAPv3)
|
|
static final String LDAP_CONTROL_MANAGE_DSA_IT = "2.16.840.1.113730.3.4.2";
|
|
static final String LDAP_CONTROL_PREFERRED_LANG = "1.3.6.1.4.1.1466.20035";
|
|
static final String LDAP_CONTROL_PAGED_RESULTS = "1.2.840.113556.1.4.319";
|
|
static final String LDAP_CONTROL_SERVER_SORT_REQ = "1.2.840.113556.1.4.473";
|
|
static final String LDAP_CONTROL_SERVER_SORT_RES = "1.2.840.113556.1.4.474";
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// return codes
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
static final int LDAP_SUCCESS = 0;
|
|
static final int LDAP_OPERATIONS_ERROR = 1;
|
|
static final int LDAP_PROTOCOL_ERROR = 2;
|
|
static final int LDAP_TIME_LIMIT_EXCEEDED = 3;
|
|
static final int LDAP_SIZE_LIMIT_EXCEEDED = 4;
|
|
static final int LDAP_COMPARE_FALSE = 5;
|
|
static final int LDAP_COMPARE_TRUE = 6;
|
|
static final int LDAP_AUTH_METHOD_NOT_SUPPORTED = 7;
|
|
static final int LDAP_STRONG_AUTH_REQUIRED = 8;
|
|
static final int LDAP_PARTIAL_RESULTS = 9; // Slapd
|
|
static final int LDAP_REFERRAL = 10; // LDAPv3
|
|
static final int LDAP_ADMIN_LIMIT_EXCEEDED = 11; // LDAPv3
|
|
static final int LDAP_UNAVAILABLE_CRITICAL_EXTENSION = 12; // LDAPv3
|
|
static final int LDAP_CONFIDENTIALITY_REQUIRED = 13; // LDAPv3
|
|
static final int LDAP_SASL_BIND_IN_PROGRESS = 14; // LDAPv3
|
|
static final int LDAP_NO_SUCH_ATTRIBUTE = 16;
|
|
static final int LDAP_UNDEFINED_ATTRIBUTE_TYPE = 17;
|
|
static final int LDAP_INAPPROPRIATE_MATCHING = 18;
|
|
static final int LDAP_CONSTRAINT_VIOLATION = 19;
|
|
static final int LDAP_ATTRIBUTE_OR_VALUE_EXISTS = 20;
|
|
static final int LDAP_INVALID_ATTRIBUTE_SYNTAX = 21;
|
|
static final int LDAP_NO_SUCH_OBJECT = 32;
|
|
static final int LDAP_ALIAS_PROBLEM = 33;
|
|
static final int LDAP_INVALID_DN_SYNTAX = 34;
|
|
static final int LDAP_IS_LEAF = 35;
|
|
static final int LDAP_ALIAS_DEREFERENCING_PROBLEM = 36;
|
|
static final int LDAP_INAPPROPRIATE_AUTHENTICATION = 48;
|
|
static final int LDAP_INVALID_CREDENTIALS = 49;
|
|
static final int LDAP_INSUFFICIENT_ACCESS_RIGHTS = 50;
|
|
static final int LDAP_BUSY = 51;
|
|
static final int LDAP_UNAVAILABLE = 52;
|
|
static final int LDAP_UNWILLING_TO_PERFORM = 53;
|
|
static final int LDAP_LOOP_DETECT = 54;
|
|
static final int LDAP_NAMING_VIOLATION = 64;
|
|
static final int LDAP_OBJECT_CLASS_VIOLATION = 65;
|
|
static final int LDAP_NOT_ALLOWED_ON_NON_LEAF = 66;
|
|
static final int LDAP_NOT_ALLOWED_ON_RDN = 67;
|
|
static final int LDAP_ENTRY_ALREADY_EXISTS = 68;
|
|
static final int LDAP_OBJECT_CLASS_MODS_PROHIBITED = 69;
|
|
static final int LDAP_AFFECTS_MULTIPLE_DSAS = 71; // LDAPv3
|
|
static final int LDAP_OTHER = 80;
|
|
|
|
static final String[] ldap_error_message = {
|
|
"Success", // 0
|
|
"Operations Error", // 1
|
|
"Protocol Error", // 2
|
|
"Timelimit Exceeded", // 3
|
|
"Sizelimit Exceeded", // 4
|
|
"Compare False", // 5
|
|
"Compare True", // 6
|
|
"Authentication Method Not Supported", // 7
|
|
"Strong Authentication Required", // 8
|
|
null,
|
|
"Referral", // 10
|
|
"Administrative Limit Exceeded", // 11
|
|
"Unavailable Critical Extension", // 12
|
|
"Confidentiality Required", // 13
|
|
"SASL Bind In Progress", // 14
|
|
null,
|
|
"No Such Attribute", // 16
|
|
"Undefined Attribute Type", // 17
|
|
"Inappropriate Matching", // 18
|
|
"Constraint Violation", // 19
|
|
"Attribute Or Value Exists", // 20
|
|
"Invalid Attribute Syntax", // 21
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
"No Such Object", // 32
|
|
"Alias Problem", // 33
|
|
"Invalid DN Syntax", // 34
|
|
null,
|
|
"Alias Dereferencing Problem", // 36
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
"Inappropriate Authentication", // 48
|
|
"Invalid Credentials", // 49
|
|
"Insufficient Access Rights", // 50
|
|
"Busy", // 51
|
|
"Unavailable", // 52
|
|
"Unwilling To Perform", // 53
|
|
"Loop Detect", // 54
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
"Naming Violation", // 64
|
|
"Object Class Violation", // 65
|
|
"Not Allowed On Non-leaf", // 66
|
|
"Not Allowed On RDN", // 67
|
|
"Entry Already Exists", // 68
|
|
"Object Class Modifications Prohibited", // 69
|
|
null,
|
|
"Affects Multiple DSAs", // 71
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
"Other", // 80
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
null
|
|
};
|
|
|
|
|
|
/*
|
|
* Generate an error message from the LDAP error code and error diagnostic.
|
|
* The message format is:
|
|
*
|
|
* "[LDAP: error code <errorCode> - <errorMessage>]"
|
|
*
|
|
* where <errorCode> is a numeric error code
|
|
* and <errorMessage> is a textual description of the error (if available)
|
|
*
|
|
*/
|
|
static String getErrorMessage(int errorCode, String errorMessage) {
|
|
|
|
String message = "[LDAP: error code " + errorCode;
|
|
|
|
if ((errorMessage != null) && (errorMessage.length() != 0)) {
|
|
|
|
// append error message from the server
|
|
message = message + " - " + errorMessage + "]";
|
|
|
|
} else {
|
|
|
|
// append built-in error message
|
|
try {
|
|
if (ldap_error_message[errorCode] != null) {
|
|
message = message + " - " + ldap_error_message[errorCode] +
|
|
"]";
|
|
}
|
|
} catch (ArrayIndexOutOfBoundsException ex) {
|
|
message = message + "]";
|
|
}
|
|
}
|
|
return message;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Unsolicited notification support.
|
|
//
|
|
// An LdapClient maintains a list of LdapCtx that have registered
|
|
// for UnsolicitedNotifications. This is a list because a single
|
|
// LdapClient might be shared among multiple contexts.
|
|
//
|
|
// When addUnsolicited() is invoked, the LdapCtx is added to the list.
|
|
//
|
|
// When Connection receives an unsolicited notification (msgid == 0),
|
|
// it invokes LdapClient.processUnsolicited(). processUnsolicited()
|
|
// parses the Extended Response. If there are registered listeners,
|
|
// LdapClient creates an UnsolicitedNotification from the response
|
|
// and informs each LdapCtx to fire an event for the notification.
|
|
// If it is a DISCONNECT notification, the connection is closed and a
|
|
// NamingExceptionEvent is fired to the listeners.
|
|
//
|
|
// When the connection is closed out-of-band like this, the next
|
|
// time a method is invoked on LdapClient, an IOException is thrown.
|
|
//
|
|
// removeUnsolicited() is invoked to remove an LdapCtx from this client.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
private Vector<LdapCtx> unsolicited = new Vector<>(3);
|
|
void addUnsolicited(LdapCtx ctx) {
|
|
if (debug > 0) {
|
|
System.err.println("LdapClient.addUnsolicited" + ctx);
|
|
}
|
|
unsolicited.addElement(ctx);
|
|
}
|
|
|
|
void removeUnsolicited(LdapCtx ctx) {
|
|
if (debug > 0) {
|
|
System.err.println("LdapClient.removeUnsolicited" + ctx);
|
|
}
|
|
unsolicited.removeElement(ctx);
|
|
}
|
|
|
|
// NOTE: Cannot be synchronized because this is called asynchronously
|
|
// by the reader thread in Connection. Instead, sync on 'unsolicited' Vector.
|
|
void processUnsolicited(BerDecoder ber) {
|
|
if (debug > 0) {
|
|
System.err.println("LdapClient.processUnsolicited");
|
|
}
|
|
try {
|
|
// Parse the response
|
|
LdapResult res = new LdapResult();
|
|
|
|
ber.parseSeq(null); // init seq
|
|
ber.parseInt(); // msg id; should be 0; ignored
|
|
if (ber.parseByte() != LDAP_REP_EXTENSION) {
|
|
throw new IOException(
|
|
"Unsolicited Notification must be an Extended Response");
|
|
}
|
|
ber.parseLength();
|
|
parseExtResponse(ber, res);
|
|
|
|
if (DISCONNECT_OID.equals(res.extensionId)) {
|
|
// force closing of connection
|
|
forceClose(pooled);
|
|
}
|
|
|
|
LdapCtx first = null;
|
|
UnsolicitedNotification notice = null;
|
|
|
|
synchronized (unsolicited) {
|
|
if (unsolicited.size() > 0) {
|
|
first = unsolicited.elementAt(0);
|
|
|
|
// Create an UnsolicitedNotification using the parsed data
|
|
// Need a 'ctx' object because we want to use the context's
|
|
// list of provider control factories.
|
|
notice = new UnsolicitedResponseImpl(
|
|
res.extensionId,
|
|
res.extensionValue,
|
|
res.referrals,
|
|
res.status,
|
|
res.errorMessage,
|
|
res.matchedDN,
|
|
(res.resControls != null) ?
|
|
first.convertControls(res.resControls) :
|
|
null);
|
|
}
|
|
}
|
|
|
|
if (notice != null) {
|
|
// Fire UnsolicitedNotification events to listeners
|
|
notifyUnsolicited(notice);
|
|
|
|
// If "disconnect" notification,
|
|
// notify unsolicited listeners via NamingException
|
|
if (DISCONNECT_OID.equals(res.extensionId)) {
|
|
notifyUnsolicited(
|
|
new CommunicationException("Connection closed"));
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
NamingException ne = new CommunicationException(
|
|
"Problem parsing unsolicited notification");
|
|
ne.setRootCause(e);
|
|
|
|
notifyUnsolicited(ne);
|
|
|
|
} catch (NamingException e) {
|
|
notifyUnsolicited(e);
|
|
}
|
|
}
|
|
|
|
|
|
private void notifyUnsolicited(Object e) {
|
|
ArrayList<LdapCtx> unsolicitedCopy;
|
|
synchronized (unsolicited) {
|
|
unsolicitedCopy = new ArrayList<>(unsolicited);
|
|
if (e instanceof NamingException) {
|
|
unsolicited.setSize(0); // no more listeners after exception
|
|
}
|
|
}
|
|
for (int i = 0; i < unsolicitedCopy.size(); i++) {
|
|
unsolicitedCopy.get(i).fireUnsolicited(e);
|
|
}
|
|
}
|
|
|
|
private void ensureOpen() throws IOException {
|
|
if (conn == null || !conn.useable) {
|
|
if (conn != null && conn.closureReason != null) {
|
|
throw conn.closureReason;
|
|
} else {
|
|
throw new IOException("connection closed");
|
|
}
|
|
}
|
|
}
|
|
|
|
// package private (used by LdapCtx)
|
|
static LdapClient getInstance(boolean usePool, String hostname, int port,
|
|
String factory, int connectTimeout, int readTimeout, OutputStream trace,
|
|
int version, String authMechanism, Control[] ctls, String protocol,
|
|
String user, Object passwd, Hashtable<?,?> env) throws NamingException {
|
|
|
|
if (usePool) {
|
|
if (LdapPoolManager.isPoolingAllowed(factory, trace,
|
|
authMechanism, protocol, env)) {
|
|
LdapClient answer = LdapPoolManager.getLdapClient(
|
|
hostname, port, factory, connectTimeout, readTimeout,
|
|
trace, version, authMechanism, ctls, protocol, user,
|
|
passwd, env);
|
|
answer.referenceCount = 1; // always one when starting out
|
|
return answer;
|
|
}
|
|
}
|
|
return new LdapClient(hostname, port, factory, connectTimeout,
|
|
readTimeout, trace, null);
|
|
}
|
|
}
|