src/share/classes/com/sun/jndi/ldap/Connection.java

Wed, 09 Sep 2020 14:19:14 -0400

author
zgu
date
Wed, 09 Sep 2020 14:19:14 -0400
changeset 14206
ab2e99db6702
parent 13833
c9e76bc2aae1
child 14211
ccf97104b8ea
permissions
-rw-r--r--

8062947: Fix exception message to correctly represent LDAP connection failure
Reviewed-by: dfuchs, xyin, vtewari

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

mercurial