8217606: LdapContext#reconnect always opens a new connection
Reviewed-by: lancea, vtewari, rriggs
This commit is contained in:
parent
392b5f8f62
commit
af89550878
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -224,7 +224,6 @@ final public class LdapCtx extends ComponentDirContext
|
||||
String hostname = null; // host name of server (no brackets
|
||||
// for IPv6 literals)
|
||||
LdapClient clnt = null; // connection handle
|
||||
private boolean reconnect = false; // indicates that re-connect requested
|
||||
Hashtable<String, java.lang.Object> envprops = null; // environment properties of context
|
||||
int handleReferrals = DEFAULT_REFERRAL_MODE; // how referral is handled
|
||||
boolean hasLdapsScheme = false; // true if the context was created
|
||||
@ -2668,7 +2667,6 @@ final public class LdapCtx extends ComponentDirContext
|
||||
}
|
||||
|
||||
sharable = false; // can't share with existing contexts
|
||||
reconnect = true;
|
||||
ensureOpen(); // open or reauthenticated
|
||||
}
|
||||
|
||||
@ -2693,7 +2691,8 @@ final public class LdapCtx extends ComponentDirContext
|
||||
synchronized (clnt) {
|
||||
if (!clnt.isLdapv3
|
||||
|| clnt.referenceCount > 1
|
||||
|| clnt.usingSaslStreams()) {
|
||||
|| clnt.usingSaslStreams()
|
||||
|| !clnt.conn.useable) {
|
||||
closeConnection(SOFT_CLOSE);
|
||||
}
|
||||
}
|
||||
@ -2745,7 +2744,7 @@ final public class LdapCtx extends ComponentDirContext
|
||||
try {
|
||||
boolean initial = (clnt == null);
|
||||
|
||||
if (initial || reconnect) {
|
||||
if (initial) {
|
||||
ldapVersion = (ver != null) ? Integer.parseInt(ver) :
|
||||
DEFAULT_LDAP_VERSION;
|
||||
|
||||
@ -2773,8 +2772,6 @@ final public class LdapCtx extends ComponentDirContext
|
||||
// Required for SASL client identity
|
||||
envprops);
|
||||
|
||||
reconnect = false;
|
||||
|
||||
/**
|
||||
* Pooled connections are preauthenticated;
|
||||
* newly created ones are not.
|
||||
|
117
test/jdk/com/sun/jndi/ldap/LdapCtx/Reconnect.java
Normal file
117
test/jdk/com/sun/jndi/ldap/LdapCtx/Reconnect.java
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import javax.naming.Context;
|
||||
import javax.naming.ldap.InitialLdapContext;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.Hashtable;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8217606
|
||||
* @summary The LdapContext.reconnect method allows LDAP clients to initiate an
|
||||
* LDAP bind operation on the existing connection. Invoking this method
|
||||
* should not open a new connection under those circumstances.
|
||||
*
|
||||
* @library ../lib/
|
||||
* @run main Reconnect
|
||||
*/
|
||||
public class Reconnect {
|
||||
|
||||
private static final byte[] BIND_RESPONSE = {
|
||||
0x30, 0x0C, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0A,
|
||||
0x01, 0x00, 0x04, 0x00, 0x04, 0x00
|
||||
};
|
||||
|
||||
/*
|
||||
* This test checks that there's only one connection from the client to
|
||||
* the server.
|
||||
*
|
||||
* The mechanics is as follows. The first connection is awaited for some
|
||||
* generous timeout to factor in a possibility of running on a slow system.
|
||||
* Once the connection has been made, the second timeout begins. This
|
||||
* second timeout is smaller. The test then verifies that no further
|
||||
* connections have been made for that amount of time.
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
final Semaphore s = new Semaphore(0);
|
||||
|
||||
BaseLdapServer server = new BaseLdapServer() {
|
||||
|
||||
@Override
|
||||
protected void beforeConnectionHandled(Socket socket) {
|
||||
// Increment the number of connections from LDAP client
|
||||
s.release(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleRequest(Socket socket,
|
||||
LdapMessage msg,
|
||||
OutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
switch (msg.getOperation()) {
|
||||
case BIND_REQUEST:
|
||||
out.write(BIND_RESPONSE);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try (var s1 = server.start()) {
|
||||
Hashtable<String, Object> env = new Hashtable<>();
|
||||
env.put(Context.INITIAL_CONTEXT_FACTORY,
|
||||
"com.sun.jndi.ldap.LdapCtxFactory");
|
||||
env.put(Context.PROVIDER_URL,
|
||||
"ldap://" + InetAddress.getLoopbackAddress().getHostName()
|
||||
+ ":" + server.getPort());
|
||||
env.put("java.naming.ldap.version", "3");
|
||||
|
||||
// open connection
|
||||
InitialLdapContext context = new InitialLdapContext(env, null);
|
||||
|
||||
// send bind request
|
||||
context.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
|
||||
context.addToEnvironment(Context.SECURITY_PRINCIPAL, "test");
|
||||
context.addToEnvironment(Context.SECURITY_CREDENTIALS, "secret");
|
||||
|
||||
context.reconnect(null);
|
||||
}
|
||||
|
||||
if (!s.tryAcquire(60L, TimeUnit.SECONDS)) {
|
||||
throw new RuntimeException("No connection has been made");
|
||||
}
|
||||
|
||||
if (s.tryAcquire(5L, TimeUnit.SECONDS)) {
|
||||
throw new RuntimeException("Expected 1 connection, but found: "
|
||||
+ (s.availablePermits() + 2));
|
||||
}
|
||||
}
|
||||
}
|
268
test/jdk/com/sun/jndi/ldap/lib/BaseLdapServer.java
Normal file
268
test/jdk/com/sun/jndi/ldap/lib/BaseLdapServer.java
Normal file
@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import static java.lang.System.Logger.Level.INFO;
|
||||
|
||||
/*
|
||||
* A bare-bones (testing aid) server for LDAP scenarios.
|
||||
*
|
||||
* Override the following methods to provide customized behavior
|
||||
*
|
||||
* * beforeConnectionHandled
|
||||
* * handleRequest
|
||||
*
|
||||
* Instances of this class are safe for use by multiple threads.
|
||||
*/
|
||||
public class BaseLdapServer implements Closeable {
|
||||
|
||||
private static final System.Logger logger = System.getLogger("BaseLdapServer");
|
||||
|
||||
private final Thread acceptingThread = new Thread(this::acceptConnections);
|
||||
private final ServerSocket serverSocket;
|
||||
private final List<Socket> socketList = new ArrayList<>();
|
||||
private final ExecutorService connectionsPool;
|
||||
|
||||
private final Object lock = new Object();
|
||||
/*
|
||||
* 3-valued state to detect restarts and other programming errors.
|
||||
*/
|
||||
private State state = State.NEW;
|
||||
|
||||
private enum State {
|
||||
NEW,
|
||||
STARTED,
|
||||
STOPPED
|
||||
}
|
||||
|
||||
public BaseLdapServer() throws IOException {
|
||||
this(new ServerSocket(0, 0, InetAddress.getLoopbackAddress()));
|
||||
}
|
||||
|
||||
public BaseLdapServer(ServerSocket serverSocket) {
|
||||
this.serverSocket = Objects.requireNonNull(serverSocket);
|
||||
this.connectionsPool = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
private void acceptConnections() {
|
||||
logger().log(INFO, "Server is accepting connections at port {0}",
|
||||
getPort());
|
||||
try {
|
||||
while (isRunning()) {
|
||||
Socket socket = serverSocket.accept();
|
||||
logger().log(INFO, "Accepted new connection at {0}", socket);
|
||||
synchronized (lock) {
|
||||
// Recheck if the server is still running
|
||||
// as someone has to close the `socket`
|
||||
if (isRunning()) {
|
||||
socketList.add(socket);
|
||||
} else {
|
||||
closeSilently(socket);
|
||||
}
|
||||
}
|
||||
connectionsPool.submit(() -> handleConnection(socket));
|
||||
}
|
||||
} catch (IOException | RejectedExecutionException e) {
|
||||
if (isRunning()) {
|
||||
throw new RuntimeException(
|
||||
"Unexpected exception while accepting connections", e);
|
||||
}
|
||||
} finally {
|
||||
logger().log(INFO, "Server stopped accepting connections at port {0}",
|
||||
getPort());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A "Template Method" describing how a connection (represented by a socket)
|
||||
* is handled.
|
||||
*
|
||||
* The socket is closed immediately before the method returns (normally or
|
||||
* abruptly).
|
||||
*/
|
||||
private void handleConnection(Socket socket) {
|
||||
// No need to close socket's streams separately, they will be closed
|
||||
// automatically when `socket.close()` is called
|
||||
beforeConnectionHandled(socket);
|
||||
try (socket) {
|
||||
OutputStream out = socket.getOutputStream();
|
||||
InputStream in = socket.getInputStream();
|
||||
byte[] inBuffer = new byte[1024];
|
||||
int count;
|
||||
byte[] request;
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
int msgLen = -1;
|
||||
|
||||
// As inBuffer.length > 0, at least 1 byte is read
|
||||
while ((count = in.read(inBuffer)) > 0) {
|
||||
buffer.write(inBuffer, 0, count);
|
||||
if (msgLen <= 0) {
|
||||
msgLen = LdapMessage.getMessageLength(buffer.toByteArray());
|
||||
}
|
||||
|
||||
if (msgLen > 0 && buffer.size() >= msgLen) {
|
||||
if (buffer.size() > msgLen) {
|
||||
byte[] tmpBuffer = buffer.toByteArray();
|
||||
request = Arrays.copyOf(tmpBuffer, msgLen);
|
||||
buffer.reset();
|
||||
buffer.write(tmpBuffer, msgLen, tmpBuffer.length - msgLen);
|
||||
} else {
|
||||
request = buffer.toByteArray();
|
||||
buffer.reset();
|
||||
}
|
||||
msgLen = -1;
|
||||
} else {
|
||||
logger.log(INFO, "Request message incomplete, " +
|
||||
"bytes received {0}, expected {1}", buffer.size(), msgLen);
|
||||
continue;
|
||||
}
|
||||
handleRequest(socket, new LdapMessage(request), out);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
if (!isRunning()) {
|
||||
logger.log(INFO, "Connection Handler exit {0}", t.getMessage());
|
||||
} else {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called first thing in `handleConnection()`.
|
||||
*
|
||||
* Override to customize the behavior.
|
||||
*/
|
||||
protected void beforeConnectionHandled(Socket socket) { /* empty */ }
|
||||
|
||||
/*
|
||||
* Called after an LDAP request has been read in `handleConnection()`.
|
||||
*
|
||||
* Override to customize the behavior.
|
||||
*/
|
||||
protected void handleRequest(Socket socket,
|
||||
LdapMessage request,
|
||||
OutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
logger().log(INFO, "Discarding message {0} from {1}. "
|
||||
+ "Override {2}.handleRequest to change this behavior.",
|
||||
request, socket, getClass().getName());
|
||||
}
|
||||
|
||||
/*
|
||||
* To be used by subclasses.
|
||||
*/
|
||||
protected final System.Logger logger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/*
|
||||
* Starts this server. May be called only once.
|
||||
*/
|
||||
public BaseLdapServer start() {
|
||||
synchronized (lock) {
|
||||
if (state != State.NEW) {
|
||||
throw new IllegalStateException(state.toString());
|
||||
}
|
||||
state = State.STARTED;
|
||||
logger().log(INFO, "Starting server at port {0}", getPort());
|
||||
acceptingThread.start();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Stops this server.
|
||||
*
|
||||
* May be called at any time, even before a call to `start()`. In the latter
|
||||
* case the subsequent call to `start()` will throw an exception. Repeated
|
||||
* calls to this method have no effect.
|
||||
*
|
||||
* Stops accepting new connections, interrupts the threads serving already
|
||||
* accepted connections and closes all the sockets.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
synchronized (lock) {
|
||||
if (state == State.STOPPED) {
|
||||
return;
|
||||
}
|
||||
state = State.STOPPED;
|
||||
logger().log(INFO, "Stopping server at port {0}", getPort());
|
||||
acceptingThread.interrupt();
|
||||
closeSilently(serverSocket);
|
||||
// It's important to signal an interruption so that overridden
|
||||
// methods have a chance to return if they use
|
||||
// interruption-sensitive blocking operations. However, blocked I/O
|
||||
// operations on the socket will NOT react on that, hence the socket
|
||||
// also has to be closed to propagate shutting down.
|
||||
connectionsPool.shutdownNow();
|
||||
socketList.forEach(BaseLdapServer.this::closeSilently);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local port this server is listening at.
|
||||
*
|
||||
* @return the port this server is listening at
|
||||
*/
|
||||
public int getPort() {
|
||||
return serverSocket.getLocalPort();
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a flag to indicate whether this server is running or not.
|
||||
*
|
||||
* @return {@code true} if this server is running, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
synchronized (lock) {
|
||||
return state == State.STARTED;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* To be used by subclasses.
|
||||
*/
|
||||
protected final void closeSilently(Closeable resource) {
|
||||
try {
|
||||
resource.close();
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
}
|
228
test/jdk/com/sun/jndi/ldap/lib/LdapMessage.java
Normal file
228
test/jdk/com/sun/jndi/ldap/lib/LdapMessage.java
Normal file
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* An LDAP message.
|
||||
*/
|
||||
public class LdapMessage {
|
||||
|
||||
private final byte[] message;
|
||||
private int messageID;
|
||||
private Operation operation;
|
||||
|
||||
public enum Operation {
|
||||
BIND_REQUEST(0x60, "BindRequest"), // [APPLICATION 0]
|
||||
BIND_RESPONSE(0x61, "BindResponse"), // [APPLICATION 1]
|
||||
UNBIND_REQUEST(0x42, "UnbindRequest"), // [APPLICATION 2]
|
||||
SEARCH_REQUEST(0x63, "SearchRequest"), // [APPLICATION 3]
|
||||
SEARCH_RESULT_ENTRY(0x64, "SearchResultEntry"), // [APPLICATION 4]
|
||||
SEARCH_RESULT_DONE(0x65, "SearchResultDone"), // [APPLICATION 5]
|
||||
MODIFY_REQUEST(0x66, "ModifyRequest"), // [APPLICATION 6]
|
||||
MODIFY_RESPONSE(0x67, "ModifyResponse"), // [APPLICATION 7]
|
||||
ADD_REQUEST(0x68, "AddRequest"), // [APPLICATION 8]
|
||||
ADD_RESPONSE(0x69, "AddResponse"), // [APPLICATION 9]
|
||||
DELETE_REQUEST(0x4A, "DeleteRequest"), // [APPLICATION 10]
|
||||
DELETE_RESPONSE(0x6B, "DeleteResponse"), // [APPLICATION 11]
|
||||
MODIFY_DN_REQUEST(0x6C, "ModifyDNRequest"), // [APPLICATION 12]
|
||||
MODIFY_DN_RESPONSE(0x6D, "ModifyDNResponse"), // [APPLICATION 13]
|
||||
COMPARE_REQUEST(0x6E, "CompareRequest"), // [APPLICATION 14]
|
||||
COMPARE_RESPONSE(0x6F, "CompareResponse"), // [APPLICATION 15]
|
||||
ABANDON_REQUEST(0x50, "AbandonRequest"), // [APPLICATION 16]
|
||||
SEARCH_RESULT_REFERENCE(0x73, "SearchResultReference"), // [APPLICATION 19]
|
||||
EXTENDED_REQUEST(0x77, "ExtendedRequest"), // [APPLICATION 23]
|
||||
EXTENDED_RESPONSE(0x78, "ExtendedResponse"), // [APPLICATION 24]
|
||||
INTERMEDIATE_RESPONSE(0x79, "IntermediateResponse"); // [APPLICATION 25]
|
||||
|
||||
private final int id;
|
||||
private final String name;
|
||||
|
||||
Operation(int id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
private static Operation fromId(int id) {
|
||||
Optional<Operation> optional = Stream.of(Operation.values())
|
||||
.filter(o -> o.id == id).findFirst();
|
||||
if (optional.isPresent()) {
|
||||
return optional.get();
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Unknown id " + id + " for enum Operation.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LdapMessage(byte[] message) {
|
||||
this.message = message;
|
||||
parse();
|
||||
}
|
||||
|
||||
public LdapMessage(String hexString) {
|
||||
this(parseHexBinary(hexString));
|
||||
}
|
||||
|
||||
// Extracts the message ID and operation ID from an LDAP protocol encoding
|
||||
private void parse() {
|
||||
if (message == null || message.length < 2) {
|
||||
throw new RuntimeException(
|
||||
"Invalid ldap message: " + Arrays.toString(message));
|
||||
}
|
||||
|
||||
if (message[0] != 0x30) {
|
||||
throw new RuntimeException("Bad LDAP encoding in message, "
|
||||
+ "expected ASN.1 SEQUENCE tag (0x30), encountered "
|
||||
+ message[0]);
|
||||
}
|
||||
|
||||
int index = 2;
|
||||
if ((message[1] & 0x80) == 0x80) {
|
||||
index += (message[1] & 0x0F);
|
||||
}
|
||||
|
||||
if (message[index] != 0x02) {
|
||||
throw new RuntimeException("Bad LDAP encoding in message, "
|
||||
+ "expected ASN.1 INTEGER tag (0x02), encountered "
|
||||
+ message[index]);
|
||||
}
|
||||
int length = message[index + 1];
|
||||
index += 2;
|
||||
messageID = new BigInteger(1,
|
||||
Arrays.copyOfRange(message, index, index + length)).intValue();
|
||||
index += length;
|
||||
int operationID = message[index];
|
||||
operation = Operation.fromId(operationID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return original ldap message in byte array.
|
||||
*
|
||||
* @return original ldap message
|
||||
*/
|
||||
public byte[] getMessage() {
|
||||
return Arrays.copyOf(message, message.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return ldap message id.
|
||||
*
|
||||
* @return ldap message id.
|
||||
*/
|
||||
public int getMessageID() {
|
||||
return messageID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return ldap message's operation.
|
||||
*
|
||||
* @return ldap message's operation.
|
||||
*/
|
||||
public Operation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
private static byte[] parseHexBinary(String s) {
|
||||
|
||||
final int len = s.length();
|
||||
|
||||
// "111" is not a valid hex encoding.
|
||||
if (len % 2 != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"hexBinary needs to be even-length: " + s);
|
||||
}
|
||||
|
||||
byte[] out = new byte[len / 2];
|
||||
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
int h = Character.digit(s.charAt(i), 16);
|
||||
int l = Character.digit(s.charAt(i + 1), 16);
|
||||
if (h == -1 || l == -1) {
|
||||
throw new IllegalArgumentException(
|
||||
"contains illegal character for hexBinary: " + s);
|
||||
}
|
||||
|
||||
out[i / 2] = (byte) (h * 16 + l);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static int getMessageLength(byte[] encoding) {
|
||||
if (encoding.length < 2) {
|
||||
// not enough data to extract msg len, just return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (encoding[0] != 0x30) {
|
||||
throw new RuntimeException("Error: bad LDAP encoding message: "
|
||||
+ "expected ASN.1 SEQUENCE tag (0x30), encountered "
|
||||
+ encoding[0]);
|
||||
}
|
||||
|
||||
int len;
|
||||
int index = 1;
|
||||
int payloadLen = 0;
|
||||
|
||||
if ((encoding[1] & 0x80) == 0x80) {
|
||||
len = (encoding[1] & 0x0F);
|
||||
index++;
|
||||
} else {
|
||||
len = 1;
|
||||
}
|
||||
|
||||
if (len > 4) {
|
||||
throw new RuntimeException(
|
||||
"Error: LDAP encoding message payload too large");
|
||||
}
|
||||
|
||||
if (encoding.length < index + len) {
|
||||
// additional data required to extract payload len, return -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (byte b : Arrays.copyOfRange(encoding, index, index + len)) {
|
||||
payloadLen = payloadLen << 8 | (b & 0xFF);
|
||||
}
|
||||
|
||||
if (payloadLen <= 0) {
|
||||
throw new RuntimeException(
|
||||
"Error: invalid LDAP encoding message length or payload too large");
|
||||
}
|
||||
|
||||
return index + len + payloadLen;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user