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