1 /*
2  * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.jndi.ldap;
27 
28 import java.io.BufferedInputStream;
29 import java.io.BufferedOutputStream;
30 import java.io.InterruptedIOException;
31 import java.io.IOException;
32 import java.io.OutputStream;
33 import java.io.InputStream;
34 import java.net.InetSocketAddress;
35 import java.net.Socket;
36 import javax.net.ssl.SSLSocket;
37 
38 import javax.naming.CommunicationException;
39 import javax.naming.ServiceUnavailableException;
40 import javax.naming.NamingException;
41 import javax.naming.InterruptedNamingException;
42 
43 import javax.naming.ldap.Control;
44 
45 import java.lang.reflect.Method;
46 import java.lang.reflect.InvocationTargetException;
47 import java.security.AccessController;
48 import java.security.PrivilegedAction;
49 import java.util.Arrays;
50 import javax.net.SocketFactory;
51 import javax.net.ssl.SSLParameters;
52 
53 /**
54   * A thread that creates a connection to an LDAP server.
55   * After the connection, the thread reads from the connection.
56   * A caller can invoke methods on the instance to read LDAP responses
57   * and to send LDAP requests.
58   * <p>
59   * There is a one-to-one correspondence between an LdapClient and
60   * a Connection. Access to Connection and its methods is only via
61   * LdapClient with two exceptions: SASL authentication and StartTLS.
62   * SASL needs to access Connection's socket IO streams (in order to do encryption
63   * of the security layer). StartTLS needs to do replace IO streams
64   * and close the IO  streams on nonfatal close. The code for SASL
65   * authentication can be treated as being the same as from LdapClient
66   * because the SASL code is only ever called from LdapClient, from
67   * inside LdapClient's synchronized authenticate() method. StartTLS is called
68   * directly by the application but should only occur when the underlying
69   * connection is quiet.
70   * <p>
71   * In terms of synchronization, worry about data structures
72   * used by the Connection thread because that usage might contend
73   * with calls by the main threads (i.e., those that call LdapClient).
74   * Main threads need to worry about contention with each other.
75   * Fields that Connection thread uses:
76   *     inStream - synced access and update; initialized in constructor;
77   *           referenced outside class unsync'ed (by LdapSasl) only
78   *           when connection is quiet
79   *     traceFile, traceTagIn, traceTagOut - no sync; debugging only
80   *     parent - no sync; initialized in constructor; no updates
81   *     pendingRequests - sync
82   *     pauseLock - per-instance lock;
83   *     paused - sync via pauseLock (pauseReader())
84   * Members used by main threads (LdapClient):
85   *     host, port - unsync; read-only access for StartTLS and debug messages
86   *     setBound(), setV3() - no sync; called only by LdapClient.authenticate(),
87   *             which is a sync method called only when connection is "quiet"
88   *     getMsgId() - sync
89   *     writeRequest(), removeRequest(),findRequest(), abandonOutstandingReqs() -
90   *             access to shared pendingRequests is sync
91   *     writeRequest(),  abandonRequest(), ldapUnbind() - access to outStream sync
92   *     cleanup() - sync
93   *     readReply() - access to sock sync
94   *     unpauseReader() - (indirectly via writeRequest) sync on pauseLock
95   * Members used by SASL auth (main thread):
96   *     inStream, outStream - no sync; used to construct new stream; accessed
97   *             only when conn is "quiet" and not shared
98   *     replaceStreams() - sync method
99   * Members used by StartTLS:
100   *     inStream, outStream - no sync; used to record the existing streams;
101   *             accessed only when conn is "quiet" and not shared
102   *     replaceStreams() - sync method
103   * <p>
104   * Handles anonymous, simple, and SASL bind for v3; anonymous and simple
105   * for v2.
106   * %%% made public for access by LdapSasl %%%
107   *
108   * @author Vincent Ryan
109   * @author Rosanna Lee
110   * @author Jagane Sundar
111   */
112 public final class Connection implements Runnable {
113 
114     private static final boolean debug = false;
115     private static final int dump = 0; // > 0 r, > 1 rw
116 
117 
118     final private Thread worker;    // Initialized in constructor
119 
120     private boolean v3 = true;       // Set in setV3()
121 
122     final public String host;  // used by LdapClient for generating exception messages
123                          // used by StartTlsResponse when creating an SSL socket
124     final public int port;     // used by LdapClient for generating exception messages
125                          // used by StartTlsResponse when creating an SSL socket
126 
127     private boolean bound = false;   // Set in setBound()
128 
129     // All three are initialized in constructor and read-only afterwards
130     private OutputStream traceFile = null;
131     private String traceTagIn = null;
132     private String traceTagOut = null;
133 
134     // Initialized in constructor; read and used externally (LdapSasl);
135     // Updated in replaceStreams() during "quiet", unshared, period
136     public InputStream inStream;   // must be public; used by LdapSasl
137 
138     // Initialized in constructor; read and used externally (LdapSasl);
139     // Updated in replaceOutputStream() during "quiet", unshared, period
140     public OutputStream outStream; // must be public; used by LdapSasl
141 
142     // Initialized in constructor; read and used externally (TLS) to
143     // get new IO streams; closed during cleanup
144     public Socket sock;            // for TLS
145 
146     // For processing "disconnect" unsolicited notification
147     // Initialized in constructor
148     final private LdapClient parent;
149 
150     // Incremented and returned in sync getMsgId()
151     private int outMsgId = 0;
152 
153     //
154     // The list of ldapRequests pending on this binding
155     //
156     // Accessed only within sync methods
157     private LdapRequest pendingRequests = null;
158 
159     volatile IOException closureReason = null;
160     volatile boolean useable = true;  // is Connection still useable
161 
162     int readTimeout;
163     int connectTimeout;
164     private static final boolean IS_HOSTNAME_VERIFICATION_DISABLED
165             = hostnameVerificationDisabledValue();
166 
hostnameVerificationDisabledValue()167     private static boolean hostnameVerificationDisabledValue() {
168         PrivilegedAction<String> act = () -> System.getProperty(
169                 "com.sun.jndi.ldap.object.disableEndpointIdentification");
170         String prop = AccessController.doPrivileged(act);
171         if (prop == null) {
172             return false;
173         }
174         return prop.isEmpty() ? true : Boolean.parseBoolean(prop);
175     }
176     // true means v3; false means v2
177     // Called in LdapClient.authenticate() (which is synchronized)
178     // when connection is "quiet" and not shared; no need to synchronize
setV3(boolean v)179     void setV3(boolean v) {
180         v3 = v;
181     }
182 
183     // A BIND request has been successfully made on this connection
184     // When cleaning up, remember to do an UNBIND
185     // Called in LdapClient.authenticate() (which is synchronized)
186     // when connection is "quiet" and not shared; no need to synchronize
setBound()187     void setBound() {
188         bound = true;
189     }
190 
191     ////////////////////////////////////////////////////////////////////////////
192     //
193     // Create an LDAP Binding object and bind to a particular server
194     //
195     ////////////////////////////////////////////////////////////////////////////
196 
Connection(LdapClient parent, String host, int port, String socketFactory, int connectTimeout, int readTimeout, OutputStream trace)197     Connection(LdapClient parent, String host, int port, String socketFactory,
198         int connectTimeout, int readTimeout, OutputStream trace) throws NamingException {
199 
200         this.host = host;
201         this.port = port;
202         this.parent = parent;
203         this.readTimeout = readTimeout;
204         this.connectTimeout = connectTimeout;
205 
206         if (trace != null) {
207             traceFile = trace;
208             traceTagIn = "<- " + host + ":" + port + "\n\n";
209             traceTagOut = "-> " + host + ":" + port + "\n\n";
210         }
211 
212         //
213         // Connect to server
214         //
215         try {
216             sock = createSocket(host, port, socketFactory, connectTimeout);
217 
218             if (debug) {
219                 System.err.println("Connection: opening socket: " + host + "," + port);
220             }
221 
222             inStream = new BufferedInputStream(sock.getInputStream());
223             outStream = new BufferedOutputStream(sock.getOutputStream());
224 
225         } catch (InvocationTargetException e) {
226             Throwable realException = e.getTargetException();
227             // realException.printStackTrace();
228 
229             CommunicationException ce =
230                 new CommunicationException(host + ":" + port);
231             ce.setRootCause(realException);
232             throw ce;
233         } catch (Exception e) {
234             // We need to have a catch all here and
235             // ignore generic exceptions.
236             // Also catches all IO errors generated by socket creation.
237             CommunicationException ce =
238                 new CommunicationException(host + ":" + port);
239             ce.setRootCause(e);
240             throw ce;
241         }
242 
243         worker = Obj.helper.createThread(this);
244         worker.setDaemon(true);
245         worker.start();
246     }
247 
248     /*
249      * Create an InetSocketAddress using the specified hostname and port number.
250      */
createInetSocketAddress(String host, int port)251     private InetSocketAddress createInetSocketAddress(String host, int port) {
252             return new InetSocketAddress(host, port);
253     }
254 
255     /*
256      * Create a Socket object using the specified socket factory and time limit.
257      *
258      * If a timeout is supplied and unconnected sockets are supported then
259      * an unconnected socket is created and the timeout is applied when
260      * connecting the socket. If a timeout is supplied but unconnected sockets
261      * are not supported then the timeout is ignored and a connected socket
262      * is created.
263      */
createSocket(String host, int port, String socketFactory, int connectTimeout)264     private Socket createSocket(String host, int port, String socketFactory,
265             int connectTimeout) throws Exception {
266 
267         Socket socket = null;
268 
269         if (socketFactory != null) {
270 
271             // create the factory
272 
273             @SuppressWarnings("unchecked")
274             Class<? extends SocketFactory> socketFactoryClass =
275                 (Class<? extends SocketFactory>)Obj.helper.loadClass(socketFactory);
276             Method getDefault =
277                 socketFactoryClass.getMethod("getDefault", new Class<?>[]{});
278             SocketFactory factory = (SocketFactory) getDefault.invoke(null, new Object[]{});
279 
280             // create the socket
281 
282             if (connectTimeout > 0) {
283 
284                 InetSocketAddress endpoint =
285                         createInetSocketAddress(host, port);
286 
287                 // unconnected socket
288                 socket = factory.createSocket();
289 
290                 if (debug) {
291                     System.err.println("Connection: creating socket with " +
292                             "a timeout using supplied socket factory");
293                 }
294 
295                 // connected socket
296                 socket.connect(endpoint, connectTimeout);
297             }
298 
299             // continue (but ignore connectTimeout)
300             if (socket == null) {
301                 if (debug) {
302                     System.err.println("Connection: creating socket using " +
303                         "supplied socket factory");
304                 }
305                 // connected socket
306                 socket = factory.createSocket(host, port);
307             }
308         } else {
309 
310             if (connectTimeout > 0) {
311 
312                     InetSocketAddress endpoint = createInetSocketAddress(host, port);
313 
314                     socket = new Socket();
315 
316                     if (debug) {
317                         System.err.println("Connection: creating socket with " +
318                             "a timeout");
319                     }
320                     socket.connect(endpoint, connectTimeout);
321             }
322 
323             // continue (but ignore connectTimeout)
324 
325             if (socket == null) {
326                 if (debug) {
327                     System.err.println("Connection: creating socket");
328                 }
329                 // connected socket
330                 socket = new Socket(host, port);
331             }
332         }
333 
334         // For LDAP connect timeouts on LDAP over SSL connections must treat
335         // the SSL handshake following socket connection as part of the timeout.
336         // So explicitly set a socket read timeout, trigger the SSL handshake,
337         // then reset the timeout.
338         if (socket instanceof SSLSocket) {
339             SSLSocket sslSocket = (SSLSocket) socket;
340             if (!IS_HOSTNAME_VERIFICATION_DISABLED) {
341                 SSLParameters param = sslSocket.getSSLParameters();
342                 param.setEndpointIdentificationAlgorithm("LDAPS");
343                 sslSocket.setSSLParameters(param);
344             }
345             if (connectTimeout > 0) {
346                 int socketTimeout = sslSocket.getSoTimeout();
347                 sslSocket.setSoTimeout(connectTimeout); // reuse full timeout value
348                 sslSocket.startHandshake();
349                 sslSocket.setSoTimeout(socketTimeout);
350             }
351         }
352         return socket;
353     }
354 
355     ////////////////////////////////////////////////////////////////////////////
356     //
357     // Methods to IO to the LDAP server
358     //
359     ////////////////////////////////////////////////////////////////////////////
360 
getMsgId()361     synchronized int getMsgId() {
362         return ++outMsgId;
363     }
364 
writeRequest(BerEncoder ber, int msgId)365     LdapRequest writeRequest(BerEncoder ber, int msgId) throws IOException {
366         return writeRequest(ber, msgId, false /* pauseAfterReceipt */, -1);
367     }
368 
writeRequest(BerEncoder ber, int msgId, boolean pauseAfterReceipt)369     LdapRequest writeRequest(BerEncoder ber, int msgId,
370         boolean pauseAfterReceipt) throws IOException {
371         return writeRequest(ber, msgId, pauseAfterReceipt, -1);
372     }
373 
writeRequest(BerEncoder ber, int msgId, boolean pauseAfterReceipt, int replyQueueCapacity)374     LdapRequest writeRequest(BerEncoder ber, int msgId,
375         boolean pauseAfterReceipt, int replyQueueCapacity) throws IOException {
376 
377         LdapRequest req =
378             new LdapRequest(msgId, pauseAfterReceipt, replyQueueCapacity);
379         addRequest(req);
380 
381         if (traceFile != null) {
382             Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 0, ber.getDataLen());
383         }
384 
385 
386         // unpause reader so that it can get response
387         // NOTE: Must do this before writing request, otherwise might
388         // create a race condition where the writer unblocks its own response
389         unpauseReader();
390 
391         if (debug) {
392             System.err.println("Writing request to: " + outStream);
393         }
394 
395         try {
396             synchronized (this) {
397                 outStream.write(ber.getBuf(), 0, ber.getDataLen());
398                 outStream.flush();
399             }
400         } catch (IOException e) {
401             cleanup(null, true);
402             throw (closureReason = e); // rethrow
403         }
404 
405         return req;
406     }
407 
408     /**
409      * Reads a reply; waits until one is ready.
410      */
readReply(LdapRequest ldr)411     BerDecoder readReply(LdapRequest ldr)
412             throws IOException, NamingException {
413         BerDecoder rber;
414 
415         // Track down elapsed time to workaround spurious wakeups
416         long elapsedMilli = 0;
417         long elapsedNano = 0;
418 
419         while (((rber = ldr.getReplyBer()) == null) &&
420                 (readTimeout <= 0 || elapsedMilli < readTimeout))
421         {
422             try {
423                 // If socket closed, don't even try
424                 synchronized (this) {
425                     if (sock == null) {
426                         throw new ServiceUnavailableException(host + ":" + port +
427                             "; socket closed");
428                     }
429                 }
430                 synchronized (ldr) {
431                     // check if condition has changed since our last check
432                     rber = ldr.getReplyBer();
433                     if (rber == null) {
434                         if (readTimeout > 0) {  // Socket read timeout is specified
435                             long beginNano = System.nanoTime();
436 
437                             // will be woken up before readTimeout if reply is
438                             // available
439                             ldr.wait(readTimeout - elapsedMilli);
440                             elapsedNano += (System.nanoTime() - beginNano);
441                             elapsedMilli += elapsedNano / 1000_000;
442                             elapsedNano %= 1000_000;
443 
444                         } else {
445                             // no timeout is set so we wait infinitely until
446                             // a response is received
447                             // http://docs.oracle.com/javase/8/docs/technotes/guides/jndi/jndi-ldap.html#PROP
448                             ldr.wait();
449                         }
450                     } else {
451                         break;
452                     }
453                 }
454             } catch (InterruptedException ex) {
455                 throw new InterruptedNamingException(
456                     "Interrupted during LDAP operation");
457             }
458         }
459 
460         if ((rber == null) && (elapsedMilli >= readTimeout)) {
461             abandonRequest(ldr, null);
462             throw new NamingException("LDAP response read timed out, timeout used:"
463                             + readTimeout + "ms." );
464 
465         }
466         return rber;
467     }
468 
469 
470     ////////////////////////////////////////////////////////////////////////////
471     //
472     // Methods to add, find, delete, and abandon requests made to server
473     //
474     ////////////////////////////////////////////////////////////////////////////
475 
addRequest(LdapRequest ldapRequest)476     private synchronized void addRequest(LdapRequest ldapRequest) {
477 
478         LdapRequest ldr = pendingRequests;
479         if (ldr == null) {
480             pendingRequests = ldapRequest;
481             ldapRequest.next = null;
482         } else {
483             ldapRequest.next = pendingRequests;
484             pendingRequests = ldapRequest;
485         }
486     }
487 
findRequest(int msgId)488     synchronized LdapRequest findRequest(int msgId) {
489 
490         LdapRequest ldr = pendingRequests;
491         while (ldr != null) {
492             if (ldr.msgId == msgId) {
493                 return ldr;
494             }
495             ldr = ldr.next;
496         }
497         return null;
498 
499     }
500 
removeRequest(LdapRequest req)501     synchronized void removeRequest(LdapRequest req) {
502         LdapRequest ldr = pendingRequests;
503         LdapRequest ldrprev = null;
504 
505         while (ldr != null) {
506             if (ldr == req) {
507                 ldr.cancel();
508 
509                 if (ldrprev != null) {
510                     ldrprev.next = ldr.next;
511                 } else {
512                     pendingRequests = ldr.next;
513                 }
514                 ldr.next = null;
515             }
516             ldrprev = ldr;
517             ldr = ldr.next;
518         }
519     }
520 
abandonRequest(LdapRequest ldr, Control[] reqCtls)521     void abandonRequest(LdapRequest ldr, Control[] reqCtls) {
522         // Remove from queue
523         removeRequest(ldr);
524 
525         BerEncoder ber = new BerEncoder(256);
526         int abandonMsgId = getMsgId();
527 
528         //
529         // build the abandon request.
530         //
531         try {
532             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
533                 ber.encodeInt(abandonMsgId);
534                 ber.encodeInt(ldr.msgId, LdapClient.LDAP_REQ_ABANDON);
535 
536                 if (v3) {
537                     LdapClient.encodeControls(ber, reqCtls);
538                 }
539             ber.endSeq();
540 
541             if (traceFile != null) {
542                 Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 0,
543                     ber.getDataLen());
544             }
545 
546             synchronized (this) {
547                 outStream.write(ber.getBuf(), 0, ber.getDataLen());
548                 outStream.flush();
549             }
550 
551         } catch (IOException ex) {
552             //System.err.println("ldap.abandon: " + ex);
553         }
554 
555         // Don't expect any response for the abandon request.
556     }
557 
abandonOutstandingReqs(Control[] reqCtls)558     synchronized void abandonOutstandingReqs(Control[] reqCtls) {
559         LdapRequest ldr = pendingRequests;
560 
561         while (ldr != null) {
562             abandonRequest(ldr, reqCtls);
563             pendingRequests = ldr = ldr.next;
564         }
565     }
566 
567     ////////////////////////////////////////////////////////////////////////////
568     //
569     // Methods to unbind from server and clear up resources when object is
570     // destroyed.
571     //
572     ////////////////////////////////////////////////////////////////////////////
573 
ldapUnbind(Control[] reqCtls)574     private void ldapUnbind(Control[] reqCtls) {
575 
576         BerEncoder ber = new BerEncoder(256);
577         int unbindMsgId = getMsgId();
578 
579         //
580         // build the unbind request.
581         //
582 
583         try {
584 
585             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
586                 ber.encodeInt(unbindMsgId);
587                 // IMPLICIT TAGS
588                 ber.encodeByte(LdapClient.LDAP_REQ_UNBIND);
589                 ber.encodeByte(0);
590 
591                 if (v3) {
592                     LdapClient.encodeControls(ber, reqCtls);
593                 }
594             ber.endSeq();
595 
596             if (traceFile != null) {
597                 Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(),
598                     0, ber.getDataLen());
599             }
600 
601             synchronized (this) {
602                 outStream.write(ber.getBuf(), 0, ber.getDataLen());
603                 outStream.flush();
604             }
605 
606         } catch (IOException ex) {
607             //System.err.println("ldap.unbind: " + ex);
608         }
609 
610         // Don't expect any response for the unbind request.
611     }
612 
613     /**
614      * @param reqCtls Possibly null request controls that accompanies the
615      *    abandon and unbind LDAP request.
616      * @param notifyParent true means to call parent LdapClient back, notifying
617      *    it that the connection has been closed; false means not to notify
618      *    parent. If LdapClient invokes cleanup(), notifyParent should be set to
619      *    false because LdapClient already knows that it is closing
620      *    the connection. If Connection invokes cleanup(), notifyParent should be
621      *    set to true because LdapClient needs to know about the closure.
622      */
cleanup(Control[] reqCtls, boolean notifyParent)623     void cleanup(Control[] reqCtls, boolean notifyParent) {
624         boolean nparent = false;
625 
626         synchronized (this) {
627             useable = false;
628 
629             if (sock != null) {
630                 if (debug) {
631                     System.err.println("Connection: closing socket: " + host + "," + port);
632                 }
633                 try {
634                     if (!notifyParent) {
635                         abandonOutstandingReqs(reqCtls);
636                     }
637                     if (bound) {
638                         ldapUnbind(reqCtls);
639                     }
640                 } finally {
641                     try {
642                         outStream.flush();
643                         sock.close();
644                         unpauseReader();
645                     } catch (IOException ie) {
646                         if (debug)
647                             System.err.println("Connection: problem closing socket: " + ie);
648                     }
649                     if (!notifyParent) {
650                         LdapRequest ldr = pendingRequests;
651                         while (ldr != null) {
652                             ldr.cancel();
653                             ldr = ldr.next;
654                         }
655                     }
656                     sock = null;
657                 }
658                 nparent = notifyParent;
659             }
660             if (nparent) {
661                 LdapRequest ldr = pendingRequests;
662                 while (ldr != null) {
663 
664                     synchronized (ldr) {
665                         ldr.notify();
666                         ldr = ldr.next;
667                     }
668                 }
669             }
670         }
671         if (nparent) {
672             parent.processConnectionClosure();
673         }
674     }
675 
676 
677     // Assume everything is "quiet"
678     // "synchronize" might lead to deadlock so don't synchronize method
679     // Use streamLock instead for synchronizing update to stream
680 
replaceStreams(InputStream newIn, OutputStream newOut)681     synchronized public void replaceStreams(InputStream newIn, OutputStream newOut) {
682         if (debug) {
683             System.err.println("Replacing " + inStream + " with: " + newIn);
684             System.err.println("Replacing " + outStream + " with: " + newOut);
685         }
686 
687         inStream = newIn;
688 
689         // Cleanup old stream
690         try {
691             outStream.flush();
692         } catch (IOException ie) {
693             if (debug)
694                 System.err.println("Connection: cannot flush outstream: " + ie);
695         }
696 
697         // Replace stream
698         outStream = newOut;
699     }
700 
701     /**
702      * Used by Connection thread to read inStream into a local variable.
703      * This ensures that there is no contention between the main thread
704      * and the Connection thread when the main thread updates inStream.
705      */
getInputStream()706     synchronized private InputStream getInputStream() {
707         return inStream;
708     }
709 
710 
711     ////////////////////////////////////////////////////////////////////////////
712     //
713     // Code for pausing/unpausing the reader thread ('worker')
714     //
715     ////////////////////////////////////////////////////////////////////////////
716 
717     /*
718      * The main idea is to mark requests that need the reader thread to
719      * pause after getting the response. When the reader thread gets the response,
720      * it waits on a lock instead of returning to the read(). The next time a
721      * request is sent, the reader is automatically unblocked if necessary.
722      * Note that the reader must be unblocked BEFORE the request is sent.
723      * Otherwise, there is a race condition where the request is sent and
724      * the reader thread might read the response and be unblocked
725      * by writeRequest().
726      *
727      * This pause gives the main thread (StartTLS or SASL) an opportunity to
728      * update the reader's state (e.g., its streams) if necessary.
729      * The assumption is that the connection will remain quiet during this pause
730      * (i.e., no intervening requests being sent).
731      *<p>
732      * For dealing with StartTLS close,
733      * when the read() exits either due to EOF or an exception,
734      * the reader thread checks whether there is a new stream to read from.
735      * If so, then it reattempts the read. Otherwise, the EOF or exception
736      * is processed and the reader thread terminates.
737      * In a StartTLS close, the client first replaces the SSL IO streams with
738      * plain ones and then closes the SSL socket.
739      * If the reader thread attempts to read, or was reading, from
740      * the SSL socket (that is, it got to the read BEFORE replaceStreams()),
741      * the SSL socket close will cause the reader thread to
742      * get an EOF/exception and reexamine the input stream.
743      * If the reader thread sees a new stream, it reattempts the read.
744      * If the underlying socket is still alive, then the new read will succeed.
745      * If the underlying socket has been closed also, then the new read will
746      * fail and the reader thread exits.
747      * If the reader thread attempts to read, or was reading, from the plain
748      * socket (that is, it got to the read AFTER replaceStreams()), the
749      * SSL socket close will have no effect on the reader thread.
750      *
751      * The check for new stream is made only
752      * in the first attempt at reading a BER buffer; the reader should
753      * never be in midst of reading a buffer when a nonfatal close occurs.
754      * If this occurs, then the connection is in an inconsistent state and
755      * the safest thing to do is to shut it down.
756      */
757 
758     private Object pauseLock = new Object();  // lock for reader to wait on while paused
759     private boolean paused = false;           // paused state of reader
760 
761     /*
762      * Unpauses reader thread if it was paused
763      */
unpauseReader()764     private void unpauseReader() throws IOException {
765         synchronized (pauseLock) {
766             if (paused) {
767                 if (debug) {
768                     System.err.println("Unpausing reader; read from: " +
769                                         inStream);
770                 }
771                 paused = false;
772                 pauseLock.notify();
773             }
774         }
775     }
776 
777      /*
778      * Pauses reader so that it stops reading from the input stream.
779      * Reader blocks on pauseLock instead of read().
780      * MUST be called from within synchronized (pauseLock) clause.
781      */
pauseReader()782     private void pauseReader() throws IOException {
783         if (debug) {
784             System.err.println("Pausing reader;  was reading from: " +
785                                 inStream);
786         }
787         paused = true;
788         try {
789             while (paused) {
790                 pauseLock.wait(); // notified by unpauseReader
791             }
792         } catch (InterruptedException e) {
793             throw new InterruptedIOException(
794                     "Pause/unpause reader has problems.");
795         }
796     }
797 
798 
799     ////////////////////////////////////////////////////////////////////////////
800     //
801     // The LDAP Binding thread. It does the mux/demux of multiple requests
802     // on the same TCP connection.
803     //
804     ////////////////////////////////////////////////////////////////////////////
805 
806 
run()807     public void run() {
808         byte inbuf[];   // Buffer for reading incoming bytes
809         int inMsgId;    // Message id of incoming response
810         int bytesread;  // Number of bytes in inbuf
811         int br;         // Temp; number of bytes read from stream
812         int offset;     // Offset of where to store bytes in inbuf
813         int seqlen;     // Length of ASN sequence
814         int seqlenlen;  // Number of sequence length bytes
815         boolean eos;    // End of stream
816         BerDecoder retBer;    // Decoder for ASN.1 BER data from inbuf
817         InputStream in = null;
818 
819         try {
820             while (true) {
821                 try {
822                     // type and length (at most 128 octets for long form)
823                     inbuf = new byte[129];
824 
825                     offset = 0;
826                     seqlen = 0;
827                     seqlenlen = 0;
828 
829                     in = getInputStream();
830 
831                     // check that it is the beginning of a sequence
832                     bytesread = in.read(inbuf, offset, 1);
833                     if (bytesread < 0) {
834                         if (in != getInputStream()) {
835                             continue;   // a new stream to try
836                         } else {
837                             break; // EOF
838                         }
839                     }
840 
841                     if (inbuf[offset++] != (Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR))
842                         continue;
843 
844                     // get length of sequence
845                     bytesread = in.read(inbuf, offset, 1);
846                     if (bytesread < 0)
847                         break; // EOF
848                     seqlen = inbuf[offset++];
849 
850                     // if high bit is on, length is encoded in the
851                     // subsequent length bytes and the number of length bytes
852                     // is equal to & 0x80 (i.e. length byte with high bit off).
853                     if ((seqlen & 0x80) == 0x80) {
854                         seqlenlen = seqlen & 0x7f;  // number of length bytes
855 
856                         bytesread = 0;
857                         eos = false;
858 
859                         // Read all length bytes
860                         while (bytesread < seqlenlen) {
861                             br = in.read(inbuf, offset+bytesread,
862                                 seqlenlen-bytesread);
863                             if (br < 0) {
864                                 eos = true;
865                                 break; // EOF
866                             }
867                             bytesread += br;
868                         }
869 
870                         // end-of-stream reached before length bytes are read
871                         if (eos)
872                             break;  // EOF
873 
874                         // Add contents of length bytes to determine length
875                         seqlen = 0;
876                         for( int i = 0; i < seqlenlen; i++) {
877                             seqlen = (seqlen << 8) + (inbuf[offset+i] & 0xff);
878                         }
879                         offset += bytesread;
880                     }
881 
882                     // read in seqlen bytes
883                     byte[] left = readFully(in, seqlen);
884                     inbuf = Arrays.copyOf(inbuf, offset + left.length);
885                     System.arraycopy(left, 0, inbuf, offset, left.length);
886                     offset += left.length;
887 /*
888 if (dump > 0) {
889 System.err.println("seqlen: " + seqlen);
890 System.err.println("bufsize: " + offset);
891 System.err.println("bytesleft: " + bytesleft);
892 System.err.println("bytesread: " + bytesread);
893 }
894 */
895 
896 
897                     try {
898                         retBer = new BerDecoder(inbuf, 0, offset);
899 
900                         if (traceFile != null) {
901                             Ber.dumpBER(traceFile, traceTagIn, inbuf, 0, offset);
902                         }
903 
904                         retBer.parseSeq(null);
905                         inMsgId = retBer.parseInt();
906                         retBer.reset(); // reset offset
907 
908                         boolean needPause = false;
909 
910                         if (inMsgId == 0) {
911                             // Unsolicited Notification
912                             parent.processUnsolicited(retBer);
913                         } else {
914                             LdapRequest ldr = findRequest(inMsgId);
915 
916                             if (ldr != null) {
917 
918                                 /**
919                                  * Grab pauseLock before making reply available
920                                  * to ensure that reader goes into paused state
921                                  * before writer can attempt to unpause reader
922                                  */
923                                 synchronized (pauseLock) {
924                                     needPause = ldr.addReplyBer(retBer);
925                                     if (needPause) {
926                                         /*
927                                          * Go into paused state; release
928                                          * pauseLock
929                                          */
930                                         pauseReader();
931                                     }
932 
933                                     // else release pauseLock
934                                 }
935                             } else {
936                                 // System.err.println("Cannot find" +
937                                 //              "LdapRequest for " + inMsgId);
938                             }
939                         }
940                     } catch (Ber.DecodeException e) {
941                         //System.err.println("Cannot parse Ber");
942                     }
943                 } catch (IOException ie) {
944                     if (debug) {
945                         System.err.println("Connection: Inside Caught " + ie);
946                         ie.printStackTrace();
947                     }
948 
949                     if (in != getInputStream()) {
950                         // A new stream to try
951                         // Go to top of loop and continue
952                     } else {
953                         if (debug) {
954                             System.err.println("Connection: rethrowing " + ie);
955                         }
956                         throw ie;  // rethrow exception
957                     }
958                 }
959             }
960 
961             if (debug) {
962                 System.err.println("Connection: end-of-stream detected: "
963                     + in);
964             }
965         } catch (IOException ex) {
966             if (debug) {
967                 System.err.println("Connection: Caught " + ex);
968             }
969             closureReason = ex;
970         } finally {
971             cleanup(null, true); // cleanup
972         }
973         if (debug) {
974             System.err.println("Connection: Thread Exiting");
975         }
976     }
977 
readFully(InputStream is, int length)978     private static byte[] readFully(InputStream is, int length)
979         throws IOException
980     {
981         byte[] buf = new byte[Math.min(length, 8192)];
982         int nread = 0;
983         while (nread < length) {
984             int bytesToRead;
985             if (nread >= buf.length) {  // need to allocate a larger buffer
986                 bytesToRead = Math.min(length - nread, buf.length + 8192);
987                 if (buf.length < nread + bytesToRead) {
988                     buf = Arrays.copyOf(buf, nread + bytesToRead);
989                 }
990             } else {
991                 bytesToRead = buf.length - nread;
992             }
993             int count = is.read(buf, nread, bytesToRead);
994             if (count < 0) {
995                 if (buf.length != nread)
996                     buf = Arrays.copyOf(buf, nread);
997                 break;
998             }
999             nread += count;
1000         }
1001         return buf;
1002     }
1003 
1004     // This code must be uncommented to run the LdapAbandonTest.
1005     /*public void sendSearchReqs(String dn, int numReqs) {
1006         int i;
1007         String attrs[] = null;
1008         for(i = 1; i <= numReqs; i++) {
1009             BerEncoder ber = new BerEncoder(2048);
1010 
1011             try {
1012             ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1013                 ber.encodeInt(i);
1014                 ber.beginSeq(LdapClient.LDAP_REQ_SEARCH);
1015                     ber.encodeString(dn == null ? "" : dn);
1016                     ber.encodeInt(0, LdapClient.LBER_ENUMERATED);
1017                     ber.encodeInt(3, LdapClient.LBER_ENUMERATED);
1018                     ber.encodeInt(0);
1019                     ber.encodeInt(0);
1020                     ber.encodeBoolean(true);
1021                     LdapClient.encodeFilter(ber, "");
1022                     ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR);
1023                         ber.encodeStringArray(attrs);
1024                     ber.endSeq();
1025                 ber.endSeq();
1026             ber.endSeq();
1027             writeRequest(ber, i);
1028             //System.err.println("wrote request " + i);
1029             } catch (Exception ex) {
1030             //System.err.println("ldap.search: Caught " + ex + " building req");
1031             }
1032 
1033         }
1034     } */
1035 }
1036