|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package com.sun.jndi.ldap; |
|
|
|
import java.io.BufferedInputStream; |
|
import java.io.BufferedOutputStream; |
|
import java.io.IOException; |
|
import java.io.InputStream; |
|
import java.io.InterruptedIOException; |
|
import java.io.OutputStream; |
|
import java.lang.reflect.Constructor; |
|
import java.lang.reflect.InvocationTargetException; |
|
import java.lang.reflect.Method; |
|
import java.net.Socket; |
|
import java.security.AccessController; |
|
import java.security.PrivilegedAction; |
|
import java.util.Arrays; |
|
|
|
import javax.naming.CommunicationException; |
|
import javax.naming.InterruptedNamingException; |
|
import javax.naming.NamingException; |
|
import javax.naming.ServiceUnavailableException; |
|
import javax.naming.ldap.Control; |
|
import javax.net.ssl.SSLParameters; |
|
import javax.net.ssl.SSLSocket; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final class Connection implements Runnable { |
|
|
|
private static final boolean debug = false; |
|
private static final int dump = 0; |
|
|
|
|
|
final private Thread worker; |
|
|
|
private boolean v3 = true; |
|
|
|
final public String host; |
|
// used by StartTlsResponse when creating an SSL socket |
|
final public int port; |
|
// used by StartTlsResponse when creating an SSL socket |
|
|
|
private boolean bound = false; |
|
|
|
|
|
private OutputStream traceFile = null; |
|
private String traceTagIn = null; |
|
private String traceTagOut = null; |
|
|
|
// Initialized in constructor; read and used externally (LdapSasl); |
|
// Updated in replaceStreams() during "quiet", unshared, period |
|
public InputStream inStream; |
|
|
|
// Initialized in constructor; read and used externally (LdapSasl); |
|
// Updated in replaceOutputStream() during "quiet", unshared, period |
|
public OutputStream outStream; |
|
|
|
// Initialized in constructor; read and used externally (TLS) to |
|
// get new IO streams; closed during cleanup |
|
public Socket sock; |
|
|
|
// For processing "disconnect" unsolicited notification |
|
|
|
final private LdapClient parent; |
|
|
|
|
|
private int outMsgId = 0; |
|
|
|
// |
|
// The list of ldapRequests pending on this binding |
|
// |
|
|
|
private LdapRequest pendingRequests = null; |
|
|
|
volatile IOException closureReason = null; |
|
volatile boolean useable = true; |
|
|
|
int readTimeout; |
|
int connectTimeout; |
|
|
|
|
|
private volatile boolean isUpgradedToStartTls; |
|
|
|
|
|
final Object startTlsLock = new Object(); |
|
|
|
private static final boolean IS_HOSTNAME_VERIFICATION_DISABLED |
|
= hostnameVerificationDisabledValue(); |
|
|
|
private static boolean hostnameVerificationDisabledValue() { |
|
PrivilegedAction<String> act = () -> System.getProperty( |
|
"com.sun.jndi.ldap.object.disableEndpointIdentification"); |
|
String prop = AccessController.doPrivileged(act); |
|
if (prop == null) { |
|
return false; |
|
} |
|
return prop.isEmpty() ? true : Boolean.parseBoolean(prop); |
|
} |
|
// true means v3; false means v2 |
|
// Called in LdapClient.authenticate() (which is synchronized) |
|
|
|
void setV3(boolean v) { |
|
v3 = v; |
|
} |
|
|
|
// A BIND request has been successfully made on this connection |
|
// When cleaning up, remember to do an UNBIND |
|
// Called in LdapClient.authenticate() (which is synchronized) |
|
|
|
void setBound() { |
|
bound = true; |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
// |
|
// Create an LDAP Binding object and bind to a particular server |
|
// |
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
Connection(LdapClient parent, String host, int port, String socketFactory, |
|
int connectTimeout, int readTimeout, OutputStream trace) throws NamingException { |
|
|
|
this.host = host; |
|
this.port = port; |
|
this.parent = parent; |
|
this.readTimeout = readTimeout; |
|
this.connectTimeout = connectTimeout; |
|
|
|
if (trace != null) { |
|
traceFile = trace; |
|
traceTagIn = "<- " + host + ":" + port + "\n\n"; |
|
traceTagOut = "-> " + host + ":" + port + "\n\n"; |
|
} |
|
|
|
// |
|
// Connect to server |
|
|
|
try { |
|
sock = createSocket(host, port, socketFactory, connectTimeout); |
|
|
|
if (debug) { |
|
System.err.println("Connection: opening socket: " + host + "," + port); |
|
} |
|
|
|
inStream = new BufferedInputStream(sock.getInputStream()); |
|
outStream = new BufferedOutputStream(sock.getOutputStream()); |
|
|
|
} catch (InvocationTargetException e) { |
|
Throwable realException = e.getTargetException(); |
|
// realException.printStackTrace(); |
|
|
|
CommunicationException ce = |
|
new CommunicationException(host + ":" + port); |
|
ce.setRootCause(realException); |
|
throw ce; |
|
} catch (Exception e) { |
|
// Class.forName() seems to do more error checking |
|
// and will throw IllegalArgumentException and such. |
|
// That's why we need to have a catch all here and |
|
// ignore generic exceptions. |
|
|
|
CommunicationException ce = |
|
new CommunicationException(host + ":" + port); |
|
ce.setRootCause(e); |
|
throw ce; |
|
} |
|
|
|
worker = Obj.helper.createThread(this); |
|
worker.setDaemon(true); |
|
worker.start(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private Object createInetSocketAddress(String host, int port) |
|
throws NoSuchMethodException { |
|
|
|
try { |
|
Class<?> inetSocketAddressClass = |
|
Class.forName("java.net.InetSocketAddress"); |
|
|
|
Constructor<?> inetSocketAddressCons = |
|
inetSocketAddressClass.getConstructor(new Class<?>[]{ |
|
String.class, int.class}); |
|
|
|
return inetSocketAddressCons.newInstance(new Object[]{ |
|
host, new Integer(port)}); |
|
|
|
} catch (ClassNotFoundException | |
|
InstantiationException | |
|
InvocationTargetException | |
|
IllegalAccessException e) { |
|
throw new NoSuchMethodException(); |
|
|
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private Socket createSocket(String host, int port, String socketFactory, |
|
int connectTimeout) throws Exception { |
|
|
|
Socket socket = null; |
|
|
|
if (socketFactory != null) { |
|
|
|
// create the factory |
|
|
|
Class<?> socketFactoryClass = Obj.helper.loadClass(socketFactory); |
|
Method getDefault = |
|
socketFactoryClass.getMethod("getDefault", new Class<?>[]{}); |
|
Object factory = getDefault.invoke(null, new Object[]{}); |
|
|
|
// create the socket |
|
|
|
Method createSocket = null; |
|
|
|
if (connectTimeout > 0) { |
|
|
|
try { |
|
createSocket = socketFactoryClass.getMethod("createSocket", |
|
new Class<?>[]{}); |
|
|
|
Method connect = Socket.class.getMethod("connect", |
|
new Class<?>[]{Class.forName("java.net.SocketAddress"), |
|
int.class}); |
|
Object endpoint = createInetSocketAddress(host, port); |
|
|
|
|
|
socket = |
|
(Socket)createSocket.invoke(factory, new Object[]{}); |
|
|
|
if (debug) { |
|
System.err.println("Connection: creating socket with " + |
|
"a timeout using supplied socket factory"); |
|
} |
|
|
|
|
|
connect.invoke(socket, new Object[]{ |
|
endpoint, new Integer(connectTimeout)}); |
|
|
|
} catch (NoSuchMethodException e) { |
|
// continue (but ignore connectTimeout) |
|
} |
|
} |
|
|
|
if (socket == null) { |
|
createSocket = socketFactoryClass.getMethod("createSocket", |
|
new Class<?>[]{String.class, int.class}); |
|
|
|
if (debug) { |
|
System.err.println("Connection: creating socket using " + |
|
"supplied socket factory"); |
|
} |
|
|
|
socket = (Socket) createSocket.invoke(factory, |
|
new Object[]{host, new Integer(port)}); |
|
} |
|
} else { |
|
|
|
if (connectTimeout > 0) { |
|
|
|
try { |
|
Constructor<Socket> socketCons = |
|
Socket.class.getConstructor(new Class<?>[]{}); |
|
|
|
Method connect = Socket.class.getMethod("connect", |
|
new Class<?>[]{Class.forName("java.net.SocketAddress"), |
|
int.class}); |
|
Object endpoint = createInetSocketAddress(host, port); |
|
|
|
socket = socketCons.newInstance(new Object[]{}); |
|
|
|
if (debug) { |
|
System.err.println("Connection: creating socket with " + |
|
"a timeout"); |
|
} |
|
connect.invoke(socket, new Object[]{ |
|
endpoint, new Integer(connectTimeout)}); |
|
|
|
} catch (NoSuchMethodException e) { |
|
// continue (but ignore connectTimeout) |
|
} |
|
} |
|
|
|
if (socket == null) { |
|
if (debug) { |
|
System.err.println("Connection: creating socket"); |
|
} |
|
|
|
socket = new Socket(host, port); |
|
} |
|
} |
|
|
|
// For LDAP connect timeouts on LDAP over SSL connections must treat |
|
// the SSL handshake following socket connection as part of the timeout. |
|
// So explicitly set a socket read timeout, trigger the SSL handshake, |
|
|
|
if (socket instanceof SSLSocket) { |
|
SSLSocket sslSocket = (SSLSocket) socket; |
|
if (!IS_HOSTNAME_VERIFICATION_DISABLED) { |
|
SSLParameters param = sslSocket.getSSLParameters(); |
|
param.setEndpointIdentificationAlgorithm("LDAPS"); |
|
sslSocket.setSSLParameters(param); |
|
} |
|
if (connectTimeout > 0) { |
|
int socketTimeout = sslSocket.getSoTimeout(); |
|
sslSocket.setSoTimeout(connectTimeout); |
|
sslSocket.startHandshake(); |
|
sslSocket.setSoTimeout(socketTimeout); |
|
} |
|
} |
|
return socket; |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
// |
|
// Methods to IO to the LDAP server |
|
// |
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
synchronized int getMsgId() { |
|
return ++outMsgId; |
|
} |
|
|
|
LdapRequest writeRequest(BerEncoder ber, int msgId) throws IOException { |
|
return writeRequest(ber, msgId, false , -1); |
|
} |
|
|
|
LdapRequest writeRequest(BerEncoder ber, int msgId, |
|
boolean pauseAfterReceipt) throws IOException { |
|
return writeRequest(ber, msgId, pauseAfterReceipt, -1); |
|
} |
|
|
|
LdapRequest writeRequest(BerEncoder ber, int msgId, |
|
boolean pauseAfterReceipt, int replyQueueCapacity) throws IOException { |
|
|
|
LdapRequest req = |
|
new LdapRequest(msgId, pauseAfterReceipt, replyQueueCapacity); |
|
addRequest(req); |
|
|
|
if (traceFile != null) { |
|
Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 0, ber.getDataLen()); |
|
} |
|
|
|
|
|
// unpause reader so that it can get response |
|
// NOTE: Must do this before writing request, otherwise might |
|
|
|
unpauseReader(); |
|
|
|
if (debug) { |
|
System.err.println("Writing request to: " + outStream); |
|
} |
|
|
|
try { |
|
synchronized (this) { |
|
outStream.write(ber.getBuf(), 0, ber.getDataLen()); |
|
outStream.flush(); |
|
} |
|
} catch (IOException e) { |
|
cleanup(null, true); |
|
throw (closureReason = e); |
|
} |
|
|
|
return req; |
|
} |
|
|
|
|
|
|
|
*/ |
|
BerDecoder readReply(LdapRequest ldr) throws IOException, NamingException { |
|
BerDecoder rber; |
|
|
|
NamingException namingException = null; |
|
try { |
|
// if no timeout is set so we wait infinitely until |
|
// a response is received OR until the connection is closed or cancelled |
|
|
|
rber = ldr.getReplyBer(readTimeout); |
|
} catch (InterruptedException ex) { |
|
throw new InterruptedNamingException( |
|
"Interrupted during LDAP operation"); |
|
} catch (CommunicationException ce) { |
|
|
|
throw ce; |
|
} catch (NamingException ne) { |
|
|
|
namingException = ne; |
|
rber = null; |
|
} |
|
|
|
if (rber == null) { |
|
abandonRequest(ldr, null); |
|
} |
|
// namingException can be not null in the following cases: |
|
// a) The response is timed-out |
|
// b) LDAP request connection has been closed or cancelled |
|
|
|
if (namingException != null) { |
|
|
|
throw namingException; |
|
} |
|
return rber; |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
// |
|
// Methods to add, find, delete, and abandon requests made to server |
|
// |
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
private synchronized void addRequest(LdapRequest ldapRequest) { |
|
|
|
LdapRequest ldr = pendingRequests; |
|
if (ldr == null) { |
|
pendingRequests = ldapRequest; |
|
ldapRequest.next = null; |
|
} else { |
|
ldapRequest.next = pendingRequests; |
|
pendingRequests = ldapRequest; |
|
} |
|
} |
|
|
|
synchronized LdapRequest findRequest(int msgId) { |
|
|
|
LdapRequest ldr = pendingRequests; |
|
while (ldr != null) { |
|
if (ldr.msgId == msgId) { |
|
return ldr; |
|
} |
|
ldr = ldr.next; |
|
} |
|
return null; |
|
|
|
} |
|
|
|
synchronized void removeRequest(LdapRequest req) { |
|
LdapRequest ldr = pendingRequests; |
|
LdapRequest ldrprev = null; |
|
|
|
while (ldr != null) { |
|
if (ldr == req) { |
|
ldr.cancel(); |
|
|
|
if (ldrprev != null) { |
|
ldrprev.next = ldr.next; |
|
} else { |
|
pendingRequests = ldr.next; |
|
} |
|
ldr.next = null; |
|
} |
|
ldrprev = ldr; |
|
ldr = ldr.next; |
|
} |
|
} |
|
|
|
void abandonRequest(LdapRequest ldr, Control[] reqCtls) { |
|
|
|
removeRequest(ldr); |
|
|
|
BerEncoder ber = new BerEncoder(256); |
|
int abandonMsgId = getMsgId(); |
|
|
|
// |
|
// build the abandon request. |
|
|
|
try { |
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); |
|
ber.encodeInt(abandonMsgId); |
|
ber.encodeInt(ldr.msgId, LdapClient.LDAP_REQ_ABANDON); |
|
|
|
if (v3) { |
|
LdapClient.encodeControls(ber, reqCtls); |
|
} |
|
ber.endSeq(); |
|
|
|
if (traceFile != null) { |
|
Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), 0, |
|
ber.getDataLen()); |
|
} |
|
|
|
synchronized (this) { |
|
outStream.write(ber.getBuf(), 0, ber.getDataLen()); |
|
outStream.flush(); |
|
} |
|
|
|
} catch (IOException ex) { |
|
//System.err.println("ldap.abandon: " + ex); |
|
} |
|
|
|
// Don't expect any response for the abandon request. |
|
} |
|
|
|
synchronized void abandonOutstandingReqs(Control[] reqCtls) { |
|
LdapRequest ldr = pendingRequests; |
|
|
|
while (ldr != null) { |
|
abandonRequest(ldr, reqCtls); |
|
pendingRequests = ldr = ldr.next; |
|
} |
|
} |
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
// |
|
// Methods to unbind from server and clear up resources when object is |
|
// destroyed. |
|
// |
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
private void ldapUnbind(Control[] reqCtls) { |
|
|
|
BerEncoder ber = new BerEncoder(256); |
|
int unbindMsgId = getMsgId(); |
|
|
|
// |
|
// build the unbind request. |
|
// |
|
|
|
try { |
|
|
|
ber.beginSeq(Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR); |
|
ber.encodeInt(unbindMsgId); |
|
|
|
ber.encodeByte(LdapClient.LDAP_REQ_UNBIND); |
|
ber.encodeByte(0); |
|
|
|
if (v3) { |
|
LdapClient.encodeControls(ber, reqCtls); |
|
} |
|
ber.endSeq(); |
|
|
|
if (traceFile != null) { |
|
Ber.dumpBER(traceFile, traceTagOut, ber.getBuf(), |
|
0, ber.getDataLen()); |
|
} |
|
|
|
synchronized (this) { |
|
outStream.write(ber.getBuf(), 0, ber.getDataLen()); |
|
outStream.flush(); |
|
} |
|
|
|
} catch (IOException ex) { |
|
//System.err.println("ldap.unbind: " + ex); |
|
} |
|
|
|
// Don't expect any response for the unbind request. |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void cleanup(Control[] reqCtls, boolean notifyParent) { |
|
boolean nparent = false; |
|
|
|
synchronized (this) { |
|
useable = false; |
|
|
|
if (sock != null) { |
|
if (debug) { |
|
System.err.println("Connection: closing socket: " + host + "," + port); |
|
} |
|
try { |
|
if (!notifyParent) { |
|
abandonOutstandingReqs(reqCtls); |
|
} |
|
if (bound) { |
|
ldapUnbind(reqCtls); |
|
} |
|
} finally { |
|
try { |
|
outStream.flush(); |
|
sock.close(); |
|
unpauseReader(); |
|
} catch (IOException ie) { |
|
if (debug) |
|
System.err.println("Connection: problem closing socket: " + ie); |
|
} |
|
if (!notifyParent) { |
|
LdapRequest ldr = pendingRequests; |
|
while (ldr != null) { |
|
ldr.cancel(); |
|
ldr = ldr.next; |
|
} |
|
} |
|
sock = null; |
|
} |
|
nparent = notifyParent; |
|
} |
|
if (nparent) { |
|
LdapRequest ldr = pendingRequests; |
|
while (ldr != null) { |
|
ldr.close(); |
|
ldr = ldr.next; |
|
} |
|
} |
|
} |
|
if (nparent) { |
|
parent.processConnectionClosure(); |
|
} |
|
} |
|
|
|
|
|
// Assume everything is "quiet" |
|
// "synchronize" might lead to deadlock so don't synchronize method |
|
// Use streamLock instead for synchronizing update to stream |
|
|
|
synchronized public void replaceStreams(InputStream newIn, OutputStream newOut) { |
|
if (debug) { |
|
System.err.println("Replacing " + inStream + " with: " + newIn); |
|
System.err.println("Replacing " + outStream + " with: " + newOut); |
|
} |
|
|
|
inStream = newIn; |
|
|
|
|
|
try { |
|
outStream.flush(); |
|
} catch (IOException ie) { |
|
if (debug) |
|
System.err.println("Connection: cannot flush outstream: " + ie); |
|
} |
|
|
|
|
|
outStream = newOut; |
|
} |
|
|
|
|
|
|
|
*/ |
|
synchronized public void replaceStreams(InputStream newIn, OutputStream newOut, boolean isStartTls) { |
|
synchronized (startTlsLock) { |
|
replaceStreams(newIn, newOut); |
|
isUpgradedToStartTls = isStartTls; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public boolean isUpgradedToStartTls() { |
|
return isUpgradedToStartTls; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
synchronized private InputStream getInputStream() { |
|
return inStream; |
|
} |
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
// |
|
// Code for pausing/unpausing the reader thread ('worker') |
|
// |
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
/* |
|
* The main idea is to mark requests that need the reader thread to |
|
* pause after getting the response. When the reader thread gets the response, |
|
* it waits on a lock instead of returning to the read(). The next time a |
|
* request is sent, the reader is automatically unblocked if necessary. |
|
* Note that the reader must be unblocked BEFORE the request is sent. |
|
* Otherwise, there is a race condition where the request is sent and |
|
* the reader thread might read the response and be unblocked |
|
* by writeRequest(). |
|
* |
|
* This pause gives the main thread (StartTLS or SASL) an opportunity to |
|
* update the reader's state (e.g., its streams) if necessary. |
|
* The assumption is that the connection will remain quiet during this pause |
|
* (i.e., no intervening requests being sent). |
|
*<p> |
|
* For dealing with StartTLS close, |
|
* when the read() exits either due to EOF or an exception, |
|
* the reader thread checks whether there is a new stream to read from. |
|
* If so, then it reattempts the read. Otherwise, the EOF or exception |
|
* is processed and the reader thread terminates. |
|
* In a StartTLS close, the client first replaces the SSL IO streams with |
|
* plain ones and then closes the SSL socket. |
|
* If the reader thread attempts to read, or was reading, from |
|
* the SSL socket (that is, it got to the read BEFORE replaceStreams()), |
|
* the SSL socket close will cause the reader thread to |
|
* get an EOF/exception and reexamine the input stream. |
|
* If the reader thread sees a new stream, it reattempts the read. |
|
* If the underlying socket is still alive, then the new read will succeed. |
|
* If the underlying socket has been closed also, then the new read will |
|
* fail and the reader thread exits. |
|
* If the reader thread attempts to read, or was reading, from the plain |
|
* socket (that is, it got to the read AFTER replaceStreams()), the |
|
* SSL socket close will have no effect on the reader thread. |
|
* |
|
* The check for new stream is made only |
|
* in the first attempt at reading a BER buffer; the reader should |
|
* never be in midst of reading a buffer when a nonfatal close occurs. |
|
* If this occurs, then the connection is in an inconsistent state and |
|
* the safest thing to do is to shut it down. |
|
*/ |
|
|
|
private final Object pauseLock = new Object(); |
|
private boolean paused = false; |
|
|
|
|
|
|
|
*/ |
|
private void unpauseReader() throws IOException { |
|
synchronized (pauseLock) { |
|
if (paused) { |
|
if (debug) { |
|
System.err.println("Unpausing reader; read from: " + |
|
inStream); |
|
} |
|
paused = false; |
|
pauseLock.notify(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void pauseReader() throws IOException { |
|
if (debug) { |
|
System.err.println("Pausing reader; was reading from: " + |
|
inStream); |
|
} |
|
paused = true; |
|
try { |
|
while (paused) { |
|
pauseLock.wait(); |
|
} |
|
} catch (InterruptedException e) { |
|
throw new InterruptedIOException( |
|
"Pause/unpause reader has problems."); |
|
} |
|
} |
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////// |
|
// |
|
// The LDAP Binding thread. It does the mux/demux of multiple requests |
|
// on the same TCP connection. |
|
// |
|
//////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
public void run() { |
|
byte inbuf[]; |
|
int inMsgId; |
|
int bytesread; |
|
int br; |
|
int offset; |
|
int seqlen; |
|
int seqlenlen; |
|
boolean eos; |
|
BerDecoder retBer; |
|
InputStream in = null; |
|
|
|
try { |
|
while (true) { |
|
try { |
|
|
|
inbuf = new byte[129]; |
|
|
|
offset = 0; |
|
seqlen = 0; |
|
seqlenlen = 0; |
|
|
|
in = getInputStream(); |
|
|
|
|
|
bytesread = in.read(inbuf, offset, 1); |
|
if (bytesread < 0) { |
|
if (in != getInputStream()) { |
|
continue; |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
if (inbuf[offset++] != (Ber.ASN_SEQUENCE | Ber.ASN_CONSTRUCTOR)) |
|
continue; |
|
|
|
|
|
bytesread = in.read(inbuf, offset, 1); |
|
if (bytesread < 0) |
|
break; |
|
seqlen = inbuf[offset++]; |
|
|
|
// if high bit is on, length is encoded in the |
|
// subsequent length bytes and the number of length bytes |
|
|
|
if ((seqlen & 0x80) == 0x80) { |
|
seqlenlen = seqlen & 0x7f; |
|
// Check the length of length field, since seqlen is int |
|
|
|
if (seqlenlen > 4) { |
|
throw new IOException("Length coded with too many bytes: " + seqlenlen); |
|
} |
|
|
|
bytesread = 0; |
|
eos = false; |
|
|
|
|
|
while (bytesread < seqlenlen) { |
|
br = in.read(inbuf, offset+bytesread, |
|
seqlenlen-bytesread); |
|
if (br < 0) { |
|
eos = true; |
|
break; |
|
} |
|
bytesread += br; |
|
} |
|
|
|
|
|
if (eos) |
|
break; |
|
|
|
|
|
seqlen = 0; |
|
for( int i = 0; i < seqlenlen; i++) { |
|
seqlen = (seqlen << 8) + (inbuf[offset+i] & 0xff); |
|
} |
|
offset += bytesread; |
|
} |
|
|
|
if (seqlenlen > bytesread) { |
|
throw new IOException("Unexpected EOF while reading length"); |
|
} |
|
|
|
if (seqlen < 0) { |
|
throw new IOException("Length too big: " + (((long) seqlen) & 0xFFFFFFFFL)); |
|
} |
|
|
|
byte[] left = readFully(in, seqlen); |
|
inbuf = Arrays.copyOf(inbuf, offset + left.length); |
|
System.arraycopy(left, 0, inbuf, offset, left.length); |
|
offset += left.length; |
|
|
|
try { |
|
retBer = new BerDecoder(inbuf, 0, offset); |
|
|
|
if (traceFile != null) { |
|
Ber.dumpBER(traceFile, traceTagIn, inbuf, 0, offset); |
|
} |
|
|
|
retBer.parseSeq(null); |
|
inMsgId = retBer.parseInt(); |
|
retBer.reset(); |
|
|
|
boolean needPause = false; |
|
|
|
if (inMsgId == 0) { |
|
|
|
parent.processUnsolicited(retBer); |
|
} else { |
|
LdapRequest ldr = findRequest(inMsgId); |
|
|
|
if (ldr != null) { |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
synchronized (pauseLock) { |
|
needPause = ldr.addReplyBer(retBer); |
|
if (needPause) { |
|
|
|
|
|
|
|
*/ |
|
pauseReader(); |
|
} |
|
|
|
// else release pauseLock |
|
} |
|
} else { |
|
// System.err.println("Cannot find" + |
|
// "LdapRequest for " + inMsgId); |
|
} |
|
} |
|
} catch (Ber.DecodeException e) { |
|
//System.err.println("Cannot parse Ber"); |
|
} |
|
} catch (IOException ie) { |
|
if (debug) { |
|
System.err.println("Connection: Inside Caught " + ie); |
|
ie.printStackTrace(); |
|
} |
|
|
|
if (in != getInputStream()) { |
|
// A new stream to try |
|
// Go to top of loop and continue |
|
} else { |
|
if (debug) { |
|
System.err.println("Connection: rethrowing " + ie); |
|
} |
|
throw ie; |
|
} |
|
} |
|
} |
|
|
|
if (debug) { |
|
System.err.println("Connection: end-of-stream detected: " |
|
+ in); |
|
} |
|
} catch (IOException ex) { |
|
if (debug) { |
|
System.err.println("Connection: Caught " + ex); |
|
} |
|
closureReason = ex; |
|
} finally { |
|
cleanup(null, true); |
|
} |
|
if (debug) { |
|
System.err.println("Connection: Thread Exiting"); |
|
} |
|
} |
|
|
|
private static byte[] readFully(InputStream is, int length) |
|
throws IOException |
|
{ |
|
byte[] buf = new byte[Math.min(length, 8192)]; |
|
int nread = 0; |
|
while (nread < length) { |
|
int bytesToRead; |
|
if (nread >= buf.length) { |
|
bytesToRead = Math.min(length - nread, buf.length + 8192); |
|
if (buf.length < nread + bytesToRead) { |
|
buf = Arrays.copyOf(buf, nread + bytesToRead); |
|
} |
|
} else { |
|
bytesToRead = buf.length - nread; |
|
} |
|
int count = is.read(buf, nread, bytesToRead); |
|
if (count < 0) { |
|
if (buf.length != nread) |
|
buf = Arrays.copyOf(buf, nread); |
|
break; |
|
} |
|
nread += count; |
|
} |
|
return buf; |
|
} |
|
} |