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