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 }