|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package com.sun.jndi.ldap; |
|
|
|
import java.io.*; |
|
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; |
|
|
|
/** |
|
* 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 { |
|
|
|
private static final int debug = 0; |
|
static final boolean caseIgnore = true; |
|
|
|
|
|
private static final Hashtable<String, Boolean> defaultBinaryAttrs = |
|
new Hashtable<>(23,0.75f); |
|
static { |
|
defaultBinaryAttrs.put("userpassword", Boolean.TRUE); |
|
defaultBinaryAttrs.put("javaserializeddata", Boolean.TRUE); |
|
|
|
defaultBinaryAttrs.put("javaserializedobject", Boolean.TRUE); |
|
|
|
defaultBinaryAttrs.put("jpegphoto", Boolean.TRUE); |
|
//0.9.2342.19200300.100.1.60 |
|
defaultBinaryAttrs.put("audio", Boolean.TRUE); |
|
defaultBinaryAttrs.put("thumbnailphoto", Boolean.TRUE); |
|
|
|
defaultBinaryAttrs.put("thumbnaillogo", Boolean.TRUE); |
|
//1.3.6.1.4.1.1466.101.120.36 |
|
defaultBinaryAttrs.put("usercertificate", Boolean.TRUE); |
|
defaultBinaryAttrs.put("cacertificate", Boolean.TRUE); |
|
defaultBinaryAttrs.put("certificaterevocationlist", Boolean.TRUE); |
|
//2.5.4.39 |
|
defaultBinaryAttrs.put("authorityrevocationlist", Boolean.TRUE); |
|
defaultBinaryAttrs.put("crosscertificatepair", Boolean.TRUE); |
|
defaultBinaryAttrs.put("photo", Boolean.TRUE); |
|
defaultBinaryAttrs.put("personalsignature", Boolean.TRUE); |
|
//0.9.2342.19200300.100.1.53 |
|
defaultBinaryAttrs.put("x500uniqueidentifier", Boolean.TRUE); |
|
} |
|
|
|
private static final String DISCONNECT_OID = "1.3.6.1.4.1.1466.20036"; |
|
|
|
|
|
// ----------------------- instance fields ------------------------ |
|
boolean isLdapv3; |
|
int referenceCount = 1; |
|
|
|
final Connection conn; |
|
// used by LdapCtx for StartTLS |
|
|
|
final private PoolCallback pcb; |
|
final private 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, |
|
|
|
if (!initial || |
|
(version == LDAP_VERSION2) || |
|
(version == LDAP_VERSION3_VERSION2) || |
|
((ctls != null) && (ctls.length > 0))) { |
|
try { |
|
|
|
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 { |
|
|
|
res = new LdapResult(); |
|
res.status = LdapClient.LDAP_SUCCESS; |
|
} |
|
} else if (authMechanism.equalsIgnoreCase("simple")) { |
|
|
|
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 |
|
|
|
if (encodedPw != pw && encodedPw != null) { |
|
for (int i = 0; i < encodedPw.length; i++) { |
|
encodedPw[i] = 0; |
|
} |
|
} |
|
} |
|
} else if (isLdapv3) { |
|
|
|
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 |
|
|
|
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 |
|
|
|
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; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
synchronized public LdapResult ldapBind(String dn, byte[]toServer, |
|
Control[] bindCtls, String auth, boolean pauseAfterReceipt) |
|
throws java.io.IOException, NamingException { |
|
|
|
ensureOpen(); |
|
|
|
|
|
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 (auth != null) { |
|
ber.beginSeq(Ber.ASN_CONTEXT | Ber.ASN_CONSTRUCTOR | 3); |
|
ber.encodeString(auth, isLdapv3); |
|
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(); |
|
|
|
|
|
if (isLdapv3) { |
|
encodeControls(ber, bindCtls); |
|
} |
|
ber.endSeq(); |
|
|
|
LdapRequest req = conn.writeRequest(ber, curMsgId, pauseAfterReceipt); |
|
if (toServer != null) { |
|
ber.reset(); |
|
} |
|
|
|
|
|
BerDecoder rber = conn.readReply(req); |
|
|
|
rber.parseSeq(null); |
|
rber.parseInt(); |
|
if (rber.parseByte() != LDAP_REP_BIND) { |
|
return res; |
|
} |
|
|
|
rber.parseLength(); |
|
parseResult(rber, res, isLdapv3); |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
boolean usingSaslStreams() { |
|
return (conn.inStream instanceof SaslInputStream); |
|
} |
|
|
|
// Returns true if client connection was upgraded |
|
|
|
boolean isUpgradedToStartTls() { |
|
return conn.isUpgradedToStartTls(); |
|
} |
|
|
|
synchronized void incRefCount() { |
|
++referenceCount; |
|
if (debug > 1) { |
|
System.err.println("LdapClient.incRefCount: " + referenceCount + " " + this); |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
*/ |
|
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("UTF8"); |
|
} else { |
|
return ((String)pw).getBytes("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) { |
|
|
|
conn.cleanup(reqCtls, false); |
|
} else { |
|
// Pooled |
|
|
|
|
|
if (hardClose) { |
|
conn.cleanup(reqCtls, false); |
|
pcb.removePooledConnection(this); |
|
} else { |
|
pcb.releasePooledConnection(this); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
private void forceClose(boolean cleanPool) { |
|
referenceCount = 0; |
|
|
|
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); |
|
} |
|
} |
|
|
|
protected void finalize() { |
|
if (debug > 0) System.err.println("LdapClient: finalize " + this); |
|
forceClose(pooled); |
|
} |
|
|
|
|
|
|
|
*/ |
|
synchronized public void closeConnection() { |
|
forceClose(false); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void processConnectionClosure() { |
|
|
|
if (unsolicited.size() > 0) { |
|
String msg; |
|
if (conn != null) { |
|
msg = conn.host + ":" + conn.port + " connection closed"; |
|
} else { |
|
msg = "Connection closed"; |
|
} |
|
notifyUnsolicited(new CommunicationException(msg)); |
|
} |
|
|
|
|
|
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; |
|
if (waitFirstReply) { |
|
|
|
res = getSearchReply(req, batchSize, res, binaryAttrs); |
|
} |
|
return res; |
|
} |
|
|
|
|
|
|
|
*/ |
|
void clearSearchReply(LdapResult res, Control[] ctls) { |
|
if (res != null) { |
|
|
|
// Only send an LDAP abandon operation when clearing the 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 |
|
|
|
if (req.hasSearchCompleted()) { |
|
conn.removeRequest(req); |
|
} else { |
|
conn.abandonRequest(req, ctls); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
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); |
|
} else { |
|
res.entries = |
|
new Vector<>(batchSize == Integer.MAX_VALUE ? 32 : batchSize); |
|
} |
|
|
|
if (res.referrals != null) { |
|
res.referrals.setSize(0); |
|
} |
|
|
|
BerDecoder replyBer; |
|
int seq; |
|
|
|
Attributes lattrs; |
|
Attribute la; |
|
String DN; |
|
LdapEntry le; |
|
int[] seqlen; |
|
int endseq; |
|
|
|
for (int i = 0; i < batchSize;) { |
|
replyBer = conn.readReply(req); |
|
|
|
// |
|
// process search reply |
|
// |
|
replyBer.parseSeq(null); |
|
replyBer.parseInt(); |
|
seq = replyBer.parseSeq(null); |
|
|
|
if (seq == LDAP_REP_SEARCH) { |
|
|
|
|
|
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) { |
|
|
|
|
|
Vector<String> URLs = new Vector<>(4); |
|
|
|
// %%% Although not strictly correct, some LDAP servers |
|
|
|
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); |
|
|
|
} else if (seq == LDAP_REP_RESULT) { |
|
|
|
parseResult(replyBer, res, isLdapv3); |
|
res.resControls = isLdapv3 ? parseControls(replyBer) : null; |
|
|
|
conn.removeRequest(req); |
|
return res; |
|
} |
|
} |
|
|
|
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 { |
|
|
|
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.indexOf(";binary") != -1) || |
|
defaultBinaryAttrs.containsKey(id) || |
|
((binaryAttrs != null) && (binaryAttrs.containsKey(id)))); |
|
} |
|
|
|
|
|
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); |
|
|
|
|
|
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); |
|
} |
|
} |
|
|
|
|
|
static Vector<Control> parseControls(BerDecoder replyBer) throws IOException { |
|
|
|
|
|
if ((replyBer.bytesLeft() > 0) && (replyBer.peekByte() == LDAP_CONTROLS)) { |
|
Vector<Control> ctls = new Vector<>(4); |
|
String controlOID; |
|
boolean criticality = false; |
|
byte[] controlValue = null; |
|
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); |
|
if (reqCtls[i].isCritical()) { |
|
ber.encodeBoolean(true); |
|
} |
|
if ((controlVal = reqCtls[i].getEncodedValue()) != null) { |
|
ber.encodeOctetString(controlVal, Ber.ASN_OCTET_STR); |
|
} |
|
ber.endSeq(); |
|
} |
|
ber.endSeq(); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private LdapResult processReply(LdapRequest req, |
|
LdapResult res, int responseType) throws IOException, NamingException { |
|
|
|
BerDecoder rber = conn.readReply(req); |
|
|
|
rber.parseSeq(null); |
|
rber.parseInt(); |
|
if (rber.parseByte() != responseType) { |
|
return res; |
|
} |
|
|
|
rber.parseLength(); |
|
parseResult(rber, res, isLdapv3); |
|
res.resControls = isLdapv3 ? parseControls(rber) : null; |
|
|
|
conn.removeRequest(req); |
|
|
|
return res; |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
// |
|
// 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); |
|
|
|
|
|
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(); |
|
|
|
|
|
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) { |
|
|
|
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); |
|
|
|
|
|
byte[] val = isLdapv3 ? |
|
value.getBytes("UTF8") : value.getBytes("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); |
|
if (request != null) { |
|
ber.encodeOctetString(request, |
|
Ber.ASN_CONTEXT | 1); |
|
} |
|
ber.endSeq(); |
|
encodeControls(ber, reqCtls); |
|
ber.endSeq(); |
|
|
|
LdapRequest req = conn.writeRequest(ber, curMsgId, pauseAfterReceipt); |
|
|
|
BerDecoder rber = conn.readReply(req); |
|
|
|
rber.parseSeq(null); |
|
rber.parseInt(); |
|
if (rber.parseByte() != LDAP_REP_EXTENSION) { |
|
return res; |
|
} |
|
|
|
rber.parseLength(); |
|
parseExtResponse(rber, res); |
|
conn.removeRequest(req); |
|
|
|
return res; |
|
} |
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
// |
|
// 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; |
|
static final int LDAP_VERSION = LDAP_VERSION3; |
|
|
|
static final int LDAP_REF_FOLLOW = 0x01; |
|
static final int LDAP_REF_THROW = 0x02; |
|
static final int LDAP_REF_IGNORE = 0x03; |
|
static final int LDAP_REF_FOLLOW_SCHEME = 0x04; |
|
|
|
static final String LDAP_URL = "ldap://"; |
|
static final String LDAPS_URL = "ldaps://"; |
|
|
|
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; |
|
static final int LDAP_REQ_UNBIND = 0x42; |
|
static final int LDAP_REQ_SEARCH = 0x63; |
|
static final int LDAP_REQ_MODIFY = 0x66; |
|
static final int LDAP_REQ_ADD = 0x68; |
|
static final int LDAP_REQ_DELETE = 0x4a; |
|
static final int LDAP_REQ_MODRDN = 0x6c; |
|
static final int LDAP_REQ_COMPARE = 0x6e; |
|
static final int LDAP_REQ_ABANDON = 0x50; |
|
static final int LDAP_REQ_EXTENSION = 0x77; |
|
|
|
static final int LDAP_REP_BIND = 0x61; |
|
static final int LDAP_REP_SEARCH = 0x64; |
|
static final int LDAP_REP_SEARCH_REF = 0x73; |
|
static final int LDAP_REP_RESULT = 0x65; |
|
static final int LDAP_REP_MODIFY = 0x67; |
|
static final int LDAP_REP_ADD = 0x69; |
|
static final int LDAP_REP_DELETE = 0x6b; |
|
static final int LDAP_REP_MODRDN = 0x6d; |
|
static final int LDAP_REP_COMPARE = 0x6f; |
|
static final int LDAP_REP_EXTENSION = 0x78; |
|
|
|
static final int LDAP_REP_REFERRAL = 0xa3; |
|
static final int LDAP_REP_EXT_OID = 0x8a; |
|
static final int LDAP_REP_EXT_VAL = 0x8b; |
|
|
|
// LDAPv3 Controls |
|
|
|
static final int LDAP_CONTROLS = 0xa0; |
|
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; |
|
static final int LDAP_REFERRAL = 10; |
|
static final int LDAP_ADMIN_LIMIT_EXCEEDED = 11; |
|
static final int LDAP_UNAVAILABLE_CRITICAL_EXTENSION = 12; |
|
static final int LDAP_CONFIDENTIALITY_REQUIRED = 13; |
|
static final int LDAP_SASL_BIND_IN_PROGRESS = 14; |
|
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; |
|
static final int LDAP_OTHER = 80; |
|
|
|
static final String[] ldap_error_message = { |
|
"Success", |
|
"Operations Error", |
|
"Protocol Error", |
|
"Timelimit Exceeded", |
|
"Sizelimit Exceeded", |
|
"Compare False", |
|
"Compare True", |
|
"Authentication Method Not Supported", |
|
"Strong Authentication Required", |
|
null, |
|
"Referral", |
|
"Administrative Limit Exceeded", |
|
"Unavailable Critical Extension", |
|
"Confidentiality Required", |
|
"SASL Bind In Progress", |
|
null, |
|
"No Such Attribute", |
|
"Undefined Attribute Type", |
|
"Inappropriate Matching", |
|
"Constraint Violation", |
|
"Attribute Or Value Exists", |
|
"Invalid Attribute Syntax", |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
"No Such Object", |
|
"Alias Problem", |
|
"Invalid DN Syntax", |
|
null, |
|
"Alias Dereferencing Problem", |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
"Inappropriate Authentication", |
|
"Invalid Credentials", |
|
"Insufficient Access Rights", |
|
"Busy", |
|
"Unavailable", |
|
"Unwilling To Perform", |
|
"Loop Detect", |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
"Naming Violation", |
|
"Object Class Violation", |
|
"Not Allowed On Non-leaf", |
|
"Not Allowed On RDN", |
|
"Entry Already Exists", |
|
"Object Class Modifications Prohibited", |
|
null, |
|
"Affects Multiple DSAs", |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
"Other", |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null, |
|
null |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static String getErrorMessage(int errorCode, String errorMessage) { |
|
|
|
String message = "[LDAP: error code " + errorCode; |
|
|
|
if ((errorMessage != null) && (errorMessage.length() != 0)) { |
|
|
|
|
|
message = message + " - " + errorMessage + "]"; |
|
|
|
} else { |
|
|
|
|
|
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 |
|
|
|
void processUnsolicited(BerDecoder ber) { |
|
if (debug > 0) { |
|
System.err.println("LdapClient.processUnsolicited"); |
|
} |
|
try { |
|
|
|
LdapResult res = new LdapResult(); |
|
|
|
ber.parseSeq(null); |
|
ber.parseInt(); |
|
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)) { |
|
|
|
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 |
|
|
|
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) { |
|
|
|
notifyUnsolicited(notice); |
|
|
|
// If "disconnect" notification, |
|
|
|
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) { |
|
Vector<LdapCtx> unsolicitedCopy; |
|
synchronized (unsolicited) { |
|
unsolicitedCopy = new Vector<>(unsolicited); |
|
if (e instanceof NamingException) { |
|
unsolicited.setSize(0); |
|
} |
|
} |
|
for (int i = 0; i < unsolicitedCopy.size(); i++) { |
|
unsolicitedCopy.elementAt(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"); |
|
} |
|
} |
|
} |
|
|
|
|
|
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; |
|
return answer; |
|
} |
|
} |
|
return new LdapClient(hostname, port, factory, connectTimeout, |
|
readTimeout, trace, null); |
|
} |
|
} |