1603 lines · 58.7 KB · Java
RawDownload
/* * Copyright (c) 1999, 2011, 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.net.*;import java.io.*;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 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 {         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");        }         LdapResult res = null;         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;    }     /**     * 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);            }        }    }     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

Lines 1–500 of 1603.