1 /*
2  * Copyright (c) 1999, 2021, 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.security.cert.Certificate;
50 import java.security.cert.X509Certificate;
51 import java.util.Arrays;
52 import java.util.concurrent.CompletableFuture;
53 import java.util.concurrent.ExecutionException;
54 import javax.net.SocketFactory;
55 import javax.net.ssl.SSLParameters;
56 import javax.net.ssl.HandshakeCompletedEvent;
57 import javax.net.ssl.HandshakeCompletedListener;
58 import javax.net.ssl.SSLPeerUnverifiedException;
59 import javax.security.sasl.SaslException;
60 
61 /**
62   * A thread that creates a connection to an LDAP server.
63   * After the connection, the thread reads from the connection.
64   * A caller can invoke methods on the instance to read LDAP responses
65   * and to send LDAP requests.
66   * <p>
67   * There is a one-to-one correspondence between an LdapClient and
68   * a Connection. Access to Connection and its methods is only via
69   * LdapClient with two exceptions: SASL authentication and StartTLS.
70   * SASL needs to access Connection's socket IO streams (in order to do encryption
71   * of the security layer). StartTLS needs to do replace IO streams
72   * and close the IO  streams on nonfatal close. The code for SASL
73   * authentication can be treated as being the same as from LdapClient
74   * because the SASL code is only ever called from LdapClient, from
75   * inside LdapClient's synchronized authenticate() method. StartTLS is called
76   * directly by the application but should only occur when the underlying
77   * connection is quiet.
78   * <p>
79   * In terms of synchronization, worry about data structures
80   * used by the Connection thread because that usage might contend
81   * with calls by the main threads (i.e., those that call LdapClient).
82   * Main threads need to worry about contention with each other.
83   * Fields that Connection thread uses:
84   *     inStream - synced access and update; initialized in constructor;
85   *           referenced outside class unsync'ed (by LdapSasl) only
86   *           when connection is quiet
87   *     traceFile, traceTagIn, traceTagOut - no sync; debugging only
88   *     parent - no sync; initialized in constructor; no updates
89   *     pendingRequests - sync
90   *     pauseLock - per-instance lock;
91   *     paused - sync via pauseLock (pauseReader())
92   * Members used by main threads (LdapClient):
93   *     host, port - unsync; read-only access for StartTLS and debug messages
94   *     setBound(), setV3() - no sync; called only by LdapClient.authenticate(),
95   *             which is a sync method called only when connection is "quiet"
96   *     getMsgId() - sync
97   *     writeRequest(), removeRequest(),findRequest(), abandonOutstandingReqs() -
98   *             access to shared pendingRequests is sync
99   *     writeRequest(),  abandonRequest(), ldapUnbind() - access to outStream sync
100   *     cleanup() - sync
101   *     readReply() - access to sock sync
102   *     unpauseReader() - (indirectly via writeRequest) sync on pauseLock
103   * Members used by SASL auth (main thread):
104   *     inStream, outStream - no sync; used to construct new stream; accessed
105   *             only when conn is "quiet" and not shared
106   *     replaceStreams() - sync method
107   * Members used by StartTLS:
108   *     inStream, outStream - no sync; used to record the existing streams;
109   *             accessed only when conn is "quiet" and not shared
110   *     replaceStreams() - sync method
111   * <p>
112   * Handles anonymous, simple, and SASL bind for v3; anonymous and simple
113   * for v2.
114   * %%% made public for access by LdapSasl %%%
115   *
116   * @author Vincent Ryan
117   * @author Rosanna Lee
118   * @author Jagane Sundar
119   */
120 public final class Connection implements Runnable {
121 
122     private static final boolean debug = false;
123     private static final int dump = 0; // > 0 r, > 1 rw
124 
125 
126     final private Thread worker;    // Initialized in constructor
127 
128     private boolean v3 = true;       // Set in setV3()
129 
130     final public String host;  // used by LdapClient for generating exception messages
131                          // used by StartTlsResponse when creating an SSL socket
132     final public int port;     // used by LdapClient for generating exception messages
133                          // used by StartTlsResponse when creating an SSL socket
134 
135     private boolean bound = false;   // Set in setBound()
136 
137     // All three are initialized in constructor and read-only afterwards
138     private OutputStream traceFile = null;
139     private String traceTagIn = null;
140     private String traceTagOut = null;
141 
142     // Initialized in constructor; read and used externally (LdapSasl);
143     // Updated in replaceStreams() during "quiet", unshared, period
144     public InputStream inStream;   // must be public; used by LdapSasl
145 
146     // Initialized in constructor; read and used externally (LdapSasl);
147     // Updated in replaceOutputStream() during "quiet", unshared, period
148     public OutputStream outStream; // must be public; used by LdapSasl
149 
150     // Initialized in constructor; read and used externally (TLS) to
151     // get new IO streams; closed during cleanup
152     public Socket sock;            // for TLS
153 
154     // For processing "disconnect" unsolicited notification
155     // Initialized in constructor
156     final private LdapClient parent;
157 
158     // Incremented and returned in sync getMsgId()
159     private int outMsgId = 0;
160 
161     //
162     // The list of ldapRequests pending on this binding
163     //
164     // Accessed only within sync methods
165     private LdapRequest pendingRequests = null;
166 
167     volatile IOException closureReason = null;
168     volatile boolean useable = true;  // is Connection still useable
169 
170     int readTimeout;
171     int connectTimeout;
172 
173     // Is connection upgraded to SSL via STARTTLS extended operation
174     private volatile boolean isUpgradedToStartTls;
175 
176     // Lock to maintain isUpgradedToStartTls state
177     final Object startTlsLock = new Object();
178 
179     private static final boolean IS_HOSTNAME_VERIFICATION_DISABLED
180             = hostnameVerificationDisabledValue();
181 
hostnameVerificationDisabledValue()182     private static boolean hostnameVerificationDisabledValue() {
183         PrivilegedAction<String> act = () -> System.getProperty(
184                 "com.sun.jndi.ldap.object.disableEndpointIdentification");
185         String prop = AccessController.doPrivileged(act);
186         if (prop == null) {
187             return false;
188         }
189         return prop.isEmpty() ? true : Boolean.parseBoolean(prop);
190     }
191     // true means v3; false means v2
192     // Called in LdapClient.authenticate() (which is synchronized)
193     // when connection is "quiet" and not shared; no need to synchronize
setV3(boolean v)194     void setV3(boolean v) {
195         v3 = v;
196     }
197 
198     // A BIND request has been successfully made on this connection
199     // When cleaning up, remember to do an UNBIND
200     // Called in LdapClient.authenticate() (which is synchronized)
201     // when connection is "quiet" and not shared; no need to synchronize
setBound()202     void setBound() {
203         bound = true;
204     }
205 
206     ////////////////////////////////////////////////////////////////////////////
207     //
208     // Create an LDAP Binding object and bind to a particular server
209     //
210     ////////////////////////////////////////////////////////////////////////////
211 
Connection(LdapClient parent, String host, int port, String socketFactory, int connectTimeout, int readTimeout, OutputStream trace)212     Connection(LdapClient parent, String host, int port, String socketFactory,
213         int connectTimeout, int readTimeout, OutputStream trace) throws NamingException {
214 
215         this.host = host;
216         this.port = port;
217         this.parent = parent;
218         this.readTimeout = readTimeout;
219         this.connectTimeout = connectTimeout;
220 
221         if (trace != null) {
222             traceFile = trace;
223             traceTagIn = "<- " + host + ":" + port + "\n\n";
224             traceTagOut = "-> " + host + ":" + port + "\n\n";
225         }
226 
227         //
228         // Connect to server
229         //
230         try {
231             sock = createSocket(host, port, socketFactory, connectTimeout);
232 
233             if (debug) {
234                 System.err.println("Connection: opening socket: " + host + "," + port);
235             }
236 
237             inStream = new BufferedInputStream(sock.getInputStream());
238             outStream = new BufferedOutputStream(sock.getOutputStream());
239 
240         } catch (InvocationTargetException e) {
241             Throwable realException = e.getTargetException();
242             // realException.printStackTrace();
243 
244             CommunicationException ce =
245                 new CommunicationException(host + ":" + port);
246             ce.setRootCause(realException);
247             throw ce;
248         } catch (Exception e) {
249             // We need to have a catch all here and
250             // ignore generic exceptions.
251             // Also catches all IO errors generated by socket creation.
252             CommunicationException ce =
253                 new CommunicationException(host + ":" + port);
254             ce.setRootCause(e);
255             throw ce;
256         }
257 
258         worker = Obj.helper.createThread(this);
259         worker.setDaemon(true);
260         worker.start();
261     }
262 
263     /*
264      * Create an InetSocketAddress using the specified hostname and port number.
265      */
createInetSocketAddress(String host, int port)266     private InetSocketAddress createInetSocketAddress(String host, int port) {
267             return new InetSocketAddress(host, port);
268     }
269 
270     /*
271      * Create a Socket object using the specified socket factory and time limit.
272      *
273      * If a timeout is supplied and unconnected sockets are supported then
274      * an unconnected socket is created and the timeout is applied when
275      * connecting the socket. If a timeout is supplied but unconnected sockets
276      * are not supported then the timeout is ignored and a connected socket
277      * is created.
278      */
createSocket(String host, int port, String socketFactory, int connectTimeout)279     private Socket createSocket(String host, int port, String socketFactory,
280             int connectTimeout) throws Exception {
281 
282         Socket socket = null;
283 
284         if (socketFactory != null) {
285 
286             // create the factory
287 
288             @SuppressWarnings("unchecked")
289             Class<? extends SocketFactory> socketFactoryClass =
290                 (Class<? extends SocketFactory>)Obj.helper.loadClass(socketFactory);
291             Method getDefault =
292                 socketFactoryClass.getMethod("getDefault", new Class<?>[]{});
293             SocketFactory factory = (SocketFactory) getDefault.invoke(null, new Object[]{});
294 
295             // create the socket
296 
297             if (connectTimeout > 0) {
298 
299                 InetSocketAddress endpoint =
300                         createInetSocketAddress(host, port);
301 
302                 // unconnected socket
303                 socket = factory.createSocket();
304 
305                 if (debug) {
306                     System.err.println("Connection: creating socket with " +
307                             "a timeout using supplied socket factory");
308                 }
309 
310                 // connected socket
311                 socket.connect(endpoint, connectTimeout);
312             }
313 
314             // continue (but ignore connectTimeout)
315             if (socket == null) {
316                 if (debug) {
317                     System.err.println("Connection: creating socket using " +
318                         "supplied socket factory");
319                 }
320                 // connected socket
321                 socket = factory.createSocket(host, port);
322             }
323         } else {
324 
325             if (connectTimeout > 0) {
326 
327                     InetSocketAddress endpoint = createInetSocketAddress(host, port);
328 
329                     socket = new Socket();
330 
331                     if (debug) {
332                         System.err.println("Connection: creating socket with " +
333                             "a timeout");
334                     }
335                     socket.connect(endpoint, connectTimeout);
336             }
337 
338             // continue (but ignore connectTimeout)
339 
340             if (socket == null) {
341                 if (debug) {
342                     System.err.println("Connection: creating socket");
343                 }
344                 // connected socket
345                 socket = new Socket(host, port);
346             }
347         }
348 
349         // For LDAP connect timeouts on LDAP over SSL connections must treat
350         // the SSL handshake following socket connection as part of the timeout.
351         // So explicitly set a socket read timeout, trigger the SSL handshake,
352         // then reset the timeout.
353         if (socket instanceof SSLSocket) {
354             SSLSocket sslSocket = (SSLSocket) socket;
355             if (!IS_HOSTNAME_VERIFICATION_DISABLED) {
356                 SSLParameters param = sslSocket.getSSLParameters();
357                 param.setEndpointIdentificationAlgorithm("LDAPS");
358                 sslSocket.setSSLParameters(param);
359             }
360             setHandshakeCompletedListener(sslSocket);
361             if (connectTimeout > 0) {
362                 int socketTimeout = sslSocket.getSoTimeout();
363                 sslSocket.setSoTimeout(connectTimeout); // reuse full timeout value
364                 sslSocket.startHandshake();
365                 sslSocket.setSoTimeout(socketTimeout);
366             }
367         }
368         return socket;
369     }
370 
371     ////////////////////////////////////////////////////////////////////////////
372     //
373     // Methods to IO to the LDAP server
374     //
375     ////////////////////////////////////////////////////////////////////////////
376 
getMsgId()377     synchronized int getMsgId() {
378         return ++outMsgId;
379     }
380 
writeRequest(BerEncoder ber, int msgId)381     LdapRequest writeRequest(BerEncoder ber, int msgId) throws IOException {
382         return writeRequest(ber, msgId, false /* pauseAfterReceipt */, -1);
383     }
384 
writeRequest(BerEncoder ber, int msgId, boolean pauseAfterReceipt)385     LdapRequest writeRequest(BerEncoder ber, int msgId,
386         boolean pauseAfterReceipt) throws IOException {
387         return writeRequest(ber, msgId, pauseAfterReceipt, -1);
388     }
389 
writeRequest(BerEncoder ber, int msgId, boolean pauseAfterReceipt, int replyQueueCapacity)390     LdapRequest writeRequest(BerEncoder ber, int msgId,
391         boolean pauseAfterReceipt, int replyQueueCapacity) throws IOException {
392 
393         LdapRequest req =
394             new LdapRequest(msgId, pauseAfterReceipt, replyQueueCapacity);
395         addRequest(req);
396 
397         if (traceFile != null) {
398             Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 0, ber.getDataLen());
399         }
400 
401 
402         // unpause reader so that it can get response
403         // NOTE: Must do this before writing request, otherwise might
404         // create a race condition where the writer unblocks its own response
405         unpauseReader();
406 
407         if (debug) {
408             System.err.println("Writing request to: " + outStream);
409         }
410 
411         try {
412             synchronized (this) {
413                 outStream.write(ber.getBuf(), 0, ber.getDataLen());
414                 outStream.flush();
415             }
416         } catch (IOException e) {
417             cleanup(null, true);
418             throw (closureReason = e); // rethrow
419         }
420 
421         return req;
422     }
423 
424     /**
425      * Reads a reply; waits until one is ready.
426      */
readReply(LdapRequest ldr)427     BerDecoder readReply(LdapRequest ldr) throws IOException, NamingException {
428         BerDecoder rber;
429 
430         // If socket closed, don't even try
431         synchronized (this) {
432             if (sock == null) {
433                 throw new ServiceUnavailableException(host + ":" + port +
434                     "; socket closed");
435             }
436         }
437 
438         NamingException namingException = null;
439         try {
440             // if no timeout is set so we wait infinitely until
441             // a response is received OR until the connection is closed or cancelled
442             // http://docs.oracle.com/javase/8/docs/technotes/guides/jndi/jndi-ldap.html#PROP
443             rber = ldr.getReplyBer(readTimeout);
444         } catch (InterruptedException ex) {
445             throw new InterruptedNamingException(
446                 "Interrupted during LDAP operation");
447         } catch (CommunicationException ce) {
448             // Re-throw
449             throw ce;
450         } catch (NamingException ne) {
451             // Connection is timed out OR closed/cancelled
452             namingException = ne;
453             rber = null;
454         }
455 
456         if (rber == null) {
457             abandonRequest(ldr, null);
458         }
459         // namingException can be not null in the following cases:
460         //  a) The response is timed-out
461         //  b) LDAP request connection has been closed or cancelled
462         // The exception message is initialized in LdapRequest::getReplyBer
463         if (namingException != null) {
464             // Re-throw NamingException after all cleanups are done
465             throw namingException;
466         }
467         return rber;
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                     if (isTlsConnection() && tlsHandshakeListener != null) {
657                         if (closureReason != null) {
658                             CommunicationException ce = new CommunicationException();
659                             ce.setRootCause(closureReason);
660                             tlsHandshakeListener.tlsHandshakeCompleted.completeExceptionally(ce);
661                         } else {
662                             tlsHandshakeListener.tlsHandshakeCompleted.cancel(false);
663                         }
664                     }
665                     sock = null;
666                 }
667                 nparent = notifyParent;
668             }
669             if (nparent) {
670                 LdapRequest ldr = pendingRequests;
671                 while (ldr != null) {
672                     ldr.close();
673                         ldr = ldr.next;
674                     }
675                 }
676             }
677         if (nparent) {
678             parent.processConnectionClosure();
679         }
680     }
681 
682 
683     // Assume everything is "quiet"
684     // "synchronize" might lead to deadlock so don't synchronize method
685     // Use streamLock instead for synchronizing update to stream
686 
replaceStreams(InputStream newIn, OutputStream newOut)687     synchronized public void replaceStreams(InputStream newIn, OutputStream newOut) {
688         if (debug) {
689             System.err.println("Replacing " + inStream + " with: " + newIn);
690             System.err.println("Replacing " + outStream + " with: " + newOut);
691         }
692 
693         inStream = newIn;
694 
695         // Cleanup old stream
696         try {
697             outStream.flush();
698         } catch (IOException ie) {
699             if (debug)
700                 System.err.println("Connection: cannot flush outstream: " + ie);
701         }
702 
703         // Replace stream
704         outStream = newOut;
705     }
706 
707     /*
708      * Replace streams and set isUpdradedToStartTls flag to the provided value
709      */
replaceStreams(InputStream newIn, OutputStream newOut, boolean isStartTls)710     synchronized public void replaceStreams(InputStream newIn, OutputStream newOut, boolean isStartTls) {
711         synchronized (startTlsLock) {
712             replaceStreams(newIn, newOut);
713             isUpgradedToStartTls = isStartTls;
714         }
715     }
716 
717     /*
718      * Returns true if connection was upgraded to SSL with STARTTLS extended operation
719      */
isUpgradedToStartTls()720     public boolean isUpgradedToStartTls() {
721         return isUpgradedToStartTls;
722     }
723 
724     /**
725      * Used by Connection thread to read inStream into a local variable.
726      * This ensures that there is no contention between the main thread
727      * and the Connection thread when the main thread updates inStream.
728      */
getInputStream()729     synchronized private InputStream getInputStream() {
730         return inStream;
731     }
732 
733 
734     ////////////////////////////////////////////////////////////////////////////
735     //
736     // Code for pausing/unpausing the reader thread ('worker')
737     //
738     ////////////////////////////////////////////////////////////////////////////
739 
740     /*
741      * The main idea is to mark requests that need the reader thread to
742      * pause after getting the response. When the reader thread gets the response,
743      * it waits on a lock instead of returning to the read(). The next time a
744      * request is sent, the reader is automatically unblocked if necessary.
745      * Note that the reader must be unblocked BEFORE the request is sent.
746      * Otherwise, there is a race condition where the request is sent and
747      * the reader thread might read the response and be unblocked
748      * by writeRequest().
749      *
750      * This pause gives the main thread (StartTLS or SASL) an opportunity to
751      * update the reader's state (e.g., its streams) if necessary.
752      * The assumption is that the connection will remain quiet during this pause
753      * (i.e., no intervening requests being sent).
754      *<p>
755      * For dealing with StartTLS close,
756      * when the read() exits either due to EOF or an exception,
757      * the reader thread checks whether there is a new stream to read from.
758      * If so, then it reattempts the read. Otherwise, the EOF or exception
759      * is processed and the reader thread terminates.
760      * In a StartTLS close, the client first replaces the SSL IO streams with
761      * plain ones and then closes the SSL socket.
762      * If the reader thread attempts to read, or was reading, from
763      * the SSL socket (that is, it got to the read BEFORE replaceStreams()),
764      * the SSL socket close will cause the reader thread to
765      * get an EOF/exception and reexamine the input stream.
766      * If the reader thread sees a new stream, it reattempts the read.
767      * If the underlying socket is still alive, then the new read will succeed.
768      * If the underlying socket has been closed also, then the new read will
769      * fail and the reader thread exits.
770      * If the reader thread attempts to read, or was reading, from the plain
771      * socket (that is, it got to the read AFTER replaceStreams()), the
772      * SSL socket close will have no effect on the reader thread.
773      *
774      * The check for new stream is made only
775      * in the first attempt at reading a BER buffer; the reader should
776      * never be in midst of reading a buffer when a nonfatal close occurs.
777      * If this occurs, then the connection is in an inconsistent state and
778      * the safest thing to do is to shut it down.
779      */
780 
781     private final Object pauseLock = new Object();  // lock for reader to wait on while paused
782     private boolean paused = false;           // paused state of reader
783 
784     /*
785      * Unpauses reader thread if it was paused
786      */
unpauseReader()787     private void unpauseReader() throws IOException {
788         synchronized (pauseLock) {
789             if (paused) {
790                 if (debug) {
791                     System.err.println("Unpausing reader; read from: " +
792                                         inStream);
793                 }
794                 paused = false;
795                 pauseLock.notify();
796             }
797         }
798     }
799 
800      /*
801      * Pauses reader so that it stops reading from the input stream.
802      * Reader blocks on pauseLock instead of read().
803      * MUST be called from within synchronized (pauseLock) clause.
804      */
pauseReader()805     private void pauseReader() throws IOException {
806         if (debug) {
807             System.err.println("Pausing reader;  was reading from: " +
808                                 inStream);
809         }
810         paused = true;
811         try {
812             while (paused) {
813                 pauseLock.wait(); // notified by unpauseReader
814             }
815         } catch (InterruptedException e) {
816             throw new InterruptedIOException(
817                     "Pause/unpause reader has problems.");
818         }
819     }
820 
821 
822     ////////////////////////////////////////////////////////////////////////////
823     //
824     // The LDAP Binding thread. It does the mux/demux of multiple requests
825     // on the same TCP connection.
826     //
827     ////////////////////////////////////////////////////////////////////////////
828 
829 
run()830     public void run() {
831         byte inbuf[];   // Buffer for reading incoming bytes
832         int inMsgId;    // Message id of incoming response
833         int bytesread;  // Number of bytes in inbuf
834         int br;         // Temp; number of bytes read from stream
835         int offset;     // Offset of where to store bytes in inbuf
836         int seqlen;     // Length of ASN sequence
837         int seqlenlen;  // Number of sequence length bytes
838         boolean eos;    // End of stream
839         BerDecoder retBer;    // Decoder for ASN.1 BER data from inbuf
840         InputStream in = null;
841 
842         try {
843             while (true) {
844                 try {
845                     // type and length (at most 128 octets for long form)
846                     inbuf = new byte[129];
847 
848                     offset = 0;
849                     seqlen = 0;
850                     seqlenlen = 0;
851 
852                     in = getInputStream();
853 
854                     // check that it is the beginning of a sequence
855                     bytesread = in.read(inbuf, offset, 1);
856                     if (bytesread < 0) {
857                         if (in != getInputStream()) {
858                             continue;   // a new stream to try
859                         } else {
860                             break; // EOF
861                         }
862                     }
863 
864                     if (inbuf[offset++] != (Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR))
865                         continue;
866 
867                     // get length of sequence
868                     bytesread = in.read(inbuf, offset, 1);
869                     if (bytesread < 0)
870                         break; // EOF
871                     seqlen = inbuf[offset++];
872 
873                     // if high bit is on, length is encoded in the
874                     // subsequent length bytes and the number of length bytes
875                     // is equal to & 0x80 (i.e. length byte with high bit off).
876                     if ((seqlen & 0x80) == 0x80) {
877                         seqlenlen = seqlen & 0x7f;  // number of length bytes
878                         // Check the length of length field, since seqlen is int
879                         // the number of bytes can't be greater than 4
880                         if (seqlenlen > 4) {
881                             throw new IOException("Length coded with too many bytes: " + seqlenlen);
882                         }
883 
884                         bytesread = 0;
885                         eos = false;
886 
887                         // Read all length bytes
888                         while (bytesread < seqlenlen) {
889                             br = in.read(inbuf, offset+bytesread,
890                                 seqlenlen-bytesread);
891                             if (br < 0) {
892                                 eos = true;
893                                 break; // EOF
894                             }
895                             bytesread += br;
896                         }
897 
898                         // end-of-stream reached before length bytes are read
899                         if (eos)
900                             break;  // EOF
901 
902                         // Add contents of length bytes to determine length
903                         seqlen = 0;
904                         for( int i = 0; i < seqlenlen; i++) {
905                             seqlen = (seqlen << 8) + (inbuf[offset+i] & 0xff);
906                         }
907                         offset += bytesread;
908                     }
909 
910                     if (seqlenlen > bytesread) {
911                         throw new IOException("Unexpected EOF while reading length");
912                     }
913 
914                     if (seqlen < 0) {
915                         throw new IOException("Length too big: " + (((long) seqlen) & 0xFFFFFFFFL));
916                     }
917                     // read in seqlen bytes
918                     byte[] left = readFully(in, seqlen);
919                     inbuf = Arrays.copyOf(inbuf, offset + left.length);
920                     System.arraycopy(left, 0, inbuf, offset, left.length);
921                     offset += left.length;
922 
923                     try {
924                         retBer = new BerDecoder(inbuf, 0, offset);
925 
926                         if (traceFile != null) {
927                             Ber.dumpBER(traceFile, traceTagIn, inbuf, 0, offset);
928                         }
929 
930                         retBer.parseSeq(null);
931                         inMsgId = retBer.parseInt();
932                         retBer.reset(); // reset offset
933 
934                         boolean needPause = false;
935 
936                         if (inMsgId == 0) {
937                             // Unsolicited Notification
938                             parent.processUnsolicited(retBer);
939                         } else {
940                             LdapRequest ldr = findRequest(inMsgId);
941 
942                             if (ldr != null) {
943 
944                                 /**
945                                  * Grab pauseLock before making reply available
946                                  * to ensure that reader goes into paused state
947                                  * before writer can attempt to unpause reader
948                                  */
949                                 synchronized (pauseLock) {
950                                     needPause = ldr.addReplyBer(retBer);
951                                     if (needPause) {
952                                         /*
953                                          * Go into paused state; release
954                                          * pauseLock
955                                          */
956                                         pauseReader();
957                                     }
958 
959                                     // else release pauseLock
960                                 }
961                             } else {
962                                 // System.err.println("Cannot find" +
963                                 //              "LdapRequest for " + inMsgId);
964                             }
965                         }
966                     } catch (Ber.DecodeException e) {
967                         //System.err.println("Cannot parse Ber");
968                     }
969                 } catch (IOException ie) {
970                     if (debug) {
971                         System.err.println("Connection: Inside Caught " + ie);
972                         ie.printStackTrace();
973                     }
974 
975                     if (in != getInputStream()) {
976                         // A new stream to try
977                         // Go to top of loop and continue
978                     } else {
979                         if (debug) {
980                             System.err.println("Connection: rethrowing " + ie);
981                         }
982                         throw ie;  // rethrow exception
983                     }
984                 }
985             }
986 
987             if (debug) {
988                 System.err.println("Connection: end-of-stream detected: "
989                     + in);
990             }
991         } catch (IOException ex) {
992             if (debug) {
993                 System.err.println("Connection: Caught " + ex);
994             }
995             closureReason = ex;
996         } finally {
997             cleanup(null, true); // cleanup
998         }
999         if (debug) {
1000             System.err.println("Connection: Thread Exiting");
1001         }
1002     }
1003 
readFully(InputStream is, int length)1004     private static byte[] readFully(InputStream is, int length)
1005         throws IOException
1006     {
1007         byte[] buf = new byte[Math.min(length, 8192)];
1008         int nread = 0;
1009         while (nread < length) {
1010             int bytesToRead;
1011             if (nread >= buf.length) {  // need to allocate a larger buffer
1012                 bytesToRead = Math.min(length - nread, buf.length + 8192);
1013                 if (buf.length < nread + bytesToRead) {
1014                     buf = Arrays.copyOf(buf, nread + bytesToRead);
1015                 }
1016             } else {
1017                 bytesToRead = buf.length - nread;
1018             }
1019             int count = is.read(buf, nread, bytesToRead);
1020             if (count < 0) {
1021                 if (buf.length != nread)
1022                     buf = Arrays.copyOf(buf, nread);
1023                 break;
1024             }
1025             nread += count;
1026         }
1027         return buf;
1028     }
1029 
isTlsConnection()1030     public boolean isTlsConnection() {
1031         return (sock instanceof SSLSocket) || isUpgradedToStartTls;
1032     }
1033 
1034     /*
1035      * tlsHandshakeListener can be created for initial secure connection
1036      * and updated by StartTLS extended operation. It is used later by LdapClient
1037      * to create TLS Channel Binding data on the base of TLS server certificate
1038      */
1039     private volatile HandshakeListener tlsHandshakeListener;
1040 
setHandshakeCompletedListener(SSLSocket sslSocket)1041     synchronized public void setHandshakeCompletedListener(SSLSocket sslSocket) {
1042         if (tlsHandshakeListener != null)
1043             tlsHandshakeListener.tlsHandshakeCompleted.cancel(false);
1044 
1045         tlsHandshakeListener = new HandshakeListener();
1046         sslSocket.addHandshakeCompletedListener(tlsHandshakeListener);
1047     }
1048 
getTlsServerCertificate()1049     public X509Certificate getTlsServerCertificate()
1050         throws SaslException {
1051         try {
1052             if (isTlsConnection() && tlsHandshakeListener != null)
1053                 return tlsHandshakeListener.tlsHandshakeCompleted.get();
1054         } catch (InterruptedException iex) {
1055             throw new SaslException("TLS Handshake Exception ", iex);
1056         } catch (ExecutionException eex) {
1057             throw new SaslException("TLS Handshake Exception ", eex.getCause());
1058         }
1059         return null;
1060     }
1061 
1062     private class HandshakeListener implements HandshakeCompletedListener {
1063 
1064         private final CompletableFuture<X509Certificate> tlsHandshakeCompleted =
1065                 new CompletableFuture<>();
1066         @Override
handshakeCompleted(HandshakeCompletedEvent event)1067         public void handshakeCompleted(HandshakeCompletedEvent event) {
1068             try {
1069                 X509Certificate tlsServerCert = null;
1070                 Certificate[] certs;
1071                 if (event.getSocket().getUseClientMode()) {
1072                     certs = event.getPeerCertificates();
1073                 } else {
1074                     certs = event.getLocalCertificates();
1075                 }
1076                 if (certs != null && certs.length > 0 &&
1077                         certs[0] instanceof X509Certificate) {
1078                     tlsServerCert = (X509Certificate) certs[0];
1079                 }
1080                 tlsHandshakeCompleted.complete(tlsServerCert);
1081             } catch (SSLPeerUnverifiedException ex) {
1082                 CommunicationException ce = new CommunicationException();
1083                 ce.setRootCause(closureReason);
1084                 tlsHandshakeCompleted.completeExceptionally(ex);
1085             }
1086         }
1087     }
1088 }
1089