| /* |
| * Copyright (c) 1999, 2017, 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.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 { |
| // ---------------------- 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 |
| |
| Connection conn; // Connection to server; has reader thread |
| // 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, |
| // 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 |
| * |
| */ |
| synchronized public 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); |
| } |
| |
| 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("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 && conn != null) { |
| if (debug > 0) System.err.println("LdapClient: closed connection " + this); |
| if (!pooled) { |
| // Not being pooled; continue with closing |
| conn.cleanup(reqCtls, false); |
| conn = null; |
| } else { |
| // Pooled |
| |
| // Is this a real close or a request to return conn to pool |
| if (hardClose) { |
| conn.cleanup(reqCtls, false); |
| conn = null; |
| 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 (conn != null) { |
| if (debug > 0) System.err.println( |
| "LdapClient: forced close of connection " + this); |
| conn.cleanup(null, false); |
| conn = null; |
| |
| if (cleanPool) { |
| pcb.removePooledConnection(this); |
| } |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| protected void finalize() { |
| if (debug > 0) System.err.println("LdapClient: finalize " + this); |
| forceClose(pooled); |
| } |
| |
| /* |
| * Used by connection pooling to close physical connection. |
| */ |
| synchronized public 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 && conn != 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.indexOf(";binary") != -1) || |
| 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("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);//[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) { |
| Vector<LdapCtx> unsolicitedCopy; |
| synchronized (unsolicited) { |
| unsolicitedCopy = new Vector<>(unsolicited); |
| if (e instanceof NamingException) { |
| unsolicited.setSize(0); // no more listeners after exception |
| } |
| } |
| 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"); |
| } |
| } |
| } |
| |
| // 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); |
| } |
| } |