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