1013 lines · 36.6 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.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.InterruptedIOException;import java.io.IOException;import java.io.OutputStream;import java.io.InputStream;import java.net.Socket; import javax.naming.CommunicationException;import javax.naming.ServiceUnavailableException;import javax.naming.NamingException;import javax.naming.InterruptedNamingException; import javax.naming.ldap.Control; import java.lang.reflect.Method;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.util.Arrays;import sun.misc.IOUtils;//import javax.net.SocketFactory; /**  * A thread that creates a connection to an LDAP server.  * After the connection, the thread reads from the connection.  * A caller can invoke methods on the instance to read LDAP responses  * and to send LDAP requests.  * <p>  * There is a one-to-one correspondence between an LdapClient and  * a Connection. Access to Connection and its methods is only via  * LdapClient with two exceptions: SASL authentication and StartTLS.  * SASL needs to access Connection's socket IO streams (in order to do encryption  * of the security layer). StartTLS needs to do replace IO streams  * and close the IO  streams on nonfatal close. The code for SASL  * authentication can be treated as being the same as from LdapClient  * because the SASL code is only ever called from LdapClient, from  * inside LdapClient's synchronized authenticate() method. StartTLS is called  * directly by the application but should only occur when the underlying  * connection is quiet.  * <p>  * In terms of synchronization, worry about data structures  * used by the Connection thread because that usage might contend  * with calls by the main threads (i.e., those that call LdapClient).  * Main threads need to worry about contention with each other.  * Fields that Connection thread uses:  *     inStream - synced access and update; initialized in constructor;  *           referenced outside class unsync'ed (by LdapSasl) only  *           when connection is quiet  *     traceFile, traceTagIn, traceTagOut - no sync; debugging only  *     parent - no sync; initialized in constructor; no updates  *     pendingRequests - sync  *     pauseLock - per-instance lock;  *     paused - sync via pauseLock (pauseReader())  * Members used by main threads (LdapClient):  *     host, port - unsync; read-only access for StartTLS and debug messages  *     setBound(), setV3() - no sync; called only by LdapClient.authenticate(),  *             which is a sync method called only when connection is "quiet"  *     getMsgId() - sync  *     writeRequest(), removeRequest(),findRequest(), abandonOutstandingReqs() -  *             access to shared pendingRequests is sync  *     writeRequest(),  abandonRequest(), ldapUnbind() - access to outStream sync  *     cleanup() - sync  *     readReply() - access to sock sync  *     unpauseReader() - (indirectly via writeRequest) sync on pauseLock  * Members used by SASL auth (main thread):  *     inStream, outStream - no sync; used to construct new stream; accessed  *             only when conn is "quiet" and not shared  *     replaceStreams() - sync method  * Members used by StartTLS:  *     inStream, outStream - no sync; used to record the existing streams;  *             accessed only when conn is "quiet" and not shared  *     replaceStreams() - sync method  * <p>  * Handles anonymous, simple, and SASL bind for v3; anonymous and simple  * for v2.  * %%% made public for access by LdapSasl %%%  *  * @author Vincent Ryan  * @author Rosanna Lee  * @author Jagane Sundar  */public final class Connection implements Runnable {     private static final boolean debug = false;    private static final int dump = 0; // > 0 r, > 1 rw      final private Thread worker;    // Initialized in constructor     private boolean v3 = true;       // Set in setV3()     final public String host;  // used by LdapClient for generating exception messages                         // used by StartTlsResponse when creating an SSL socket    final public int port;     // used by LdapClient for generating exception messages                         // used by StartTlsResponse when creating an SSL socket     private boolean bound = false;   // Set in setBound()     // All three are initialized in constructor and read-only afterwards    private OutputStream traceFile = null;    private String traceTagIn = null;    private String traceTagOut = null;     // Initialized in constructor; read and used externally (LdapSasl);    // Updated in replaceStreams() during "quiet", unshared, period    public InputStream inStream;   // must be public; used by LdapSasl     // Initialized in constructor; read and used externally (LdapSasl);    // Updated in replaceOutputStream() during "quiet", unshared, period    public OutputStream outStream; // must be public; used by LdapSasl     // Initialized in constructor; read and used externally (TLS) to    // get new IO streams; closed during cleanup    public Socket sock;            // for TLS     // For processing "disconnect" unsolicited notification    // Initialized in constructor    final private LdapClient parent;     // Incremented and returned in sync getMsgId()    private int outMsgId = 0;     //    // The list of ldapRequests pending on this binding    //    // Accessed only within sync methods    private LdapRequest pendingRequests = null;     volatile IOException closureReason = null;    volatile boolean useable = true;  // is Connection still useable     private int readTimeout;     // true means v3; false means v2    // Called in LdapClient.authenticate() (which is synchronized)    // when connection is "quiet" and not shared; no need to synchronize    void setV3(boolean v) {        v3 = v;    }     // A BIND request has been successfully made on this connection    // When cleaning up, remember to do an UNBIND    // Called in LdapClient.authenticate() (which is synchronized)    // when connection is "quiet" and not shared; no need to synchronize    void setBound() {        bound = true;    }     ////////////////////////////////////////////////////////////////////////////    //    // Create an LDAP Binding object and bind to a particular server    //    ////////////////////////////////////////////////////////////////////////////     Connection(LdapClient parent, String host, int port, String socketFactory,        int connectTimeout, int readTimeout, OutputStream trace) throws NamingException {         this.host = host;        this.port = port;        this.parent = parent;        this.readTimeout = readTimeout;         if (trace != null) {            traceFile = trace;            traceTagIn = "<- " + host + ":" + port + "\n\n";            traceTagOut = "-> " + host + ":" + port + "\n\n";        }         //        // Connect to server        //        try {            sock = createSocket(host, port, socketFactory, connectTimeout);             if (debug) {                System.err.println("Connection: opening socket: " + host + "," + port);            }             inStream = new BufferedInputStream(sock.getInputStream());            outStream = new BufferedOutputStream(sock.getOutputStream());         } catch (InvocationTargetException e) {            Throwable realException = e.getTargetException();            // realException.printStackTrace();             CommunicationException ce =                new CommunicationException(host + ":" + port);            ce.setRootCause(realException);            throw ce;        } catch (Exception e) {            // Class.forName() seems to do more error checking            // and will throw IllegalArgumentException and such.            // That's why we need to have a catch all here and            // ignore generic exceptions.            // Also catches all IO errors generated by socket creation.            CommunicationException ce =                new CommunicationException(host + ":" + port);            ce.setRootCause(e);            throw ce;        }         worker = Obj.helper.createThread(this);        worker.setDaemon(true);        worker.start();    }     /*     * Create an InetSocketAddress using the specified hostname and port number.     */    private Object createInetSocketAddress(String host, int port)            throws NoSuchMethodException {         try {            Class inetSocketAddressClass =                Class.forName("java.net.InetSocketAddress");             Constructor inetSocketAddressCons =                inetSocketAddressClass.getConstructor(new Class[]{                String.class, int.class});             return inetSocketAddressCons.newInstance(new Object[]{                host, new Integer(port)});         } catch (ClassNotFoundException e) {            throw new NoSuchMethodException();         } catch (InstantiationException e) {            throw new NoSuchMethodException();         } catch (InvocationTargetException e) {            throw new NoSuchMethodException();         } catch (IllegalAccessException e) {            throw new NoSuchMethodException();        }    }     /*     * Create a Socket object using the specified socket factory and time limit.     *     * If a timeout is supplied and unconnected sockets are supported then     * an unconnected socket is created and the timeout is applied when     * connecting the socket. If a timeout is supplied but unconnected sockets     * are not supported then the timeout is ignored and a connected socket     * is created.     */    private Socket createSocket(String host, int port, String socketFactory,            int connectTimeout) throws Exception {         Socket socket = null;         if (socketFactory != null) {             // create the factory             Class socketFactoryClass = Obj.helper.loadClass(socketFactory);            Method getDefault =                socketFactoryClass.getMethod("getDefault", new Class[]{});            Object factory = getDefault.invoke(null, new Object[]{});             // create the socket             Method createSocket = null;             if (connectTimeout > 0) {                 try {                    createSocket = socketFactoryClass.getMethod("createSocket",                        new Class[]{});                     Method connect = Socket.class.getMethod("connect",                        new Class[]{Class.forName("java.net.SocketAddress"),                        int.class});                    Object endpoint = createInetSocketAddress(host, port);                     // unconnected socket                    socket =                        (Socket)createSocket.invoke(factory, new Object[]{});                     if (debug) {                        System.err.println("Connection: creating socket with " +                            "a timeout using supplied socket factory");                    }                     // connected socket                    connect.invoke(socket, new Object[]{                        endpoint, new Integer(connectTimeout)});                 } catch (NoSuchMethodException e) {                    // continue (but ignore connectTimeout)                }            }             if (socket == null) {                createSocket = socketFactoryClass.getMethod("createSocket",                    new Class[]{String.class, int.class});                 if (debug) {                    System.err.println("Connection: creating socket using " +                        "supplied socket factory");                }                // connected socket                socket = (Socket) createSocket.invoke(factory,                    new Object[]{host, new Integer(port)});            }        } else {             if (connectTimeout > 0) {                 try {                    Constructor socketCons =                        Socket.class.getConstructor(new Class[]{});                     Method connect = Socket.class.getMethod("connect",                        new Class[]{Class.forName("java.net.SocketAddress"),                        int.class});                    Object endpoint = createInetSocketAddress(host, port);                     socket = (Socket) socketCons.newInstance(new Object[]{});                     if (debug) {                        System.err.println("Connection: creating socket with " +                            "a timeout");                    }                    connect.invoke(socket, new Object[]{                        endpoint, new Integer(connectTimeout)});                 } catch (NoSuchMethodException e) {                    // continue (but ignore connectTimeout)                }            }             if (socket == null) {                if (debug) {                    System.err.println("Connection: creating socket");                }                // connected socket                socket = new Socket(host, port);            }        }         return socket;    }     ////////////////////////////////////////////////////////////////////////////    //    // Methods to IO to the LDAP server    //    ////////////////////////////////////////////////////////////////////////////     synchronized int getMsgId() {        return ++outMsgId;    }     LdapRequest writeRequest(BerEncoder ber, int msgId) throws IOException {        return writeRequest(ber, msgId, false /* pauseAfterReceipt */, -1);    }     LdapRequest writeRequest(BerEncoder ber, int msgId,        boolean pauseAfterReceipt) throws IOException {        return writeRequest(ber, msgId, pauseAfterReceipt, -1);    }     LdapRequest writeRequest(BerEncoder ber, int msgId,        boolean pauseAfterReceipt, int replyQueueCapacity) throws IOException {         LdapRequest req =            new LdapRequest(msgId, pauseAfterReceipt, replyQueueCapacity);        addRequest(req);         if (traceFile != null) {            Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 0, ber.getDataLen());        }          // unpause reader so that it can get response        // NOTE: Must do this before writing request, otherwise might        // create a race condition where the writer unblocks its own response        unpauseReader();         if (debug) {            System.err.println("Writing request to: " + outStream);        }         try {            synchronized (this) {                outStream.write(ber.getBuf(), 0, ber.getDataLen());                outStream.flush();            }        } catch (IOException e) {            cleanup(null, true);            throw (closureReason = e); // rethrow        }         return req;    }     /**     * Reads a reply; waits until one is ready.     */    BerDecoder readReply(LdapRequest ldr)            throws IOException, NamingException {        BerDecoder rber;        boolean waited = false;         while (((rber = ldr.getReplyBer()) == null) && !waited) {            try {                // If socket closed, don't even try                synchronized (this) {                    if (sock == null) {                        throw new ServiceUnavailableException(host + ":" + port +                            "; socket closed");                    }                }                synchronized (ldr) {                    // check if condition has changed since our last check                    rber = ldr.getReplyBer();                    if (rber == null) {                        if (readTimeout > 0) {  // Socket read timeout is specified                             // will be woken up before readTimeout only if reply is                            // available                            ldr.wait(readTimeout);                            waited = true;                        } else {                            ldr.wait(15 * 1000); // 15 second timeout                        }                    } else {                        break;                    }                }            } catch (InterruptedException ex) {                throw new InterruptedNamingException(                    "Interrupted during LDAP operation");            }        }         if ((rber == null) && waited) {            removeRequest(ldr);            throw new NamingException("LDAP response read timed out, timeout used:"                            + readTimeout + "ms." );         }        return rber;    }      ////////////////////////////////////////////////////////////////////////////    //    // Methods to add, find, delete, and abandon requests made to server    //    ////////////////////////////////////////////////////////////////////////////     private synchronized void addRequest(LdapRequest ldapRequest) {         LdapRequest ldr = pendingRequests;        if (ldr == null) {            pendingRequests = ldapRequest;            ldapRequest.next = null;        } else {            ldapRequest.next = pendingRequests;            pendingRequests = ldapRequest;        }    }     synchronized LdapRequest findRequest(int msgId) {         LdapRequest ldr = pendingRequests;        while (ldr != null) {            if (ldr.msgId == msgId) {                return ldr;            }            ldr = ldr.next;

Lines 1–500 of 1013.