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 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 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 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 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 */ 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 */ 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 } 353 return socket; 354 } 355 356 //////////////////////////////////////////////////////////////////////////// 357 // 358 // Methods to IO to the LDAP server 359 // 360 //////////////////////////////////////////////////////////////////////////// 361 362 synchronized int getMsgId() { 363 return ++outMsgId; 364 } 365 366 LdapRequest writeRequest(BerEncoder ber, int msgId) throws IOException { 367 return writeRequest(ber, msgId, false /* pauseAfterReceipt */, -1); 368 } 369 370 LdapRequest writeRequest(BerEncoder ber, int msgId, 371 boolean pauseAfterReceipt) throws IOException { 372 return writeRequest(ber, msgId, pauseAfterReceipt, -1); 373 } 374 375 LdapRequest writeRequest(BerEncoder ber, int msgId, 376 boolean pauseAfterReceipt, int replyQueueCapacity) throws IOException { 377 378 LdapRequest req = 379 new LdapRequest(msgId, pauseAfterReceipt, replyQueueCapacity); 380 addRequest(req); 381 382 if (traceFile != null) { 383 Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 0, ber.getDataLen()); 384 } 385 386 387 // unpause reader so that it can get response 388 // NOTE: Must do this before writing request, otherwise might 389 // create a race condition where the writer unblocks its own response 390 unpauseReader(); 391 392 if (debug) { 393 System.err.println("Writing request to: " + outStream); 394 } 395 396 try { 397 synchronized (this) { 398 outStream.write(ber.getBuf(), 0, ber.getDataLen()); 399 outStream.flush(); 400 } 401 } catch (IOException e) { 402 cleanup(null, true); 403 throw (closureReason = e); // rethrow 404 } 405 406 return req; 407 } 408 409 /** 410 * Reads a reply; waits until one is ready. 411 */ 412 BerDecoder readReply(LdapRequest ldr) 413 throws IOException, NamingException { 414 BerDecoder rber; 415 416 // Track down elapsed time to workaround spurious wakeups 417 long elapsedMilli = 0; 418 long elapsedNano = 0; 419 420 while (((rber = ldr.getReplyBer()) == null) && 421 (readTimeout <= 0 || elapsedMilli < readTimeout)) 422 { 423 try { 424 // If socket closed, don't even try 425 synchronized (this) { 426 if (sock == null) { 427 throw new ServiceUnavailableException(host + ":" + port + 428 "; socket closed"); 429 } 430 } 431 synchronized (ldr) { 432 // check if condition has changed since our last check 433 rber = ldr.getReplyBer(); 434 if (rber == null) { 435 if (readTimeout > 0) { // Socket read timeout is specified 436 long beginNano = System.nanoTime(); 437 438 // will be woken up before readTimeout if reply is 439 // available 440 ldr.wait(readTimeout - elapsedMilli); 441 elapsedNano += (System.nanoTime() - beginNano); 442 elapsedMilli += elapsedNano / 1000_000; 443 elapsedNano %= 1000_000; 444 445 } else { 446 // no timeout is set so we wait infinitely until 447 // a response is received 448 // http://docs.oracle.com/javase/8/docs/technotes/guides/jndi/jndi-ldap.html#PROP 449 ldr.wait(); 450 } 451 } else { 452 break; 453 } 454 } 455 } catch (InterruptedException ex) { 456 throw new InterruptedNamingException( 457 "Interrupted during LDAP operation"); 458 } 459 } 460 461 if ((rber == null) && (elapsedMilli >= readTimeout)) { 462 abandonRequest(ldr, null); 463 throw new NamingException("LDAP response read timed out, timeout used:" 464 + readTimeout + "ms." ); 465 466 } 467 return rber; 468 } 469 470 471 //////////////////////////////////////////////////////////////////////////// 472 // 473 // Methods to add, find, delete, and abandon requests made to server 474 // 475 //////////////////////////////////////////////////////////////////////////// 476 477 private synchronized void addRequest(LdapRequest ldapRequest) { 478 479 LdapRequest ldr = pendingRequests; 480 if (ldr == null) { 481 pendingRequests = ldapRequest; 482 ldapRequest.next = null; 483 } else { 484 ldapRequest.next = pendingRequests; 485 pendingRequests = ldapRequest; 486 } 487 } 488 489 synchronized LdapRequest findRequest(int msgId) { 490 491 LdapRequest ldr = pendingRequests; 492 while (ldr != null) { 493 if (ldr.msgId == msgId) { 494 return ldr; 495 } 496 ldr = ldr.next; 497 } 498 return null; 499 500 } 501 502 synchronized void removeRequest(LdapRequest req) { 503 LdapRequest ldr = pendingRequests; 504 LdapRequest ldrprev = null; 505 506 while (ldr != null) { 507 if (ldr == req) { 508 ldr.cancel(); 509 510 if (ldrprev != null) { 511 ldrprev.next = ldr.next; 512 } else { 513 pendingRequests = ldr.next; 514 } 515 ldr.next = null; 516 } 517 ldrprev = ldr; 518 ldr = ldr.next; 519 } 520 } 521 522 void abandonRequest(LdapRequest ldr, Control[] reqCtls) { 523 // Remove from queue 524 removeRequest(ldr); 525 526 BerEncoder ber = new BerEncoder(256); 527 int abandonMsgId = getMsgId(); 528 529 // 530 // build the abandon request. 531 // 532 try { 533 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); 534 ber.encodeInt(abandonMsgId); 535 ber.encodeInt(ldr.msgId, LdapClient.LDAP_REQ_ABANDON); 536 537 if (v3) { 538 LdapClient.encodeControls(ber, reqCtls); 539 } 540 ber.endSeq(); 541 542 if (traceFile != null) { 543 Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 0, 544 ber.getDataLen()); 545 } 546 547 synchronized (this) { 548 outStream.write(ber.getBuf(), 0, ber.getDataLen()); 549 outStream.flush(); 550 } 551 552 } catch (IOException ex) { 553 //System.err.println("ldap.abandon: " + ex); 554 } 555 556 // Don't expect any response for the abandon request. 557 } 558 559 synchronized void abandonOutstandingReqs(Control[] reqCtls) { 560 LdapRequest ldr = pendingRequests; 561 562 while (ldr != null) { 563 abandonRequest(ldr, reqCtls); 564 pendingRequests = ldr = ldr.next; 565 } 566 } 567 568 //////////////////////////////////////////////////////////////////////////// 569 // 570 // Methods to unbind from server and clear up resources when object is 571 // destroyed. 572 // 573 //////////////////////////////////////////////////////////////////////////// 574 575 private void ldapUnbind(Control[] reqCtls) { 576 577 BerEncoder ber = new BerEncoder(256); 578 int unbindMsgId = getMsgId(); 579 580 // 581 // build the unbind request. 582 // 583 584 try { 585 586 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); 587 ber.encodeInt(unbindMsgId); 588 // IMPLICIT TAGS 589 ber.encodeByte(LdapClient.LDAP_REQ_UNBIND); 590 ber.encodeByte(0); 591 592 if (v3) { 593 LdapClient.encodeControls(ber, reqCtls); 594 } 595 ber.endSeq(); 596 597 if (traceFile != null) { 598 Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 599 0, ber.getDataLen()); 600 } 601 602 synchronized (this) { 603 outStream.write(ber.getBuf(), 0, ber.getDataLen()); 604 outStream.flush(); 605 } 606 607 } catch (IOException ex) { 608 //System.err.println("ldap.unbind: " + ex); 609 } 610 611 // Don't expect any response for the unbind request. 612 } 613 614 /** 615 * @param reqCtls Possibly null request controls that accompanies the 616 * abandon and unbind LDAP request. 617 * @param notifyParent true means to call parent LdapClient back, notifying 618 * it that the connection has been closed; false means not to notify 619 * parent. If LdapClient invokes cleanup(), notifyParent should be set to 620 * false because LdapClient already knows that it is closing 621 * the connection. If Connection invokes cleanup(), notifyParent should be 622 * set to true because LdapClient needs to know about the closure. 623 */ 624 void cleanup(Control[] reqCtls, boolean notifyParent) { 625 boolean nparent = false; 626 627 synchronized (this) { 628 useable = false; 629 630 if (sock != null) { 631 if (debug) { 632 System.err.println("Connection: closing socket: " + host + "," + port); 633 } 634 try { 635 if (!notifyParent) { 636 abandonOutstandingReqs(reqCtls); 637 } 638 if (bound) { 639 ldapUnbind(reqCtls); 640 } 641 } finally { 642 try { 643 outStream.flush(); 644 sock.close(); 645 unpauseReader(); 646 } catch (IOException ie) { 647 if (debug) 648 System.err.println("Connection: problem closing socket: " + ie); 649 } 650 if (!notifyParent) { 651 LdapRequest ldr = pendingRequests; 652 while (ldr != null) { 653 ldr.cancel(); 654 ldr = ldr.next; 655 } 656 } 657 sock = null; 658 } 659 nparent = notifyParent; 660 } 661 if (nparent) { 662 LdapRequest ldr = pendingRequests; 663 while (ldr != null) { 664 665 synchronized (ldr) { 666 ldr.notify(); 667 ldr = ldr.next; 668 } 669 } 670 } 671 } 672 if (nparent) { 673 parent.processConnectionClosure(); 674 } 675 } 676 677 678 // Assume everything is "quiet" 679 // "synchronize" might lead to deadlock so don't synchronize method 680 // Use streamLock instead for synchronizing update to stream 681 682 synchronized public void replaceStreams(InputStream newIn, OutputStream newOut) { 683 if (debug) { 684 System.err.println("Replacing " + inStream + " with: " + newIn); 685 System.err.println("Replacing " + outStream + " with: " + newOut); 686 } 687 688 inStream = newIn; 689 690 // Cleanup old stream 691 try { 692 outStream.flush(); 693 } catch (IOException ie) { 694 if (debug) 695 System.err.println("Connection: cannot flush outstream: " + ie); 696 } 697 698 // Replace stream 699 outStream = newOut; 700 } 701 702 /** 703 * Used by Connection thread to read inStream into a local variable. 704 * This ensures that there is no contention between the main thread 705 * and the Connection thread when the main thread updates inStream. 706 */ 707 synchronized private InputStream getInputStream() { 708 return inStream; 709 } 710 711 712 //////////////////////////////////////////////////////////////////////////// 713 // 714 // Code for pausing/unpausing the reader thread ('worker') 715 // 716 //////////////////////////////////////////////////////////////////////////// 717 718 /* 719 * The main idea is to mark requests that need the reader thread to 720 * pause after getting the response. When the reader thread gets the response, 721 * it waits on a lock instead of returning to the read(). The next time a 722 * request is sent, the reader is automatically unblocked if necessary. 723 * Note that the reader must be unblocked BEFORE the request is sent. 724 * Otherwise, there is a race condition where the request is sent and 725 * the reader thread might read the response and be unblocked 726 * by writeRequest(). 727 * 728 * This pause gives the main thread (StartTLS or SASL) an opportunity to 729 * update the reader's state (e.g., its streams) if necessary. 730 * The assumption is that the connection will remain quiet during this pause 731 * (i.e., no intervening requests being sent). 732 *<p> 733 * For dealing with StartTLS close, 734 * when the read() exits either due to EOF or an exception, 735 * the reader thread checks whether there is a new stream to read from. 736 * If so, then it reattempts the read. Otherwise, the EOF or exception 737 * is processed and the reader thread terminates. 738 * In a StartTLS close, the client first replaces the SSL IO streams with 739 * plain ones and then closes the SSL socket. 740 * If the reader thread attempts to read, or was reading, from 741 * the SSL socket (that is, it got to the read BEFORE replaceStreams()), 742 * the SSL socket close will cause the reader thread to 743 * get an EOF/exception and reexamine the input stream. 744 * If the reader thread sees a new stream, it reattempts the read. 745 * If the underlying socket is still alive, then the new read will succeed. 746 * If the underlying socket has been closed also, then the new read will 747 * fail and the reader thread exits. 748 * If the reader thread attempts to read, or was reading, from the plain 749 * socket (that is, it got to the read AFTER replaceStreams()), the 750 * SSL socket close will have no effect on the reader thread. 751 * 752 * The check for new stream is made only 753 * in the first attempt at reading a BER buffer; the reader should 754 * never be in midst of reading a buffer when a nonfatal close occurs. 755 * If this occurs, then the connection is in an inconsistent state and 756 * the safest thing to do is to shut it down. 757 */ 758 759 private Object pauseLock = new Object(); // lock for reader to wait on while paused 760 private boolean paused = false; // paused state of reader 761 762 /* 763 * Unpauses reader thread if it was paused 764 */ 765 private void unpauseReader() throws IOException { 766 synchronized (pauseLock) { 767 if (paused) { 768 if (debug) { 769 System.err.println("Unpausing reader; read from: " + 770 inStream); 771 } 772 paused = false; 773 pauseLock.notify(); 774 } 775 } 776 } 777 778 /* 779 * Pauses reader so that it stops reading from the input stream. 780 * Reader blocks on pauseLock instead of read(). 781 * MUST be called from within synchronized (pauseLock) clause. 782 */ 783 private void pauseReader() throws IOException { 784 if (debug) { 785 System.err.println("Pausing reader; was reading from: " + 786 inStream); 787 } 788 paused = true; 789 try { 790 while (paused) { 791 pauseLock.wait(); // notified by unpauseReader 792 } 793 } catch (InterruptedException e) { 794 throw new InterruptedIOException( 795 "Pause/unpause reader has problems."); 796 } 797 } 798 799 800 //////////////////////////////////////////////////////////////////////////// 801 // 802 // The LDAP Binding thread. It does the mux/demux of multiple requests 803 // on the same TCP connection. 804 // 805 //////////////////////////////////////////////////////////////////////////// 806 807 808 public void run() { 809 byte inbuf[]; // Buffer for reading incoming bytes 810 int inMsgId; // Message id of incoming response 811 int bytesread; // Number of bytes in inbuf 812 int br; // Temp; number of bytes read from stream 813 int offset; // Offset of where to store bytes in inbuf 814 int seqlen; // Length of ASN sequence 815 int seqlenlen; // Number of sequence length bytes 816 boolean eos; // End of stream 817 BerDecoder retBer; // Decoder for ASN.1 BER data from inbuf 818 InputStream in = null; 819 820 try { 821 while (true) { 822 try { 823 // type and length (at most 128 octets for long form) 824 inbuf = new byte[129]; 825 826 offset = 0; 827 seqlen = 0; 828 seqlenlen = 0; 829 830 in = getInputStream(); 831 832 // check that it is the beginning of a sequence 833 bytesread = in.read(inbuf, offset, 1); 834 if (bytesread < 0) { 835 if (in != getInputStream()) { 836 continue; // a new stream to try 837 } else { 838 break; // EOF 839 } 840 } 841 842 if (inbuf[offset++] != (Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR)) 843 continue; 844 845 // get length of sequence 846 bytesread = in.read(inbuf, offset, 1); 847 if (bytesread < 0) 848 break; // EOF 849 seqlen = inbuf[offset++]; 850 851 // if high bit is on, length is encoded in the 852 // subsequent length bytes and the number of length bytes 853 // is equal to & 0x80 (i.e. length byte with high bit off). 854 if ((seqlen & 0x80) == 0x80) { 855 seqlenlen = seqlen & 0x7f; // number of length bytes 856 857 bytesread = 0; 858 eos = false; 859 860 // Read all length bytes 861 while (bytesread < seqlenlen) { 862 br = in.read(inbuf, offset+bytesread, 863 seqlenlen-bytesread); 864 if (br < 0) { 865 eos = true; 866 break; // EOF 867 } 868 bytesread += br; 869 } 870 871 // end-of-stream reached before length bytes are read 872 if (eos) 873 break; // EOF 874 875 // Add contents of length bytes to determine length 876 seqlen = 0; 877 for( int i = 0; i < seqlenlen; i++) { 878 seqlen = (seqlen << 8) + (inbuf[offset+i] & 0xff); 879 } 880 offset += bytesread; 881 } 882 883 // read in seqlen bytes 884 byte[] left = readFully(in, seqlen); 885 inbuf = Arrays.copyOf(inbuf, offset + left.length); 886 System.arraycopy(left, 0, inbuf, offset, left.length); 887 offset += left.length; 888 /* 889 if (dump > 0) { 890 System.err.println("seqlen: " + seqlen); 891 System.err.println("bufsize: " + offset); 892 System.err.println("bytesleft: " + bytesleft); 893 System.err.println("bytesread: " + bytesread); 894 } 895 */ 896 897 898 try { 899 retBer = new BerDecoder(inbuf, 0, offset); 900 901 if (traceFile != null) { 902 Ber.dumpBER(traceFile, traceTagIn, inbuf, 0, offset); 903 } 904 905 retBer.parseSeq(null); 906 inMsgId = retBer.parseInt(); 907 retBer.reset(); // reset offset 908 909 boolean needPause = false; 910 911 if (inMsgId == 0) { 912 // Unsolicited Notification 913 parent.processUnsolicited(retBer); 914 } else { 915 LdapRequest ldr = findRequest(inMsgId); 916 917 if (ldr != null) { 918 919 /** 920 * Grab pauseLock before making reply available 921 * to ensure that reader goes into paused state 922 * before writer can attempt to unpause reader 923 */ 924 synchronized (pauseLock) { 925 needPause = ldr.addReplyBer(retBer); 926 if (needPause) { 927 /* 928 * Go into paused state; release 929 * pauseLock 930 */ 931 pauseReader(); 932 } 933 934 // else release pauseLock 935 } 936 } else { 937 // System.err.println("Cannot find" + 938 // "LdapRequest for " + inMsgId); 939 } 940 } 941 } catch (Ber.DecodeException e) { 942 //System.err.println("Cannot parse Ber"); 943 } 944 } catch (IOException ie) { 945 if (debug) { 946 System.err.println("Connection: Inside Caught " + ie); 947 ie.printStackTrace(); 948 } 949 950 if (in != getInputStream()) { 951 // A new stream to try 952 // Go to top of loop and continue 953 } else { 954 if (debug) { 955 System.err.println("Connection: rethrowing " + ie); 956 } 957 throw ie; // rethrow exception 958 } 959 } 960 } 961 962 if (debug) { 963 System.err.println("Connection: end-of-stream detected: " 964 + in); 965 } 966 } catch (IOException ex) { 967 if (debug) { 968 System.err.println("Connection: Caught " + ex); 969 } 970 closureReason = ex; 971 } finally { 972 cleanup(null, true); // cleanup 973 } 974 if (debug) { 975 System.err.println("Connection: Thread Exiting"); 976 } 977 } 978 979 private static byte[] readFully(InputStream is, int length) 980 throws IOException 981 { 982 byte[] buf = new byte[Math.min(length, 8192)]; 983 int nread = 0; 984 while (nread < length) { 985 int bytesToRead; 986 if (nread >= buf.length) { // need to allocate a larger buffer 987 bytesToRead = Math.min(length - nread, buf.length + 8192); 988 if (buf.length < nread + bytesToRead) { 989 buf = Arrays.copyOf(buf, nread + bytesToRead); 990 } 991 } else { 992 bytesToRead = buf.length - nread; 993 } 994 int count = is.read(buf, nread, bytesToRead); 995 if (count < 0) { 996 if (buf.length != nread) 997 buf = Arrays.copyOf(buf, nread); 998 break; 999 } 1000 nread += count; 1001 } 1002 return buf; 1003 } 1004 1005 // This code must be uncommented to run the LdapAbandonTest. 1006 /*public void sendSearchReqs(String dn, int numReqs) { 1007 int i; 1008 String attrs[] = null; 1009 for(i = 1; i <= numReqs; i++) { 1010 BerEncoder ber = new BerEncoder(2048); 1011 1012 try { 1013 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); 1014 ber.encodeInt(i); 1015 ber.beginSeq(LdapClient.LDAP_REQ_SEARCH); 1016 ber.encodeString(dn == null ? "" : dn); 1017 ber.encodeInt(0, LdapClient.LBER_ENUMERATED); 1018 ber.encodeInt(3, LdapClient.LBER_ENUMERATED); 1019 ber.encodeInt(0); 1020 ber.encodeInt(0); 1021 ber.encodeBoolean(true); 1022 LdapClient.encodeFilter(ber, ""); 1023 ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); 1024 ber.encodeStringArray(attrs); 1025 ber.endSeq(); 1026 ber.endSeq(); 1027 ber.endSeq(); 1028 writeRequest(ber, i); 1029 //System.err.println("wrote request " + i); 1030 } catch (Exception ex) { 1031 //System.err.println("ldap.search: Caught " + ex + " building req"); 1032 } 1033 1034 } 1035 } */ 1036 }