1 /* 2 * Copyright (c) 1999, 2020, 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, HandshakeCompletedListener { 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 private static final boolean IS_HOSTNAME_VERIFICATION_DISABLED 173 = hostnameVerificationDisabledValue(); 174 175 private static boolean hostnameVerificationDisabledValue() { 176 PrivilegedAction<String> act = () -> System.getProperty( 177 "com.sun.jndi.ldap.object.disableEndpointIdentification"); 178 String prop = AccessController.doPrivileged(act); 179 if (prop == null) { 180 return false; 181 } 182 return prop.isEmpty() ? true : Boolean.parseBoolean(prop); 183 } 184 // true means v3; false means v2 185 // Called in LdapClient.authenticate() (which is synchronized) 186 // when connection is "quiet" and not shared; no need to synchronize 187 void setV3(boolean v) { 188 v3 = v; 189 } 190 191 // A BIND request has been successfully made on this connection 192 // When cleaning up, remember to do an UNBIND 193 // Called in LdapClient.authenticate() (which is synchronized) 194 // when connection is "quiet" and not shared; no need to synchronize 195 void setBound() { 196 bound = true; 197 } 198 199 //////////////////////////////////////////////////////////////////////////// 200 // 201 // Create an LDAP Binding object and bind to a particular server 202 // 203 //////////////////////////////////////////////////////////////////////////// 204 205 Connection(LdapClient parent, String host, int port, String socketFactory, 206 int connectTimeout, int readTimeout, OutputStream trace) throws NamingException { 207 208 this.host = host; 209 this.port = port; 210 this.parent = parent; 211 this.readTimeout = readTimeout; 212 this.connectTimeout = connectTimeout; 213 214 if (trace != null) { 215 traceFile = trace; 216 traceTagIn = "<- " + host + ":" + port + "\n\n"; 217 traceTagOut = "-> " + host + ":" + port + "\n\n"; 218 } 219 220 // 221 // Connect to server 222 // 223 try { 224 sock = createSocket(host, port, socketFactory, connectTimeout); 225 226 if (debug) { 227 System.err.println("Connection: opening socket: " + host + "," + port); 228 } 229 230 inStream = new BufferedInputStream(sock.getInputStream()); 231 outStream = new BufferedOutputStream(sock.getOutputStream()); 232 233 } catch (InvocationTargetException e) { 234 Throwable realException = e.getTargetException(); 235 // realException.printStackTrace(); 236 237 CommunicationException ce = 238 new CommunicationException(host + ":" + port); 239 ce.setRootCause(realException); 240 throw ce; 241 } catch (Exception e) { 242 // We need to have a catch all here and 243 // ignore generic exceptions. 244 // Also catches all IO errors generated by socket creation. 245 CommunicationException ce = 246 new CommunicationException(host + ":" + port); 247 ce.setRootCause(e); 248 throw ce; 249 } 250 251 worker = Obj.helper.createThread(this); 252 worker.setDaemon(true); 253 worker.start(); 254 } 255 256 /* 257 * Create an InetSocketAddress using the specified hostname and port number. 258 */ 259 private InetSocketAddress createInetSocketAddress(String host, int port) { 260 return new InetSocketAddress(host, port); 261 } 262 263 /* 264 * Create a Socket object using the specified socket factory and time limit. 265 * 266 * If a timeout is supplied and unconnected sockets are supported then 267 * an unconnected socket is created and the timeout is applied when 268 * connecting the socket. If a timeout is supplied but unconnected sockets 269 * are not supported then the timeout is ignored and a connected socket 270 * is created. 271 */ 272 private Socket createSocket(String host, int port, String socketFactory, 273 int connectTimeout) throws Exception { 274 275 Socket socket = null; 276 277 if (socketFactory != null) { 278 279 // create the factory 280 281 @SuppressWarnings("unchecked") 282 Class<? extends SocketFactory> socketFactoryClass = 283 (Class<? extends SocketFactory>)Obj.helper.loadClass(socketFactory); 284 Method getDefault = 285 socketFactoryClass.getMethod("getDefault", new Class<?>[]{}); 286 SocketFactory factory = (SocketFactory) getDefault.invoke(null, new Object[]{}); 287 288 // create the socket 289 290 if (connectTimeout > 0) { 291 292 InetSocketAddress endpoint = 293 createInetSocketAddress(host, port); 294 295 // unconnected socket 296 socket = factory.createSocket(); 297 298 if (debug) { 299 System.err.println("Connection: creating socket with " + 300 "a timeout using supplied socket factory"); 301 } 302 303 // connected socket 304 socket.connect(endpoint, connectTimeout); 305 } 306 307 // continue (but ignore connectTimeout) 308 if (socket == null) { 309 if (debug) { 310 System.err.println("Connection: creating socket using " + 311 "supplied socket factory"); 312 } 313 // connected socket 314 socket = factory.createSocket(host, port); 315 } 316 } else { 317 318 if (connectTimeout > 0) { 319 320 InetSocketAddress endpoint = createInetSocketAddress(host, port); 321 322 socket = new Socket(); 323 324 if (debug) { 325 System.err.println("Connection: creating socket with " + 326 "a timeout"); 327 } 328 socket.connect(endpoint, connectTimeout); 329 } 330 331 // continue (but ignore connectTimeout) 332 333 if (socket == null) { 334 if (debug) { 335 System.err.println("Connection: creating socket"); 336 } 337 // connected socket 338 socket = new Socket(host, port); 339 } 340 } 341 342 // For LDAP connect timeouts on LDAP over SSL connections must treat 343 // the SSL handshake following socket connection as part of the timeout. 344 // So explicitly set a socket read timeout, trigger the SSL handshake, 345 // then reset the timeout. 346 if (socket instanceof SSLSocket) { 347 SSLSocket sslSocket = (SSLSocket) socket; 348 if (!IS_HOSTNAME_VERIFICATION_DISABLED) { 349 SSLParameters param = sslSocket.getSSLParameters(); 350 param.setEndpointIdentificationAlgorithm("LDAPS"); 351 sslSocket.setSSLParameters(param); 352 } 353 sslSocket.addHandshakeCompletedListener(this); 354 if (connectTimeout > 0) { 355 int socketTimeout = sslSocket.getSoTimeout(); 356 sslSocket.setSoTimeout(connectTimeout); // reuse full timeout value 357 sslSocket.startHandshake(); 358 sslSocket.setSoTimeout(socketTimeout); 359 } 360 } 361 return socket; 362 } 363 364 //////////////////////////////////////////////////////////////////////////// 365 // 366 // Methods to IO to the LDAP server 367 // 368 //////////////////////////////////////////////////////////////////////////// 369 370 synchronized int getMsgId() { 371 return ++outMsgId; 372 } 373 374 LdapRequest writeRequest(BerEncoder ber, int msgId) throws IOException { 375 return writeRequest(ber, msgId, false /* pauseAfterReceipt */, -1); 376 } 377 378 LdapRequest writeRequest(BerEncoder ber, int msgId, 379 boolean pauseAfterReceipt) throws IOException { 380 return writeRequest(ber, msgId, pauseAfterReceipt, -1); 381 } 382 383 LdapRequest writeRequest(BerEncoder ber, int msgId, 384 boolean pauseAfterReceipt, int replyQueueCapacity) throws IOException { 385 386 LdapRequest req = 387 new LdapRequest(msgId, pauseAfterReceipt, replyQueueCapacity); 388 addRequest(req); 389 390 if (traceFile != null) { 391 Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 0, ber.getDataLen()); 392 } 393 394 395 // unpause reader so that it can get response 396 // NOTE: Must do this before writing request, otherwise might 397 // create a race condition where the writer unblocks its own response 398 unpauseReader(); 399 400 if (debug) { 401 System.err.println("Writing request to: " + outStream); 402 } 403 404 try { 405 synchronized (this) { 406 outStream.write(ber.getBuf(), 0, ber.getDataLen()); 407 outStream.flush(); 408 } 409 } catch (IOException e) { 410 cleanup(null, true); 411 throw (closureReason = e); // rethrow 412 } 413 414 return req; 415 } 416 417 /** 418 * Reads a reply; waits until one is ready. 419 */ 420 BerDecoder readReply(LdapRequest ldr) throws IOException, NamingException { 421 BerDecoder rber; 422 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 431 NamingException namingException = null; 432 try { 433 // if no timeout is set so we wait infinitely until 434 // a response is received OR until the connection is closed or cancelled 435 // http://docs.oracle.com/javase/8/docs/technotes/guides/jndi/jndi-ldap.html#PROP 436 rber = ldr.getReplyBer(readTimeout); 437 } catch (InterruptedException ex) { 438 throw new InterruptedNamingException( 439 "Interrupted during LDAP operation"); 440 } catch (CommunicationException ce) { 441 // Re-throw 442 throw ce; 443 } catch (NamingException ne) { 444 // Connection is timed out OR closed/cancelled 445 namingException = ne; 446 rber = null; 447 } 448 449 if (rber == null) { 450 abandonRequest(ldr, null); 451 } 452 // namingException can be not null in the following cases: 453 // a) The response is timed-out 454 // b) LDAP request connection has been closed or cancelled 455 // The exception message is initialized in LdapRequest::getReplyBer 456 if (namingException != null) { 457 // Re-throw NamingException after all cleanups are done 458 throw namingException; 459 } 460 return rber; 461 } 462 463 //////////////////////////////////////////////////////////////////////////// 464 // 465 // Methods to add, find, delete, and abandon requests made to server 466 // 467 //////////////////////////////////////////////////////////////////////////// 468 469 private synchronized void addRequest(LdapRequest ldapRequest) { 470 471 LdapRequest ldr = pendingRequests; 472 if (ldr == null) { 473 pendingRequests = ldapRequest; 474 ldapRequest.next = null; 475 } else { 476 ldapRequest.next = pendingRequests; 477 pendingRequests = ldapRequest; 478 } 479 } 480 481 synchronized LdapRequest findRequest(int msgId) { 482 483 LdapRequest ldr = pendingRequests; 484 while (ldr != null) { 485 if (ldr.msgId == msgId) { 486 return ldr; 487 } 488 ldr = ldr.next; 489 } 490 return null; 491 492 } 493 494 synchronized void removeRequest(LdapRequest req) { 495 LdapRequest ldr = pendingRequests; 496 LdapRequest ldrprev = null; 497 498 while (ldr != null) { 499 if (ldr == req) { 500 ldr.cancel(); 501 502 if (ldrprev != null) { 503 ldrprev.next = ldr.next; 504 } else { 505 pendingRequests = ldr.next; 506 } 507 ldr.next = null; 508 } 509 ldrprev = ldr; 510 ldr = ldr.next; 511 } 512 } 513 514 void abandonRequest(LdapRequest ldr, Control[] reqCtls) { 515 // Remove from queue 516 removeRequest(ldr); 517 518 BerEncoder ber = new BerEncoder(256); 519 int abandonMsgId = getMsgId(); 520 521 // 522 // build the abandon request. 523 // 524 try { 525 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); 526 ber.encodeInt(abandonMsgId); 527 ber.encodeInt(ldr.msgId, LdapClient.LDAP_REQ_ABANDON); 528 529 if (v3) { 530 LdapClient.encodeControls(ber, reqCtls); 531 } 532 ber.endSeq(); 533 534 if (traceFile != null) { 535 Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 0, 536 ber.getDataLen()); 537 } 538 539 synchronized (this) { 540 outStream.write(ber.getBuf(), 0, ber.getDataLen()); 541 outStream.flush(); 542 } 543 544 } catch (IOException ex) { 545 //System.err.println("ldap.abandon: " + ex); 546 } 547 548 // Don't expect any response for the abandon request. 549 } 550 551 synchronized void abandonOutstandingReqs(Control[] reqCtls) { 552 LdapRequest ldr = pendingRequests; 553 554 while (ldr != null) { 555 abandonRequest(ldr, reqCtls); 556 pendingRequests = ldr = ldr.next; 557 } 558 } 559 560 //////////////////////////////////////////////////////////////////////////// 561 // 562 // Methods to unbind from server and clear up resources when object is 563 // destroyed. 564 // 565 //////////////////////////////////////////////////////////////////////////// 566 567 private void ldapUnbind(Control[] reqCtls) { 568 569 BerEncoder ber = new BerEncoder(256); 570 int unbindMsgId = getMsgId(); 571 572 // 573 // build the unbind request. 574 // 575 576 try { 577 578 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); 579 ber.encodeInt(unbindMsgId); 580 // IMPLICIT TAGS 581 ber.encodeByte(LdapClient.LDAP_REQ_UNBIND); 582 ber.encodeByte(0); 583 584 if (v3) { 585 LdapClient.encodeControls(ber, reqCtls); 586 } 587 ber.endSeq(); 588 589 if (traceFile != null) { 590 Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 591 0, ber.getDataLen()); 592 } 593 594 synchronized (this) { 595 outStream.write(ber.getBuf(), 0, ber.getDataLen()); 596 outStream.flush(); 597 } 598 599 } catch (IOException ex) { 600 //System.err.println("ldap.unbind: " + ex); 601 } 602 603 // Don't expect any response for the unbind request. 604 } 605 606 /** 607 * @param reqCtls Possibly null request controls that accompanies the 608 * abandon and unbind LDAP request. 609 * @param notifyParent true means to call parent LdapClient back, notifying 610 * it that the connection has been closed; false means not to notify 611 * parent. If LdapClient invokes cleanup(), notifyParent should be set to 612 * false because LdapClient already knows that it is closing 613 * the connection. If Connection invokes cleanup(), notifyParent should be 614 * set to true because LdapClient needs to know about the closure. 615 */ 616 void cleanup(Control[] reqCtls, boolean notifyParent) { 617 boolean nparent = false; 618 619 synchronized (this) { 620 useable = false; 621 622 if (sock != null) { 623 if (debug) { 624 System.err.println("Connection: closing socket: " + host + "," + port); 625 } 626 try { 627 if (!notifyParent) { 628 abandonOutstandingReqs(reqCtls); 629 } 630 if (bound) { 631 ldapUnbind(reqCtls); 632 } 633 } finally { 634 try { 635 outStream.flush(); 636 sock.close(); 637 unpauseReader(); 638 } catch (IOException ie) { 639 if (debug) 640 System.err.println("Connection: problem closing socket: " + ie); 641 } 642 if (!notifyParent) { 643 LdapRequest ldr = pendingRequests; 644 while (ldr != null) { 645 ldr.cancel(); 646 ldr = ldr.next; 647 } 648 } 649 if (isTlsConnection()) { 650 if (closureReason != null) { 651 CommunicationException ce = new CommunicationException(); 652 ce.setRootCause(closureReason); 653 tlsHandshakeCompleted.completeExceptionally(ce); 654 } else { 655 tlsHandshakeCompleted.cancel(false); 656 } 657 } 658 sock = null; 659 } 660 nparent = notifyParent; 661 } 662 if (nparent) { 663 LdapRequest ldr = pendingRequests; 664 while (ldr != null) { 665 ldr.close(); 666 ldr = ldr.next; 667 } 668 } 669 } 670 if (nparent) { 671 parent.processConnectionClosure(); 672 } 673 } 674 675 676 // Assume everything is "quiet" 677 // "synchronize" might lead to deadlock so don't synchronize method 678 // Use streamLock instead for synchronizing update to stream 679 680 synchronized public void replaceStreams(InputStream newIn, OutputStream newOut) { 681 if (debug) { 682 System.err.println("Replacing " + inStream + " with: " + newIn); 683 System.err.println("Replacing " + outStream + " with: " + newOut); 684 } 685 686 inStream = newIn; 687 688 // Cleanup old stream 689 try { 690 outStream.flush(); 691 } catch (IOException ie) { 692 if (debug) 693 System.err.println("Connection: cannot flush outstream: " + ie); 694 } 695 696 // Replace stream 697 outStream = newOut; 698 } 699 700 /** 701 * Used by Connection thread to read inStream into a local variable. 702 * This ensures that there is no contention between the main thread 703 * and the Connection thread when the main thread updates inStream. 704 */ 705 synchronized private InputStream getInputStream() { 706 return inStream; 707 } 708 709 710 //////////////////////////////////////////////////////////////////////////// 711 // 712 // Code for pausing/unpausing the reader thread ('worker') 713 // 714 //////////////////////////////////////////////////////////////////////////// 715 716 /* 717 * The main idea is to mark requests that need the reader thread to 718 * pause after getting the response. When the reader thread gets the response, 719 * it waits on a lock instead of returning to the read(). The next time a 720 * request is sent, the reader is automatically unblocked if necessary. 721 * Note that the reader must be unblocked BEFORE the request is sent. 722 * Otherwise, there is a race condition where the request is sent and 723 * the reader thread might read the response and be unblocked 724 * by writeRequest(). 725 * 726 * This pause gives the main thread (StartTLS or SASL) an opportunity to 727 * update the reader's state (e.g., its streams) if necessary. 728 * The assumption is that the connection will remain quiet during this pause 729 * (i.e., no intervening requests being sent). 730 *<p> 731 * For dealing with StartTLS close, 732 * when the read() exits either due to EOF or an exception, 733 * the reader thread checks whether there is a new stream to read from. 734 * If so, then it reattempts the read. Otherwise, the EOF or exception 735 * is processed and the reader thread terminates. 736 * In a StartTLS close, the client first replaces the SSL IO streams with 737 * plain ones and then closes the SSL socket. 738 * If the reader thread attempts to read, or was reading, from 739 * the SSL socket (that is, it got to the read BEFORE replaceStreams()), 740 * the SSL socket close will cause the reader thread to 741 * get an EOF/exception and reexamine the input stream. 742 * If the reader thread sees a new stream, it reattempts the read. 743 * If the underlying socket is still alive, then the new read will succeed. 744 * If the underlying socket has been closed also, then the new read will 745 * fail and the reader thread exits. 746 * If the reader thread attempts to read, or was reading, from the plain 747 * socket (that is, it got to the read AFTER replaceStreams()), the 748 * SSL socket close will have no effect on the reader thread. 749 * 750 * The check for new stream is made only 751 * in the first attempt at reading a BER buffer; the reader should 752 * never be in midst of reading a buffer when a nonfatal close occurs. 753 * If this occurs, then the connection is in an inconsistent state and 754 * the safest thing to do is to shut it down. 755 */ 756 757 private final Object pauseLock = new Object(); // lock for reader to wait on while paused 758 private boolean paused = false; // paused state of reader 759 760 /* 761 * Unpauses reader thread if it was paused 762 */ 763 private void unpauseReader() throws IOException { 764 synchronized (pauseLock) { 765 if (paused) { 766 if (debug) { 767 System.err.println("Unpausing reader; read from: " + 768 inStream); 769 } 770 paused = false; 771 pauseLock.notify(); 772 } 773 } 774 } 775 776 /* 777 * Pauses reader so that it stops reading from the input stream. 778 * Reader blocks on pauseLock instead of read(). 779 * MUST be called from within synchronized (pauseLock) clause. 780 */ 781 private void pauseReader() throws IOException { 782 if (debug) { 783 System.err.println("Pausing reader; was reading from: " + 784 inStream); 785 } 786 paused = true; 787 try { 788 while (paused) { 789 pauseLock.wait(); // notified by unpauseReader 790 } 791 } catch (InterruptedException e) { 792 throw new InterruptedIOException( 793 "Pause/unpause reader has problems."); 794 } 795 } 796 797 798 //////////////////////////////////////////////////////////////////////////// 799 // 800 // The LDAP Binding thread. It does the mux/demux of multiple requests 801 // on the same TCP connection. 802 // 803 //////////////////////////////////////////////////////////////////////////// 804 805 806 public void run() { 807 byte inbuf[]; // Buffer for reading incoming bytes 808 int inMsgId; // Message id of incoming response 809 int bytesread; // Number of bytes in inbuf 810 int br; // Temp; number of bytes read from stream 811 int offset; // Offset of where to store bytes in inbuf 812 int seqlen; // Length of ASN sequence 813 int seqlenlen; // Number of sequence length bytes 814 boolean eos; // End of stream 815 BerDecoder retBer; // Decoder for ASN.1 BER data from inbuf 816 InputStream in = null; 817 818 try { 819 while (true) { 820 try { 821 // type and length (at most 128 octets for long form) 822 inbuf = new byte[129]; 823 824 offset = 0; 825 seqlen = 0; 826 seqlenlen = 0; 827 828 in = getInputStream(); 829 830 // check that it is the beginning of a sequence 831 bytesread = in.read(inbuf, offset, 1); 832 if (bytesread < 0) { 833 if (in != getInputStream()) { 834 continue; // a new stream to try 835 } else { 836 break; // EOF 837 } 838 } 839 840 if (inbuf[offset++] != (Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR)) 841 continue; 842 843 // get length of sequence 844 bytesread = in.read(inbuf, offset, 1); 845 if (bytesread < 0) 846 break; // EOF 847 seqlen = inbuf[offset++]; 848 849 // if high bit is on, length is encoded in the 850 // subsequent length bytes and the number of length bytes 851 // is equal to & 0x80 (i.e. length byte with high bit off). 852 if ((seqlen & 0x80) == 0x80) { 853 seqlenlen = seqlen & 0x7f; // number of length bytes 854 855 bytesread = 0; 856 eos = false; 857 858 // Read all length bytes 859 while (bytesread < seqlenlen) { 860 br = in.read(inbuf, offset+bytesread, 861 seqlenlen-bytesread); 862 if (br < 0) { 863 eos = true; 864 break; // EOF 865 } 866 bytesread += br; 867 } 868 869 // end-of-stream reached before length bytes are read 870 if (eos) 871 break; // EOF 872 873 // Add contents of length bytes to determine length 874 seqlen = 0; 875 for( int i = 0; i < seqlenlen; i++) { 876 seqlen = (seqlen << 8) + (inbuf[offset+i] & 0xff); 877 } 878 offset += bytesread; 879 } 880 881 // read in seqlen bytes 882 byte[] left = readFully(in, seqlen); 883 inbuf = Arrays.copyOf(inbuf, offset + left.length); 884 System.arraycopy(left, 0, inbuf, offset, left.length); 885 offset += left.length; 886 887 try { 888 retBer = new BerDecoder(inbuf, 0, offset); 889 890 if (traceFile != null) { 891 Ber.dumpBER(traceFile, traceTagIn, inbuf, 0, offset); 892 } 893 894 retBer.parseSeq(null); 895 inMsgId = retBer.parseInt(); 896 retBer.reset(); // reset offset 897 898 boolean needPause = false; 899 900 if (inMsgId == 0) { 901 // Unsolicited Notification 902 parent.processUnsolicited(retBer); 903 } else { 904 LdapRequest ldr = findRequest(inMsgId); 905 906 if (ldr != null) { 907 908 /** 909 * Grab pauseLock before making reply available 910 * to ensure that reader goes into paused state 911 * before writer can attempt to unpause reader 912 */ 913 synchronized (pauseLock) { 914 needPause = ldr.addReplyBer(retBer); 915 if (needPause) { 916 /* 917 * Go into paused state; release 918 * pauseLock 919 */ 920 pauseReader(); 921 } 922 923 // else release pauseLock 924 } 925 } else { 926 // System.err.println("Cannot find" + 927 // "LdapRequest for " + inMsgId); 928 } 929 } 930 } catch (Ber.DecodeException e) { 931 //System.err.println("Cannot parse Ber"); 932 } 933 } catch (IOException ie) { 934 if (debug) { 935 System.err.println("Connection: Inside Caught " + ie); 936 ie.printStackTrace(); 937 } 938 939 if (in != getInputStream()) { 940 // A new stream to try 941 // Go to top of loop and continue 942 } else { 943 if (debug) { 944 System.err.println("Connection: rethrowing " + ie); 945 } 946 throw ie; // rethrow exception 947 } 948 } 949 } 950 951 if (debug) { 952 System.err.println("Connection: end-of-stream detected: " 953 + in); 954 } 955 } catch (IOException ex) { 956 if (debug) { 957 System.err.println("Connection: Caught " + ex); 958 } 959 closureReason = ex; 960 } finally { 961 cleanup(null, true); // cleanup 962 } 963 if (debug) { 964 System.err.println("Connection: Thread Exiting"); 965 } 966 } 967 968 private static byte[] readFully(InputStream is, int length) 969 throws IOException 970 { 971 byte[] buf = new byte[Math.min(length, 8192)]; 972 int nread = 0; 973 while (nread < length) { 974 int bytesToRead; 975 if (nread >= buf.length) { // need to allocate a larger buffer 976 bytesToRead = Math.min(length - nread, buf.length + 8192); 977 if (buf.length < nread + bytesToRead) { 978 buf = Arrays.copyOf(buf, nread + bytesToRead); 979 } 980 } else { 981 bytesToRead = buf.length - nread; 982 } 983 int count = is.read(buf, nread, bytesToRead); 984 if (count < 0) { 985 if (buf.length != nread) 986 buf = Arrays.copyOf(buf, nread); 987 break; 988 } 989 nread += count; 990 } 991 return buf; 992 } 993 994 private CompletableFuture<X509Certificate> tlsHandshakeCompleted = 995 new CompletableFuture<>(); 996 997 @Override 998 public void handshakeCompleted(HandshakeCompletedEvent event) { 999 try { 1000 X509Certificate tlsServerCert = null; 1001 Certificate[] certs; 1002 if (event.getSocket().getUseClientMode()) { 1003 certs = event.getPeerCertificates(); 1004 } else { 1005 certs = event.getLocalCertificates(); 1006 } 1007 if (certs != null && certs.length > 0 && 1008 certs[0] instanceof X509Certificate) { 1009 tlsServerCert = (X509Certificate) certs[0]; 1010 } 1011 tlsHandshakeCompleted.complete(tlsServerCert); 1012 } catch (SSLPeerUnverifiedException ex) { 1013 CommunicationException ce = new CommunicationException(); 1014 ce.setRootCause(closureReason); 1015 tlsHandshakeCompleted.completeExceptionally(ex); 1016 } 1017 } 1018 1019 public boolean isTlsConnection() { 1020 return sock instanceof SSLSocket; 1021 } 1022 1023 public X509Certificate getTlsServerCertificate() 1024 throws SaslException { 1025 try { 1026 if (isTlsConnection()) 1027 return tlsHandshakeCompleted.get(); 1028 } catch (InterruptedException iex) { 1029 throw new SaslException("TLS Handshake Exception ", iex); 1030 } catch (ExecutionException eex) { 1031 throw new SaslException("TLS Handshake Exception ", eex.getCause()); 1032 } 1033 return null; 1034 } 1035 }