/* * Copyright (c) 1999, 2018, 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. *

* 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). *

* 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 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 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 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 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 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 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 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 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 parseControls(BerDecoder replyBer) throws IOException { // handle LDAPv3 controls (if present) if ((replyBer.bytesLeft() > 0) && (replyBer.peekByte() == LDAP_CONTROLS)) { Vector 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 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 - ]" * * where is a numeric error code * and 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 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 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); } }