Wed, 09 Sep 2020 14:19:14 -0400
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 | } |