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