From dfb4f94fefa3ba20073b8fa2986b7e39d4f290a8 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Mon, 20 Aug 2007 16:33:24 +0000
Subject: [PATCH] Add a new internal LDAP socket implementation that provides a mechanism which allows third-party LDAP SDKs to be used to perform internal operations within the server. Rather than performing network communication, the custom socket decodes the request written to it, converts it to an internal operation, processes the request, and encodes the response so that it can be read from the socket by the LDAP SDK.
---
opends/src/messages/messages/protocol.properties | 7
opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocket.java | 860 +++++++++++++++
opends/src/server/org/opends/server/protocols/internal/NullLDAPMessage.java | 58 +
opends/src/server/org/opends/server/protocols/internal/InternalLDAPOutputStream.java | 1029 ++++++++++++++++++
opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocketFactory.java | 180 +++
opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalLDAPSocketTestCase.java | 662 ++++++++++++
opends/src/server/org/opends/server/protocols/internal/InternalLDAPInputStream.java | 497 +++++++++
7 files changed, 3,293 insertions(+), 0 deletions(-)
diff --git a/opends/src/messages/messages/protocol.properties b/opends/src/messages/messages/protocol.properties
index 0c80603..3278984 100644
--- a/opends/src/messages/messages/protocol.properties
+++ b/opends/src/messages/messages/protocol.properties
@@ -1400,3 +1400,10 @@
privileges to establish the connection through JMX. At least JMX_READ \
privilege is required
MILD_ERR_INTERNALCONN_NO_SUCH_USER_440=User %s does not exist in the directory
+MILD_ERR_INTERNALOS_CLOSED_441=This output stream has been closed
+MILD_ERR_INTERNALOS_INVALID_REQUEST_442=The provided LDAP message had an \
+ invalid operation type (%s) for a request
+MILD_ERR_INTERNALOS_SASL_BIND_NOT_SUPPORTED_443=SASL bind operations are not \
+ supported over internal LDAP sockets
+MILD_ERR_INTERNALOS_STARTTLS_NOT_SUPPORTED_444=StartTLS operations are not \
+ supported over internal LDAP sockets
diff --git a/opends/src/server/org/opends/server/protocols/internal/InternalLDAPInputStream.java b/opends/src/server/org/opends/server/protocols/internal/InternalLDAPInputStream.java
new file mode 100644
index 0000000..b29cdbb
--- /dev/null
+++ b/opends/src/server/org/opends/server/protocols/internal/InternalLDAPInputStream.java
@@ -0,0 +1,497 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.protocols.internal;
+
+
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import org.opends.server.protocols.ldap.LDAPMessage;
+
+
+
+/**
+ * This class provides an implementation of a
+ * {@code java.io.InputStream} that can be used to facilitate internal
+ * communication with the Directory Server. On the backend, this
+ * input stream will be populated by ASN.1 elements encoded from LDAP
+ * messages created from internal operation responses.
+ */
+@org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
+ mayInstantiate=false,
+ mayExtend=false,
+ mayInvoke=true)
+public final class InternalLDAPInputStream
+ extends InputStream
+{
+ // The queue of LDAP messages providing the data to be made
+ // available to the client.
+ private ArrayBlockingQueue<LDAPMessage> messageQueue;
+
+ // Indicates whether this stream has been closed.
+ private boolean closed;
+
+ // The byte buffer with partial data to be written to the client.
+ private ByteBuffer partialMessageBuffer;
+
+ // The internal LDAP socket serviced by this input stream.
+ private InternalLDAPSocket socket;
+
+
+
+ /**
+ * Creates a new internal LDAP input stream that will service the
+ * provided internal LDAP socket.
+ *
+ * @param socket The internal LDAP socket serviced by this
+ * internal LDAP input stream.
+ */
+ public InternalLDAPInputStream(InternalLDAPSocket socket)
+ {
+ this.socket = socket;
+
+ messageQueue = new ArrayBlockingQueue<LDAPMessage>(10);
+ partialMessageBuffer = null;
+ closed = false;
+ }
+
+
+
+ /**
+ * Adds the provided LDAP message to the set of messages to be
+ * returned to the client. Note that this may block if there is
+ * already a significant backlog of messages to be returned.
+ *
+ * @param message The message to add to the set of messages to be
+ * returned to the client.
+ */
+ @org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.PRIVATE,
+ mayInstantiate=false,
+ mayExtend=false,
+ mayInvoke=false)
+ void addLDAPMessage(LDAPMessage message)
+ {
+ // If the stream is closed, then simply drop the message.
+ if (closed)
+ {
+ return;
+ }
+
+ try
+ {
+ messageQueue.put(message);
+ return;
+ }
+ catch (Exception e)
+ {
+ // This shouldn't happen, but if it does then try three more
+ // times before giving up and dropping the message.
+ for (int i=0; i < 3; i++)
+ {
+ try
+ {
+ messageQueue.put(message);
+ break;
+ } catch (Exception e2) {}
+ }
+
+ return;
+ }
+ }
+
+
+
+ /**
+ * Retrieves the number of bytes that can be read (or skipped over)
+ * from this input stream without blocking.
+ *
+ * @return The number of bytes that can be read (or skipped over)
+ * from this input stream wihtout blocking.
+ */
+ @Override()
+ public synchronized int available()
+ {
+ if (partialMessageBuffer == null)
+ {
+ LDAPMessage message = messageQueue.poll();
+ if ((message == null) || (message instanceof NullLDAPMessage))
+ {
+ if (message instanceof NullLDAPMessage)
+ {
+ closed = true;
+ }
+
+ return 0;
+ }
+ else
+ {
+ partialMessageBuffer =
+ ByteBuffer.wrap(message.encode().encode());
+ return partialMessageBuffer.remaining();
+ }
+ }
+ else
+ {
+ return partialMessageBuffer.remaining();
+ }
+ }
+
+
+
+ /**
+ * Closes this input stream. This will add a special marker
+ * element to the message queue indicating that the end of the
+ * stream has been reached. If the queue is full, thenit will be
+ * cleared before adding the marker element.
+ */
+ @Override()
+ public void close()
+ {
+ socket.close();
+ }
+
+
+
+ /**
+ * Closes this input stream through an internal mechanism that will
+ * not cause an infinite recursion loop by trying to also close the
+ * input stream.
+ */
+ @org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.PRIVATE,
+ mayInstantiate=false,
+ mayExtend=false,
+ mayInvoke=false)
+ void closeInternal()
+ {
+ if (closed)
+ {
+ return;
+ }
+
+ closed = true;
+ NullLDAPMessage nullMessage = new NullLDAPMessage();
+
+ while (! messageQueue.offer(nullMessage))
+ {
+ messageQueue.clear();
+ }
+ }
+
+
+
+ /**
+ * Marks the current position in the input stream. This will not
+ * have any effect, as this input stream inplementation does not
+ * support marking.
+ *
+ * @param readLimit The maximum limit of bytes that can be read
+ * before the mark position becomes invalid.
+ */
+ @Override()
+ public void mark(int readLimit)
+ {
+ // No implementation is required.
+ }
+
+
+
+ /**
+ * Indicates whether this input stream inplementation supports the
+ * use of the {@code mark} and {@code reset} methods. This
+ * implementation does not support that functionality.
+ *
+ * @return {@code false} because this implementation does not
+ * support the use of the {@code mark} and {@code reset}
+ * methods.
+ */
+ @Override()
+ public boolean markSupported()
+ {
+ return false;
+ }
+
+
+
+ /**
+ * Reads the next byte of data from the input stream, blocking if
+ * necessary until there is data available.
+ *
+ * @return The next byte of data read from the input stream, or -1
+ * if the end of the input stream has been reached.
+ *
+ * @throws IOException If a problem occurs while trying to read
+ * data from the stream.
+ */
+ @Override()
+ public synchronized int read()
+ throws IOException
+ {
+ if (partialMessageBuffer != null)
+ {
+ if (partialMessageBuffer.remaining() > 0)
+ {
+ int i = (0xFF & partialMessageBuffer.get());
+ if (partialMessageBuffer.remaining() == 0)
+ {
+ partialMessageBuffer = null;
+ }
+
+ return i;
+ }
+ else
+ {
+ partialMessageBuffer = null;
+ }
+ }
+
+ if (closed)
+ {
+ return -1;
+ }
+
+ try
+ {
+ LDAPMessage message = messageQueue.take();
+ if (message instanceof NullLDAPMessage)
+ {
+ messageQueue.clear();
+ closed = true;
+ return -1;
+ }
+
+ partialMessageBuffer =
+ ByteBuffer.wrap(message.encode().encode());
+ return (0xFF & partialMessageBuffer.get());
+ }
+ catch (Exception e)
+ {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+
+
+ /**
+ * Reads some number of bytes from the input stream, blocking if
+ * necessary until there is data available, and adds them to the
+ * provided array starting at position 0.
+ *
+ * @param b The array to which the data is to be written.
+ *
+ * @return The number of bytes actually written into the
+ * provided array, or -1 if the end of the stream has been
+ * reached.
+ *
+ * @throws IOException If a problem occurs while trying to read
+ * data from the stream.
+ */
+ @Override()
+ public int read(byte[] b)
+ throws IOException
+ {
+ return read(b, 0, b.length);
+ }
+
+
+
+ /**
+ * Reads some number of bytes from the input stream, blocking if
+ * necessary until there is data available, and adds them to the
+ * provided array starting at the specified position.
+ *
+ * @param b The array to which the data is to be written.
+ * @param off The offset in the array at which to start writing
+ * data.
+ * @param len The maximum number of bytes that may be added to the
+ * array.
+ *
+ * @return The number of bytes actually written into the
+ * provided array, or -1 if the end of the stream has been
+ * reached.
+ *
+ * @throws IOException If a problem occurs while trying to read
+ * data from the stream.
+ */
+ @Override()
+ public synchronized int read(byte[] b, int off, int len)
+ throws IOException
+ {
+ if (partialMessageBuffer != null)
+ {
+ int remaining = partialMessageBuffer.remaining();
+ if (remaining > 0)
+ {
+ if (remaining <= len)
+ {
+ // We can fit all the remaining data in the provided array,
+ // so that's all we'll try to put in it.
+ partialMessageBuffer.get(b, off, remaining);
+ partialMessageBuffer = null;
+ return remaining;
+ }
+ else
+ {
+ // The array is too small to hold the rest of the data, so
+ // only take as much as we can.
+ partialMessageBuffer.get(b, off, len);
+ return len;
+ }
+ }
+ else
+ {
+ partialMessageBuffer = null;
+ }
+ }
+
+ if (closed)
+ {
+ return -1;
+ }
+
+ try
+ {
+ LDAPMessage message = messageQueue.take();
+ if (message instanceof NullLDAPMessage)
+ {
+ messageQueue.clear();
+ closed = true;
+ return -1;
+ }
+
+ byte[] encodedMessage = message.encode().encode();
+ if (encodedMessage.length <= len)
+ {
+ // We can fit the entire message in the array.
+ System.arraycopy(encodedMessage, 0, b, off,
+ encodedMessage.length);
+ return encodedMessage.length;
+ }
+ else
+ {
+ // We can only fit part of the message in the array,
+ // so we need to save the rest for later.
+ System.arraycopy(encodedMessage, 0, b, off, len);
+ partialMessageBuffer = ByteBuffer.wrap(encodedMessage);
+ partialMessageBuffer.position(len);
+ return len;
+ }
+ }
+ catch (Exception e)
+ {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+
+
+ /**
+ * Repositions this stream to the position at the time that the
+ * {@code mark} method was called on this stream. This will not
+ * have any effect, as this input stream inplementation does not
+ * support marking.
+ */
+ @Override()
+ public void reset()
+ {
+ // No implementation is required.
+ }
+
+
+
+ /**
+ * Skips over and discards up to the specified number of bytes of
+ * data from this input stream. This implementation will always
+ * skip the requested number of bytes unless the end of the stream
+ * is reached.
+ *
+ * @param n The maximum number of bytes to skip.
+ *
+ * @return The number of bytes actually skipped.
+ *
+ * @throws IOException If a problem occurs while trying to read
+ * data from the input stream.
+ */
+ @Override()
+ public synchronized long skip(long n)
+ throws IOException
+ {
+ byte[] b;
+ if (n > 8192)
+ {
+ b = new byte[8192];
+ }
+ else
+ {
+ b = new byte[(int) n];
+ }
+
+ long totalBytesRead = 0L;
+ while (totalBytesRead < n)
+ {
+ int maxLen = (int) Math.min((n - totalBytesRead), b.length);
+
+ int bytesRead = read(b, 0, maxLen);
+ if (bytesRead < 0)
+ {
+ if (totalBytesRead > 0)
+ {
+ return totalBytesRead;
+ }
+ else
+ {
+ return bytesRead;
+ }
+ }
+ else
+ {
+ totalBytesRead += bytesRead;
+ }
+ }
+
+ return totalBytesRead;
+ }
+
+
+
+ /**
+ * Retrieves a string representation of this internal LDAP socket.
+ *
+ * @return A string representation of this internal LDAP socket.
+ */
+ @Override()
+ public String toString()
+ {
+ return "InternalLDAPInputStream";
+ }
+}
+
diff --git a/opends/src/server/org/opends/server/protocols/internal/InternalLDAPOutputStream.java b/opends/src/server/org/opends/server/protocols/internal/InternalLDAPOutputStream.java
new file mode 100644
index 0000000..9f2b8be
--- /dev/null
+++ b/opends/src/server/org/opends/server/protocols/internal/InternalLDAPOutputStream.java
@@ -0,0 +1,1029 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.protocols.internal;
+
+
+
+import java.io.OutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.opends.messages.Message;
+import org.opends.server.core.*;
+import org.opends.server.protocols.asn1.ASN1Element;
+import org.opends.server.protocols.ldap.*;
+import org.opends.server.types.AuthenticationType;
+import org.opends.server.types.Control;
+import org.opends.server.types.SearchResultEntry;
+import org.opends.server.types.SearchResultReference;
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.server.protocols.ldap.LDAPConstants.*;
+import static org.opends.server.util.ServerConstants.*;
+
+
+
+/**
+ * This class provides an implementation of a
+ * {@code java.io.OutputStream} that can be used to facilitate
+ * internal communication with the Directory Server. On the backend,
+ * data written to this output stream will be first decoded as an
+ * ASN.1 element and then as an LDAP message. That LDAP message will
+ * be converted to an internal operation which will then be processed
+ * and the result returned to the client via the input stream on the
+ * other side of the associated internal LDAP socket.
+ */
+@org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
+ mayInstantiate=false,
+ mayExtend=false,
+ mayInvoke=true)
+public final class InternalLDAPOutputStream
+ extends OutputStream
+ implements InternalSearchListener
+{
+ // Indicates whether this stream has been closed.
+ private boolean closed;
+
+ // Indicates whether the type of the ASN.1 element is needed.
+ private boolean needType;
+
+ // The BER type for the ASN.1 element being read.
+ private byte elementType;
+
+ // The data for the ASN.1 element being read.
+ private byte[] elementBytes;
+
+ // The length bytes for the ASN.1 element being read.
+ private byte[] lengthBytes;
+
+ // The offset in the appropriate array at which we should begin
+ // writing data. This could either refer to the length or data
+ // array, depending on the stage of the encoding process.
+ private int arrayOffset;
+
+ // The internal LDAP socket with which this output stream is
+ // associated.
+ private InternalLDAPSocket socket;
+
+
+
+ /**
+ * Creates a new instance of an internal LDAP output stream that is
+ * associated with the provided internal LDAP socket.
+ *
+ * @param socket The internal LDAP socket that will be serviced by
+ * this internal LDAP output stream.
+ */
+ public InternalLDAPOutputStream(InternalLDAPSocket socket)
+ {
+ this.socket = socket;
+
+ closed = false;
+
+ needType = true;
+ elementType = 0x00;
+ elementBytes = null;
+ lengthBytes = null;
+ arrayOffset = 0;
+ }
+
+
+
+ /**
+ * Closes this output stream, its associated socket, and the
+ * socket's associated input stream.
+ */
+ @Override()
+ public void close()
+ {
+ socket.close();
+ }
+
+
+
+ /**
+ * Closes this output stream through an internal mechanism that will
+ * not cause an infinite recursion loop by trying to also close the
+ * output stream.
+ */
+ @org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.PRIVATE,
+ mayInstantiate=false,
+ mayExtend=false,
+ mayInvoke=false)
+ void closeInternal()
+ {
+ closed = true;
+ }
+
+
+
+ /**
+ * Flushes this output stream and forces any buffered data to be
+ * written out. This will have no effect, since this output
+ * stream implementation does not use buffering.
+ */
+ @Override()
+ public void flush()
+ {
+ // No implementation is required.
+ }
+
+
+
+ /**
+ * Writes the contents of the provided byte array to this output
+ * stream.
+ *
+ * @param b The byte array to be written.
+ *
+ * @throws IOException If the output stream is closed, or if there
+ * is a problem with the data being written.
+ */
+ @Override()
+ public void write(byte[] b)
+ throws IOException
+ {
+ write(b, 0, b.length);
+ }
+
+
+
+ /**
+ * Writes the specified portion of the data in the provided byte
+ * array to this output stream. Any data written will be
+ * accumulated until a complete ASN.1 element is available, at which
+ * point it will be decoded as an LDAP message and converted to an
+ * internal operation that will be processed.
+ *
+ * @param b The byte array containing the data to be read.
+ * @param off The position in the array at which to start reading
+ * data.
+ * @param len The number of bytes to read from the array.
+ *
+ * @throws IOException If the output stream is closed, or if there
+ * is a problem with the data being written.
+ */
+ @Override()
+ public synchronized void write(byte[] b, int off, int len)
+ throws IOException
+ {
+ if (closed)
+ {
+ Message m = ERR_INTERNALOS_CLOSED.get();
+ throw new IOException(m.toString());
+ }
+
+ if (len == 0)
+ {
+ return;
+ }
+
+
+ // See if we need to read the BER type.
+ int position = off;
+ int remaining = len;
+ if (needType)
+ {
+ elementType = b[position++];
+ needType = false;
+
+ if (--remaining <= 0)
+ {
+ return;
+ }
+ }
+
+
+ // See if we need to read the first length byte.
+ if ((lengthBytes == null) && (elementBytes == null))
+ {
+ int length = b[position++];
+ if (length == (length & 0x7F))
+ {
+ // It's a single-byte length, so we can create the value
+ // array.
+ elementBytes = new byte[length];
+ }
+ else
+ {
+ // It's a multi-byte length, so we can create the length
+ // array.
+ lengthBytes = new byte[length & 0x7F];
+ }
+
+ arrayOffset = 0;
+ if (--remaining <= 0)
+ {
+ return;
+ }
+ }
+
+
+ // See if we need to continue reading part of a multi-byte length.
+ if (lengthBytes != null)
+ {
+ // See if we have enough to read the full length. If so, then
+ // do it. Otherwise, read what we can and return.
+ int needed = lengthBytes.length - arrayOffset;
+ if (remaining >= needed)
+ {
+ System.arraycopy(b, position, lengthBytes, arrayOffset,
+ needed);
+ position += needed;
+ remaining -= needed;
+
+ int length = 0;
+ for (byte lb : lengthBytes)
+ {
+ length <<= 8;
+ length |= (lb & 0xFF);
+ }
+
+ elementBytes = new byte[length];
+ lengthBytes = null;
+ arrayOffset = 0;
+ if (remaining <= 0)
+ {
+ return;
+ }
+ }
+ else
+ {
+ System.arraycopy(b, position, lengthBytes, arrayOffset,
+ remaining);
+ arrayOffset += remaining;
+ return;
+ }
+ }
+
+
+ // See if we need to read data for the element value.
+ if (elementBytes != null)
+ {
+ // See if we have enough to read the full value. If so, then
+ // do it, create the element, and process it. Otherwise, read
+ // what we can and return.
+ int needed = elementBytes.length - arrayOffset;
+ if (remaining >= needed)
+ {
+ System.arraycopy(b, position, elementBytes, arrayOffset,
+ needed);
+ position += needed;
+ remaining -= needed;
+ processElement(new ASN1Element(elementType, elementBytes));
+
+ needType = true;
+ arrayOffset = 0;
+ lengthBytes = null;
+ elementBytes = null;
+ }
+ else
+ {
+ System.arraycopy(b, position, lengthBytes, arrayOffset,
+ remaining);
+ arrayOffset += remaining;
+ return;
+ }
+ }
+
+
+ // If there is still more data available, then call this method
+ // again to process it.
+ if (remaining > 0)
+ {
+ write(b, position, remaining);
+ }
+ }
+
+
+
+ /**
+ * Writes a single byte of data to this output stream. If the byte
+ * written completes an ASN.1 element that was in progress, then it
+ * will be decoded as an LDAP message and converted to an internal
+ * operation that will be processed. Otherwise, the data will be
+ * accumulated until a complete element can be formed.
+ *
+ * @param b The byte to be written.
+ *
+ * @throws IOException If the output stream is closed, or if there
+ * is a problem with the data being written.
+ */
+ @Override()
+ public synchronized void write(int b)
+ throws IOException
+ {
+ if (closed)
+ {
+ Message m = ERR_INTERNALOS_CLOSED.get();
+ throw new IOException(m.toString());
+ }
+
+ if (needType)
+ {
+ elementType = (byte) (b & 0xFF);
+ needType = false;
+ return;
+ }
+ else if (elementBytes != null)
+ {
+ // The byte should be part of the element value.
+ elementBytes[arrayOffset++] = (byte) (b & 0xFF);
+ if (arrayOffset == elementBytes.length)
+ {
+ // The element has been completed, so process it.
+ processElement(new ASN1Element(elementType, elementBytes));
+ }
+
+ lengthBytes = null;
+ elementBytes = null;
+ arrayOffset = 0;
+ needType = true;
+
+ return;
+ }
+ else if (lengthBytes != null)
+ {
+ // The byte should be part of a multi-byte length.
+ lengthBytes[arrayOffset++] = (byte) (b & 0xFF);
+ if (arrayOffset == lengthBytes.length)
+ {
+ int length = 0;
+ for (int i=0; i < lengthBytes.length; i++)
+ {
+ length <<= 8;
+ length |= (lengthBytes[i] & 0xFF);
+ }
+
+ elementBytes = new byte[length];
+ lengthBytes = null;
+ arrayOffset = 0;
+ }
+
+ return;
+ }
+ else
+ {
+ if ((b & 0x7F) == b)
+ {
+ // It's the complete length.
+ elementBytes = new byte[b];
+ lengthBytes = null;
+ arrayOffset = 0;
+ }
+ else
+ {
+ lengthBytes = new byte[b & 0x7F];
+ elementBytes = null;
+ arrayOffset = 0;
+ }
+
+ return;
+ }
+ }
+
+
+
+ /**
+ * Processes the provided ASN.1 element by decoding it as an LDAP
+ * message, converting that to an internal operation, and sending
+ * the appropriate response message(s) to the client through the
+ * corresponding internal LDAP input stream.
+ *
+ * @param element The ASN.1 element to be processed.
+ *
+ * @throws IOException If a problem occurs while attempting to
+ * decode the provided ASN.1 element as an
+ * LDAP message.
+ */
+ private void processElement(ASN1Element element)
+ throws IOException
+ {
+ LDAPMessage message;
+ try
+ {
+ message = LDAPMessage.decode(element.decodeAsSequence());
+ }
+ catch (Exception e)
+ {
+ throw new IOException(e.getMessage(), e);
+ }
+
+ switch (message.getProtocolOpType())
+ {
+ case OP_TYPE_ABANDON_REQUEST:
+ // No action is required.
+ return;
+
+ case OP_TYPE_ADD_REQUEST:
+ processAddOperation(message);
+ break;
+
+ case OP_TYPE_BIND_REQUEST:
+ processBindOperation(message);
+ break;
+
+ case OP_TYPE_COMPARE_REQUEST:
+ processCompareOperation(message);
+ break;
+
+
+ case OP_TYPE_DELETE_REQUEST:
+ processDeleteOperation(message);
+ break;
+
+
+ case OP_TYPE_EXTENDED_REQUEST:
+ processExtendedOperation(message);
+ break;
+
+
+ case OP_TYPE_MODIFY_REQUEST:
+ processModifyOperation(message);
+ break;
+
+
+ case OP_TYPE_MODIFY_DN_REQUEST:
+ processModifyDNOperation(message);
+ break;
+
+
+ case OP_TYPE_SEARCH_REQUEST:
+ processSearchOperation(message);
+ break;
+
+
+ case OP_TYPE_UNBIND_REQUEST:
+ socket.close();
+ break;
+
+
+ default:
+ Message m = ERR_INTERNALOS_INVALID_REQUEST.get(
+ message.getProtocolElementName());
+ throw new IOException(m.toString());
+ }
+ }
+
+
+
+ /**
+ * Processes the content of the provided LDAP message as an add
+ * operation and returns the appropriate result to the client.
+ *
+ * @param message The LDAP message containing the request to
+ * process.
+ *
+ * @throws IOException If a problem occurs while attempting to
+ * process the operation.
+ */
+ private void processAddOperation(LDAPMessage message)
+ throws IOException
+ {
+ int messageID = message.getMessageID();
+ AddRequestProtocolOp request = message.getAddRequestProtocolOp();
+
+ ArrayList<Control> requestControls = new ArrayList<Control>();
+ if (message.getControls() != null)
+ {
+ for (LDAPControl c : message.getControls())
+ {
+ requestControls.add(c.getControl());
+ }
+ }
+
+ InternalClientConnection conn = socket.getConnection();
+ AddOperationBasis op =
+ new AddOperationBasis(conn, conn.nextOperationID(),
+ messageID, requestControls,
+ request.getDN(),
+ request.getAttributes());
+ op.run();
+
+ AddResponseProtocolOp addResponse =
+ new AddResponseProtocolOp(op.getResultCode().getIntValue(),
+ op.getErrorMessage().toMessage(),
+ op.getMatchedDN(),
+ op.getReferralURLs());
+ ArrayList<LDAPControl> responseControls =
+ new ArrayList<LDAPControl>();
+ for (Control c : op.getResponseControls())
+ {
+ responseControls.add(new LDAPControl(c));
+ }
+
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(messageID, addResponse, responseControls));
+ }
+
+
+
+ /**
+ * Processes the content of the provided LDAP message as a bind
+ * operation and returns the appropriate result to the client.
+ *
+ * @param message The LDAP message containing the request to
+ * process.
+ *
+ * @throws IOException If a problem occurs while attempting to
+ * process the operation.
+ */
+ private void processBindOperation(LDAPMessage message)
+ throws IOException
+ {
+ int messageID = message.getMessageID();
+ BindRequestProtocolOp request =
+ message.getBindRequestProtocolOp();
+
+ if (request.getAuthenticationType() == AuthenticationType.SASL)
+ {
+ Message m = ERR_INTERNALOS_SASL_BIND_NOT_SUPPORTED.get();
+ BindResponseProtocolOp bindResponse =
+ new BindResponseProtocolOp(
+ LDAPResultCode.UNWILLING_TO_PERFORM, m);
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(messageID, bindResponse));
+ return;
+ }
+
+ ArrayList<Control> requestControls = new ArrayList<Control>();
+ if (message.getControls() != null)
+ {
+ for (LDAPControl c : message.getControls())
+ {
+ requestControls.add(c.getControl());
+ }
+ }
+
+ InternalClientConnection conn = socket.getConnection();
+ BindOperationBasis op =
+ new BindOperationBasis(conn, conn.nextOperationID(),
+ messageID, requestControls,
+ String.valueOf(request.getProtocolVersion()),
+ request.getDN(), request.getSimplePassword());
+ op.run();
+
+ BindResponseProtocolOp bindResponse =
+ new BindResponseProtocolOp(op.getResultCode().getIntValue(),
+ op.getErrorMessage().toMessage(),
+ op.getMatchedDN(),
+ op.getReferralURLs());
+ ArrayList<LDAPControl> responseControls =
+ new ArrayList<LDAPControl>();
+ for (Control c : op.getResponseControls())
+ {
+ responseControls.add(new LDAPControl(c));
+ }
+
+ if (bindResponse.getResultCode() == LDAPResultCode.SUCCESS)
+ {
+ socket.setConnection(new InternalClientConnection(
+ op.getAuthenticationInfo()));
+ }
+
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(messageID, bindResponse, responseControls));
+ }
+
+
+
+ /**
+ * Processes the content of the provided LDAP message as a compare
+ * operation and returns the appropriate result to the client.
+ *
+ * @param message The LDAP message containing the request to
+ * process.
+ *
+ * @throws IOException If a problem occurs while attempting to
+ * process the operation.
+ */
+ private void processCompareOperation(LDAPMessage message)
+ throws IOException
+ {
+ int messageID = message.getMessageID();
+ CompareRequestProtocolOp request =
+ message.getCompareRequestProtocolOp();
+
+ ArrayList<Control> requestControls = new ArrayList<Control>();
+ if (message.getControls() != null)
+ {
+ for (LDAPControl c : message.getControls())
+ {
+ requestControls.add(c.getControl());
+ }
+ }
+
+ InternalClientConnection conn = socket.getConnection();
+ CompareOperationBasis op =
+ new CompareOperationBasis(conn, conn.nextOperationID(),
+ messageID, requestControls, request.getDN(),
+ request.getAttributeType(),
+ request.getAssertionValue());
+ op.run();
+
+ CompareResponseProtocolOp compareResponse =
+ new CompareResponseProtocolOp(
+ op.getResultCode().getIntValue(),
+ op.getErrorMessage().toMessage(),
+ op.getMatchedDN(),
+ op.getReferralURLs());
+ ArrayList<LDAPControl> responseControls =
+ new ArrayList<LDAPControl>();
+ for (Control c : op.getResponseControls())
+ {
+ responseControls.add(new LDAPControl(c));
+ }
+
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(messageID, compareResponse,
+ responseControls));
+ }
+
+
+
+ /**
+ * Processes the content of the provided LDAP message as a delete
+ * operation and returns the appropriate result to the client.
+ *
+ * @param message The LDAP message containing the request to
+ * process.
+ *
+ * @throws IOException If a problem occurs while attempting to
+ * process the operation.
+ */
+ private void processDeleteOperation(LDAPMessage message)
+ throws IOException
+ {
+ int messageID = message.getMessageID();
+ DeleteRequestProtocolOp request =
+ message.getDeleteRequestProtocolOp();
+
+ ArrayList<Control> requestControls = new ArrayList<Control>();
+ if (message.getControls() != null)
+ {
+ for (LDAPControl c : message.getControls())
+ {
+ requestControls.add(c.getControl());
+ }
+ }
+
+ InternalClientConnection conn = socket.getConnection();
+ DeleteOperationBasis op =
+ new DeleteOperationBasis(conn, conn.nextOperationID(),
+ messageID, requestControls, request.getDN());
+ op.run();
+
+ DeleteResponseProtocolOp deleteResponse =
+ new DeleteResponseProtocolOp(
+ op.getResultCode().getIntValue(),
+ op.getErrorMessage().toMessage(),
+ op.getMatchedDN(),
+ op.getReferralURLs());
+ ArrayList<LDAPControl> responseControls =
+ new ArrayList<LDAPControl>();
+ for (Control c : op.getResponseControls())
+ {
+ responseControls.add(new LDAPControl(c));
+ }
+
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(messageID, deleteResponse,
+ responseControls));
+ }
+
+
+
+ /**
+ * Processes the content of the provided LDAP message as an extended
+ * operation and returns the appropriate result to the client.
+ *
+ * @param message The LDAP message containing the request to
+ * process.
+ *
+ * @throws IOException If a problem occurs while attempting to
+ * process the operation.
+ */
+ private void processExtendedOperation(LDAPMessage message)
+ throws IOException
+ {
+ int messageID = message.getMessageID();
+ ExtendedRequestProtocolOp request =
+ message.getExtendedRequestProtocolOp();
+ if (request.getOID().equals(OID_START_TLS_REQUEST))
+ {
+ Message m = ERR_INTERNALOS_STARTTLS_NOT_SUPPORTED.get();
+ ExtendedResponseProtocolOp extendedResponse =
+ new ExtendedResponseProtocolOp(
+ LDAPResultCode.UNWILLING_TO_PERFORM, m);
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(messageID, extendedResponse));
+ return;
+ }
+
+ ArrayList<Control> requestControls = new ArrayList<Control>();
+ if (message.getControls() != null)
+ {
+ for (LDAPControl c : message.getControls())
+ {
+ requestControls.add(c.getControl());
+ }
+ }
+
+ InternalClientConnection conn = socket.getConnection();
+ ExtendedOperationBasis op =
+ new ExtendedOperationBasis(conn, conn.nextOperationID(),
+ messageID, requestControls, request.getOID(),
+ request.getValue());
+ op.run();
+
+ ExtendedResponseProtocolOp extendedResponse =
+ new ExtendedResponseProtocolOp(
+ op.getResultCode().getIntValue(),
+ op.getErrorMessage().toMessage(),
+ op.getMatchedDN(),
+ op.getReferralURLs(), op.getResponseOID(),
+ op.getResponseValue());
+ ArrayList<LDAPControl> responseControls =
+ new ArrayList<LDAPControl>();
+ for (Control c : op.getResponseControls())
+ {
+ responseControls.add(new LDAPControl(c));
+ }
+
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(messageID, extendedResponse,
+ responseControls));
+ }
+
+
+
+ /**
+ * Processes the content of the provided LDAP message as a modify
+ * operation and returns the appropriate result to the client.
+ *
+ * @param message The LDAP message containing the request to
+ * process.
+ *
+ * @throws IOException If a problem occurs while attempting to
+ * process the operation.
+ */
+ private void processModifyOperation(LDAPMessage message)
+ throws IOException
+ {
+ int messageID = message.getMessageID();
+ ModifyRequestProtocolOp request =
+ message.getModifyRequestProtocolOp();
+
+ ArrayList<Control> requestControls = new ArrayList<Control>();
+ if (message.getControls() != null)
+ {
+ for (LDAPControl c : message.getControls())
+ {
+ requestControls.add(c.getControl());
+ }
+ }
+
+ InternalClientConnection conn = socket.getConnection();
+ ModifyOperationBasis op =
+ new ModifyOperationBasis(conn, conn.nextOperationID(),
+ messageID, requestControls, request.getDN(),
+ request.getModifications());
+ op.run();
+
+ ModifyResponseProtocolOp modifyResponse =
+ new ModifyResponseProtocolOp(
+ op.getResultCode().getIntValue(),
+ op.getErrorMessage().toMessage(),
+ op.getMatchedDN(),
+ op.getReferralURLs());
+ ArrayList<LDAPControl> responseControls =
+ new ArrayList<LDAPControl>();
+ for (Control c : op.getResponseControls())
+ {
+ responseControls.add(new LDAPControl(c));
+ }
+
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(messageID, modifyResponse,
+ responseControls));
+ }
+
+
+
+ /**
+ * Processes the content of the provided LDAP message as a modify DN
+ * operation and returns the appropriate result to the client.
+ *
+ * @param message The LDAP message containing the request to
+ * process.
+ *
+ * @throws IOException If a problem occurs while attempting to
+ * process the operation.
+ */
+ private void processModifyDNOperation(LDAPMessage message)
+ throws IOException
+ {
+ int messageID = message.getMessageID();
+ ModifyDNRequestProtocolOp request =
+ message.getModifyDNRequestProtocolOp();
+
+ ArrayList<Control> requestControls = new ArrayList<Control>();
+ if (message.getControls() != null)
+ {
+ for (LDAPControl c : message.getControls())
+ {
+ requestControls.add(c.getControl());
+ }
+ }
+
+ InternalClientConnection conn = socket.getConnection();
+ ModifyDNOperationBasis op =
+ new ModifyDNOperationBasis(conn, conn.nextOperationID(),
+ messageID, requestControls, request.getEntryDN(),
+ request.getNewRDN(), request.deleteOldRDN(),
+ request.getNewSuperior());
+ op.run();
+
+ ModifyDNResponseProtocolOp modifyDNResponse =
+ new ModifyDNResponseProtocolOp(
+ op.getResultCode().getIntValue(),
+ op.getErrorMessage().toMessage(),
+ op.getMatchedDN(),
+ op.getReferralURLs());
+ ArrayList<LDAPControl> responseControls =
+ new ArrayList<LDAPControl>();
+ for (Control c : op.getResponseControls())
+ {
+ responseControls.add(new LDAPControl(c));
+ }
+
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(messageID, modifyDNResponse,
+ responseControls));
+ }
+
+
+
+ /**
+ * Processes the content of the provided LDAP message as a search
+ * operation and returns the appropriate result to the client.
+ *
+ * @param message The LDAP message containing the request to
+ * process.
+ *
+ * @throws IOException If a problem occurs while attempting to
+ * process the operation.
+ */
+ private void processSearchOperation(LDAPMessage message)
+ throws IOException
+ {
+ int messageID = message.getMessageID();
+ SearchRequestProtocolOp request =
+ message.getSearchRequestProtocolOp();
+
+ ArrayList<Control> requestControls = new ArrayList<Control>();
+ if (message.getControls() != null)
+ {
+ for (LDAPControl c : message.getControls())
+ {
+ requestControls.add(c.getControl());
+ }
+ }
+
+ InternalClientConnection conn = socket.getConnection();
+ InternalSearchOperation op =
+ new InternalSearchOperation(conn, conn.nextOperationID(),
+ messageID, requestControls, request.getBaseDN(),
+ request.getScope(), request.getDereferencePolicy(),
+ request.getSizeLimit(), request.getTimeLimit(),
+ request.getTypesOnly(), request.getFilter(),
+ request.getAttributes(), this);
+ op.run();
+
+ SearchResultDoneProtocolOp searchDone =
+ new SearchResultDoneProtocolOp(
+ op.getResultCode().getIntValue(),
+ op.getErrorMessage().toMessage(),
+ op.getMatchedDN(),
+ op.getReferralURLs());
+ ArrayList<LDAPControl> responseControls =
+ new ArrayList<LDAPControl>();
+ for (Control c : op.getResponseControls())
+ {
+ responseControls.add(new LDAPControl(c));
+ }
+
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(messageID, searchDone, responseControls));
+ }
+
+
+
+ /**
+ * Performs any processing necessary for the provided search result
+ * entry.
+ *
+ * @param searchOperation The internal search operation being
+ * processed.
+ * @param searchEntry The matching search result entry to be
+ * processed.
+ */
+ @org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.PRIVATE,
+ mayInstantiate=false,
+ mayExtend=false,
+ mayInvoke=false)
+ public void handleInternalSearchEntry(
+ InternalSearchOperation searchOperation,
+ SearchResultEntry searchEntry)
+ {
+ ArrayList<LDAPControl> entryControls =
+ new ArrayList<LDAPControl>();
+ for (Control c : searchEntry.getControls())
+ {
+ entryControls.add(new LDAPControl(c));
+ }
+
+ SearchResultEntryProtocolOp entry =
+ new SearchResultEntryProtocolOp(searchEntry);
+
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(searchOperation.getMessageID(), entry,
+ entryControls));
+ }
+
+
+
+ /**
+ * Performs any processing necessary for the provided search result
+ * reference.
+ *
+ * @param searchOperation The internal search operation being
+ * processed.
+ * @param searchReference The search result reference to be
+ * processed.
+ */
+ @org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.PRIVATE,
+ mayInstantiate=false,
+ mayExtend=false,
+ mayInvoke=false)
+ public void handleInternalSearchReference(
+ InternalSearchOperation searchOperation,
+ SearchResultReference searchReference)
+ {
+ ArrayList<LDAPControl> entryControls =
+ new ArrayList<LDAPControl>();
+ for (Control c : searchReference.getControls())
+ {
+ entryControls.add(new LDAPControl(c));
+ }
+
+ SearchResultReferenceProtocolOp reference =
+ new SearchResultReferenceProtocolOp(searchReference);
+
+ socket.getInputStream().addLDAPMessage(
+ new LDAPMessage(searchOperation.getMessageID(), reference,
+ entryControls));
+ }
+
+
+
+ /**
+ * Retrieves a string representation of this internal LDAP socket.
+ *
+ * @return A string representation of this internal LDAP socket.
+ */
+ @Override()
+ public String toString()
+ {
+ return "InternalLDAPOutputStream";
+ }
+}
+
diff --git a/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocket.java b/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocket.java
new file mode 100644
index 0000000..0ecb49d
--- /dev/null
+++ b/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocket.java
@@ -0,0 +1,860 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.protocols.internal;
+
+
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.nio.channels.SocketChannel;
+
+import org.opends.server.types.DN;
+
+
+
+/**
+ * This class provides an implementation of a {@code java.net.Socket}
+ * object that can be used to facilitate internal communication with
+ * the Directory Server through third-party LDAP APIs that provide the
+ * ability to use a custom socket factory when creating connections.
+ * Whenever data is written over the socket, it is decoded as LDAP
+ * communication and converted to an appropriate internal operation,
+ * which the server then processes and converts the response back to
+ * an LDAP encoding.
+ * <BR><BR>
+ * Note that this implementation only supports those operations which
+ * can be performed in the Directory Server via internal operations.
+ * This includes add, compare, delete, modify, modify DN, and search
+ * operations, and some types of extended operations. Special support
+ * has been added for simple bind operations to function properly, but
+ * SASL binds are not supported. Abandon and unbind operations are
+ * not supported, nor are the cancel or StartTLS extended operations.
+ * Only clear-text LDAP communication may be used.
+ */
+@org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
+ mayInstantiate=true,
+ mayExtend=false,
+ mayInvoke=true)
+public final class InternalLDAPSocket
+ extends Socket
+{
+ // Indicates whether this socket is closed.
+ private boolean closed;
+
+ // The value that the client has requested for SO_KEEPALIVE.
+ private boolean keepAlive;
+
+ // The value that the client has requested for OOBINLINE.
+ private boolean oobInline;
+
+ // The value that the client has requested for SO_REUSEADDR.
+ private boolean reuseAddress;
+
+ // The value that the client has requested for TCP_NODELAY.
+ private boolean tcpNoDelay;
+
+ // The value that the client has requested for SO_LINGER.
+ private int lingerDuration;
+
+ // The value that the client has requested for SO_RCVBUF.
+ private int receiveBufferSize;
+
+ // The value that the client has requested for SO_SNDBUF.
+ private int sendBufferSize;
+
+ // The value that the client has requested for SO_TIMEOUT.
+ private int timeout;
+
+ // The value that the client has requested for the traffic class.
+ private int trafficClass;
+
+ // The internal client connection used to perform the internal
+ // operations. It will be null until it is first used.
+ private InternalClientConnection conn;
+
+ // The input stream associated with this internal LDAP socket.
+ private InternalLDAPInputStream inputStream;
+
+ // The output stream associated with this internal LDAP socket.
+ private InternalLDAPOutputStream outputStream;
+
+
+
+ /**
+ * Creates a new internal LDAP socket.
+ */
+ public InternalLDAPSocket()
+ {
+ closed = false;
+ keepAlive = true;
+ oobInline = true;
+ reuseAddress = true;
+ tcpNoDelay = true;
+ lingerDuration = 0;
+ receiveBufferSize = 1024;
+ sendBufferSize = 1024;
+ timeout = 0;
+ trafficClass = 0;
+ conn = null;
+ inputStream = new InternalLDAPInputStream(this);
+ outputStream = new InternalLDAPOutputStream(this);
+ }
+
+
+
+ /**
+ * Retrieves the internal client connection used to back this
+ * internal LDAP socket.
+ *
+ * @return The internal client connection used to back this
+ * internal LDAP socket.
+ *
+ * @throws IOException If there is a problem obtaining the
+ * connection.
+ */
+ @org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.PRIVATE,
+ mayInstantiate=false,
+ mayExtend=false,
+ mayInvoke=false)
+ synchronized InternalClientConnection getConnection()
+ throws IOException
+ {
+ if (conn == null)
+ {
+ try
+ {
+ conn = new InternalClientConnection(DN.nullDN());
+ }
+ catch (Exception e)
+ {
+ // This should never happen.
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ return conn;
+ }
+
+
+
+ /**
+ * Sets the internal client connection used to back this internal
+ * LDAP socket.
+ *
+ * @param conn The internal client connection used to back this
+ * internal LDAP socket.
+ */
+ @org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.PRIVATE,
+ mayInstantiate=false,
+ mayExtend=false,
+ mayInvoke=false)
+ synchronized void setConnection(InternalClientConnection conn)
+ {
+ this.conn = conn;
+ }
+
+
+
+ /**
+ * Binds the socket to a local address. This does nothing, since
+ * there is no actual network communication performed by this
+ * socket implementation.
+ *
+ * @param bindpoint The socket address to which to bind.
+ */
+ @Override()
+ public void bind(SocketAddress bindpoint)
+ {
+ // No implementation is required.
+ }
+
+
+
+ /**
+ * Closes this socket. This will make it unavailable for use.
+ */
+ @Override()
+ public synchronized void close()
+ {
+ try
+ {
+ inputStream.closeInternal();
+ } catch (Exception e) {}
+
+ try
+ {
+ outputStream.closeInternal();
+ } catch (Exception e) {}
+
+ closed = true;
+ inputStream = null;
+ outputStream = null;
+ }
+
+
+
+ /**
+ * Connects this socket to the specified remote endpoint. This will
+ * make the connection available again if it has been previously
+ * closed. The provided address is irrelevant, as it will always be
+ * an internal connection.
+ *
+ * @param endpoint The address of the remote endpoint.
+ */
+ @Override()
+ public synchronized void connect(SocketAddress endpoint)
+ {
+ closed = false;
+ inputStream = new InternalLDAPInputStream(this);
+ outputStream = new InternalLDAPOutputStream(this);
+ }
+
+
+
+ /**
+ * Connects this socket to the specified remote endpoint. This does
+ * nothing, since there is no actual network communication performed
+ * by this socket implementation.
+ *
+ * @param endpoint The address of the remote endpoint.
+ * @param timeout The maximum length of time in milliseconds to
+ * wait for the connection to be established.
+ */
+ @Override()
+ public void connect(SocketAddress endpoint, int timeout)
+ {
+ closed = false;
+ inputStream = new InternalLDAPInputStream(this);
+ outputStream = new InternalLDAPOutputStream(this);
+ }
+
+
+
+ /**
+ * Retrieves the socket channel associated with this socket. This
+ * method always returns {@code null} since this implementation does
+ * not support use with NIO channels.
+ *
+ * @return {@code null} because this implementation does not
+ * support use with NIO channels.
+ */
+ @Override()
+ public SocketChannel getChannel()
+ {
+ // This implementation does not support use with NIO channels.
+ return null;
+ }
+
+
+
+ /**
+ * Retrieves the address to which this socket is connected. The
+ * address returned is meaningless, since there is no actual network
+ * communication performed by this socket implementation.
+ *
+ * @return The address to which this socket is connected.
+ */
+ @Override()
+ public InetAddress getInetAddress()
+ {
+ try
+ {
+ return InetAddress.getLocalHost();
+ }
+ catch (Exception e)
+ {
+ // This should not happen.
+ return null;
+ }
+ }
+
+
+
+ /**
+ * Retrieves the input stream for this socket.
+ *
+ * @return The input stream for this socket.
+ */
+ @Override()
+ public InternalLDAPInputStream getInputStream()
+ {
+ return inputStream;
+ }
+
+
+
+ /**
+ * Indicates whether SO_KEEPALIVE is enabled. This implementation
+ * will return {@code true} by default, but if its value is changed
+ * using {@code setKeepalive} then that value will be returned.
+ * This setting has no effect in this socket implementation.
+ *
+ * @return {@code true} if SO_KEEPALIVE is enabled, or
+ * {@code false} if not.
+ */
+ @Override()
+ public boolean getKeepAlive()
+ {
+ return keepAlive;
+ }
+
+
+
+ /**
+ * Retrieves the local address to which this socket is bound. The
+ * address returned is meaningless, since there is no actual network
+ * communication performed by this socket implementation.
+ *
+ * @return The local address to which this socket is bound.
+ */
+ @Override()
+ public InetAddress getLocalAddress()
+ {
+ try
+ {
+ return InetAddress.getLocalHost();
+ }
+ catch (Exception e)
+ {
+ // This should not happen.
+ return null;
+ }
+ }
+
+
+
+ /**
+ * Retrieves the local port to which this socket is bound. The
+ * value returned is meaningless, since there is no actual network
+ * communication performed by this socket implementation.
+ *
+ * @return The local port to which this socket is bound.
+ */
+ @Override()
+ public int getLocalPort()
+ {
+ return 389;
+ }
+
+
+
+ /**
+ * Retrieves the local socket address to which this socket is bound.
+ * The value returned is meaningless, since there is no actual
+ * network communication performed by this socket implementation.
+ *
+ * @return The local socket address to which this socket is bound.
+ */
+ @Override()
+ public SocketAddress getLocalSocketAddress()
+ {
+ try
+ {
+ return new InetSocketAddress(getLocalAddress(), getLocalPort());
+ }
+ catch (Exception e)
+ {
+ // This should not happen.
+ return null;
+ }
+ }
+
+
+
+ /**
+ * Indicates whether OOBINLINE is enabled. This implementation will
+ * return {@code true} by default, but if its value is changed
+ * using {@code setOOBInline} then that value will be returned.
+ * This setting has no effect in this socket implementation.
+ *
+ * @return {@code true} if OOBINLINE is enabled, or {@code false}
+ * if it is not.
+ */
+ @Override()
+ public boolean getOOBInline()
+ {
+ return oobInline;
+ }
+
+
+
+ /**
+ * Retrieves the output stream for this socket.
+ *
+ * @return The output stream for this socket.
+ */
+ @Override()
+ public InternalLDAPOutputStream getOutputStream()
+ {
+ return outputStream;
+ }
+
+
+
+ /**
+ * Retrieves the remote port to which this socket is connected. The
+ * value returned is meaningless, since there is no actual network
+ * communication performed by this socket implementation.
+ *
+ * @return The remote port to which this socket is connected.
+ */
+ @Override()
+ public int getPort()
+ {
+ return 389;
+ }
+
+
+
+ /**
+ * Retrieves the value of the SO_RCVBUF option for this socket. The
+ * value returned is meaningless, since there is no actual network
+ * communication performed by this socket implementation.
+ *
+ * @return The value of the SO_RCVBUF option for this socket.
+ */
+ @Override()
+ public int getReceiveBufferSize()
+ {
+ return receiveBufferSize;
+ }
+
+
+
+ /**
+ * Retrieves the remote socket address to which this socket is
+ * connected. The value returned is meaningless, since there is no
+ * actual network communication performed by this socket
+ * implementation.
+ *
+ * @return The remote socket address to which this socket is
+ * connected.
+ */
+ @Override()
+ public SocketAddress getRemoteSocketAddress()
+ {
+ try
+ {
+ return new InetSocketAddress(getInetAddress(), getPort());
+ }
+ catch (Exception e)
+ {
+ // This should not happen.
+ return null;
+ }
+ }
+
+
+
+ /**
+ * Indicates whether SO_REUSEADDR is enabled. This implementation
+ * will return {@code true} by default, but if its value is changed
+ * using {@code setReuseAddress} then that value will be returned.
+ * This setting has no effect in this socket implementation.
+ *
+ * @return {@code true} if SO_REUSEADDR is enabled, or
+ * {@code false} if it is not.
+ */
+ @Override()
+ public boolean getReuseAddress()
+ {
+ return reuseAddress;
+ }
+
+
+
+ /**
+ * Retrieves the value of the SO_SNDBUF option for this socket. The
+ * value returned is meaningless, since there is no actual network
+ * communication performed by this socket implementation.
+ *
+ * @return The value of the SO_SNDBUF option for this socket.
+ */
+ @Override()
+ public int getSendBufferSize()
+ {
+ return sendBufferSize;
+ }
+
+
+
+ /**
+ * Retrieves the value of the SO_LINGER option for this socket. The
+ * value returned is meaningless, since there is no actual network
+ * communication performed by this socket implementation.
+ *
+ * @return The value of the SO_LINGER option for this socket.
+ */
+ @Override()
+ public int getSoLinger()
+ {
+ return lingerDuration;
+ }
+
+
+
+ /**
+ * Retrieves the value of the SO_TIMEOUT option for this socket.
+ * The value returned is meaningless, since there is no actual
+ * network communication performed by this socket implementation.
+ *
+ * @return The value of the SO_TIMEOUT option for this socket.
+ */
+ @Override()
+ public int getSoTimeout()
+ {
+ return timeout;
+ }
+
+
+
+ /**
+ * Indicates whether TCP_NODELAY is enabled. This implementation
+ * will return {@code true} by default, but if its value is changed
+ * using {@code setTcpNoDelay} then that value will be returned.
+ * This setting has no effect in this socket implementation.
+ *
+ * @return {@code true} if TCP_NODELAY is enabled, or {@code false}
+ * if it is not.
+ */
+ @Override()
+ public boolean getTcpNoDelay()
+ {
+ return tcpNoDelay;
+ }
+
+
+
+ /**
+ * Retrieves the traffic class for this socket. The value returned
+ * will be meaningless, since there is no actual network
+ * communication performed by this socket.
+ *
+ * @return The traffic class for this socket.
+ */
+ @Override()
+ public int getTrafficClass()
+ {
+ return trafficClass;
+ }
+
+
+
+ /**
+ * Indicates whether this socket is bound to a local address. This
+ * method will always return {@code true} to indicate that it is
+ * bound.
+ *
+ * @return {@code true} to indicate that the socket is bound to a
+ * local address.
+ */
+ @Override()
+ public boolean isBound()
+ {
+ return true;
+ }
+
+
+
+ /**
+ * Indicates whether this socket is closed. This method will always
+ * return {@code false} to indicate that it is not closed.
+ *
+ * @return {@code false} to indicate that the socket is not closed.
+ */
+ @Override()
+ public boolean isClosed()
+ {
+ return closed;
+ }
+
+
+
+ /**
+ * Indicates whether this socket is connected to both local and
+ * remote endpoints. This method will always return {@code true} to
+ * indicate that it is connected.
+ *
+ * @return {@code true} to indicate that the socket is connected.
+ */
+ @Override()
+ public boolean isConnected()
+ {
+ return (! closed);
+ }
+
+
+
+ /**
+ * Indicates whether the input side of this socket has been closed.
+ * This method will always return {@code false} to indicate that it
+ * is not closed.
+ *
+ * @return {@code false} to indicate that the input side of this
+ * socket is not closed.
+ */
+ @Override()
+ public boolean isInputShutdown()
+ {
+ return closed;
+ }
+
+
+
+ /**
+ * Indicates whether the output side of this socket has been closed.
+ * This method will always return {@code false} to indicate that it
+ * is not closed.
+ *
+ * @return {@code false} to indicate that the output side of this
+ * socket is not closed.
+ */
+ @Override()
+ public boolean isOutputShutdown()
+ {
+ return closed;
+ }
+
+
+
+ /**
+ * Sends a single byte of urgent data over this socket.
+ *
+ * @param data The data to be sent.
+ *
+ * @throws IOException If a problem occurs while trying to write
+ * the provided data over this socket.
+ */
+ @Override()
+ public void sendUrgentData(int data)
+ throws IOException
+ {
+ getOutputStream().write(data);
+ }
+
+
+
+ /**
+ * Sets the value of SO_KEEPALIVE for this socket. This will not
+ * affect anything, since there is no actual network communication
+ * performed by this socket.
+ *
+ * @param on The value to use for the SO_KEEPALIVE option.
+ */
+ @Override()
+ public void setKeepAlive(boolean on)
+ {
+ keepAlive = on;
+ }
+
+
+
+ /**
+ * Sets the value of OOBINLINE for this socket. This will not
+ * affect anything, since there is no actual network communication
+ * performed by this socket.
+ *
+ * @param on The value to use for the OOBINLINE option.
+ */
+ @Override()
+ public void setOOBInline(boolean on)
+ {
+ oobInline = on;
+ }
+
+
+
+ /**
+ * Sets the provided performance preferences for this socket. This
+ * will not affect anything, since there is no actual network
+ * communication performed by this socket.
+ *
+ * @param connectionTime An {@code int} expressing the relative
+ * importance of a short connection time.
+ * @param latency An {@code int} expressing the relative
+ * importance of low latency.
+ * @param bandwidth An {@code int} expressing the relative
+ * importance of high bandwidth.
+ */
+ @Override()
+ public void setPerformancePreferences(int connectionTime,
+ int latency, int bandwidth)
+ {
+ // No implementation is required.
+ }
+
+
+
+ /**
+ * Sets the value of SO_RCVBUF for this socket. This will not
+ * affect anything, since there is no actual network communication
+ * performed by this socket.
+ *
+ * @param size The value to use for the SO_RCVBUF option.
+ */
+ @Override()
+ public void setReceiveBufferSize(int size)
+ {
+ receiveBufferSize = size;
+ }
+
+
+
+ /**
+ * Sets the value of SO_REUSEADDR for this socket. This will not
+ * affect anything, since there is no actual network communication
+ * performed by this socket.
+ *
+ * @param on The value to use for the SO_REUSEADDR option.
+ */
+ @Override()
+ public void setReuseAddress(boolean on)
+ {
+ reuseAddress = on;
+ }
+
+
+
+ /**
+ * Sets the value of SO_SNDBUF for this socket. This will not
+ * affect anything, since there is no actual network communication
+ * performed by this socket.
+ *
+ * @param size The value to use for the SO_SNDBUF option.
+ */
+ @Override()
+ public void setSendBufferSize(int size)
+ {
+ sendBufferSize = size;
+ }
+
+
+
+ /**
+ * Sets the value of SO_LINGER for this socket. This will not
+ * affect anything, since there is no actual network communication
+ * performed by this socket.
+ *
+ * @param on Indicates whether to enable the linger option.
+ * @param linger The length of time in milliseconds to allow the
+ * connection to linger.
+ */
+ @Override()
+ public void setSoLinger(boolean on, int linger)
+ {
+ lingerDuration = linger;
+ }
+
+
+
+ /**
+ * Sets the value of SO_TIMEOUT for this socket. This will not
+ * affect anything, since there is no actual network communication
+ * performed by this socket.
+ *
+ * @param timeout The value to use for the SO_TIMEOUT option.
+ */
+ @Override()
+ public void setSoTimeout(int timeout)
+ {
+ this.timeout = timeout;
+ }
+
+
+
+ /**
+ * Sets the value of TCP_NODELAY for this socket. This will not
+ * affect anything, since there is no actual network communication
+ * performed by this socket.
+ *
+ * @param on The value to use for the TCP_NODELAY option.
+ */
+ @Override()
+ public void setTcpNoDelay(boolean on)
+ {
+ tcpNoDelay = on;
+ }
+
+
+
+ /**
+ * Sets the traffic class for this socket. This will not affect
+ * anything, since there is no actual network communication
+ * performed by this socket.
+ *
+ * @param tc The value to use for the traffic class.
+ */
+ @Override()
+ public void setTrafficClass(int tc)
+ {
+ trafficClass = tc;
+ }
+
+
+
+ /**
+ * Shuts down the input side of this socket. This will have the
+ * effect of closing the entire socket.
+ */
+ @Override()
+ public void shutdownInput()
+ {
+ close();
+ }
+
+
+
+ /**
+ * Shuts down the output side of this socket. This will have the
+ * effect of closing the entire socket.
+ */
+ @Override()
+ public void shutdownOutput()
+ {
+ close();
+ }
+
+
+
+ /**
+ * Retrieves a string representation of this internal LDAP socket.
+ *
+ * @return A string representation of this internal LDAP socket.
+ */
+ @Override()
+ public String toString()
+ {
+ return "InternalLDAPSocket";
+ }
+}
+
diff --git a/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocketFactory.java b/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocketFactory.java
new file mode 100644
index 0000000..639b62f
--- /dev/null
+++ b/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocketFactory.java
@@ -0,0 +1,180 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.protocols.internal;
+
+
+
+import java.net.InetAddress;
+import java.net.Socket;
+import javax.net.SocketFactory;
+
+
+
+/**
+ * This class provides an implementation of a
+ * {@code javax.net.SocketFactory} object that can be used to create
+ * internal LDAP sockets. This socket factory can be used with some
+ * common LDAP SDKs (e.g., JNDI) in order to allow that SDK to be used
+ * to perform internal operations within OpenDS with minimal changes
+ * needed from what is required to perform external LDAP
+ * communication.
+ */
+@org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
+ mayInstantiate=true,
+ mayExtend=false,
+ mayInvoke=true)
+public final class InternalLDAPSocketFactory
+ extends SocketFactory
+{
+ /**
+ * Creates a new instance of this internal LDAP socket factory.
+ */
+ public InternalLDAPSocketFactory()
+ {
+ // No implementation is required.
+ }
+
+
+
+ /**
+ * Retrieves the default socket factory that should be used. Note
+ * that this method must be present for the implementation to work
+ * properly. Even though the superclass declares the same static
+ * method and static methods are not generally overridden, that is
+ * not the case here because the method is invoked through
+ * reflection, and the superclass returns a bogus socket factory.
+ *
+ * @return The default socket factory that should be used.
+ */
+ public static SocketFactory getDefault()
+ {
+ return new InternalLDAPSocketFactory();
+ }
+
+
+
+ /**
+ * Creates a new internal LDAP socket. The provided arguments will
+ * be ignored, as they are not needed by this implementation.
+ *
+ * @param host The remote address to which the socket should be
+ * connected.
+ * @param port The remote port to which the socket should be
+ * connected.
+ *
+ * @return The created internal LDAP socket.
+ */
+ @Override()
+ public Socket createSocket(InetAddress host, int port)
+ {
+ return new InternalLDAPSocket();
+ }
+
+
+
+ /**
+ * Creates a new internal LDAP socket. The provided arguments will
+ * be ignored, as they are not needed by this implementation.
+ *
+ * @param host The remote address to which the socket should be
+ * connected.
+ * @param port The remote port to which the socket should be
+ * connected.
+ *
+ * @return The created internal LDAP socket.
+ */
+ @Override()
+ public Socket createSocket(String host, int port)
+ {
+ return new InternalLDAPSocket();
+ }
+
+
+
+ /**
+ * Creates a new internal LDAP socket. The provided arguments will
+ * be ignored, as they are not needed by this implementation.
+ *
+ * @param host The remote address to which the socket should
+ * be connected.
+ * @param port The remote port to which the socket should be
+ * connected.
+ * @param clientHost The local address to which the socket should
+ * be bound.
+ * @param clientPort The local port to which the socket should be
+ * bound.
+ *
+ * @return The created internal LDAP socket.
+ */
+ @Override()
+ public Socket createSocket(InetAddress host, int port,
+ InetAddress clientHost, int clientPort)
+ {
+ return new InternalLDAPSocket();
+ }
+
+
+
+ /**
+ * Creates a new internal LDAP socket. The provided arguments will
+ * be ignored, as they are not needed by this implementation.
+ *
+ * @param host The remote address to which the socket should
+ * be connected.
+ * @param port The remote port to which the socket should be
+ * connected.
+ * @param clientHost The local address to which the socket should
+ * be bound.
+ * @param clientPort The local port to which the socket should be
+ * bound.
+ *
+ * @return The created internal LDAP socket.
+ */
+ @Override()
+ public Socket createSocket(String host, int port,
+ InetAddress clientHost, int clientPort)
+ {
+ return new InternalLDAPSocket();
+ }
+
+
+
+ /**
+ * Retrieves a string representation of this internal LDAP socket
+ * factory.
+ *
+ * @return A string representation of this internal LDAP socket
+ * factory.
+ */
+ @Override()
+ public String toString()
+ {
+ return "InternalLDAPSocketFactory";
+ }
+}
+
diff --git a/opends/src/server/org/opends/server/protocols/internal/NullLDAPMessage.java b/opends/src/server/org/opends/server/protocols/internal/NullLDAPMessage.java
new file mode 100644
index 0000000..0cf1271
--- /dev/null
+++ b/opends/src/server/org/opends/server/protocols/internal/NullLDAPMessage.java
@@ -0,0 +1,58 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.protocols.internal;
+
+
+
+import org.opends.server.protocols.ldap.LDAPMessage;
+import org.opends.server.protocols.ldap.UnbindRequestProtocolOp;
+
+
+
+/**
+ * This class provides a special implementation of an LDAP message
+ * that will serve as a marker in the internal LDAP input stream
+ * message queue that the end of the stream has been reached. It
+ * should not be used for any other purpose.
+ */
+@org.opends.server.types.PublicAPI(
+ stability=org.opends.server.types.StabilityLevel.PRIVATE,
+ mayInstantiate=false,
+ mayExtend=false,
+ mayInvoke=false)
+final class NullLDAPMessage
+ extends LDAPMessage
+{
+ /**
+ * Creates a new null LDAP message.
+ */
+ NullLDAPMessage()
+ {
+ super(1, new UnbindRequestProtocolOp());
+ }
+}
+
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalLDAPSocketTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalLDAPSocketTestCase.java
new file mode 100644
index 0000000..a10c410
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalLDAPSocketTestCase.java
@@ -0,0 +1,662 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ * Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ * Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.protocols.internal;
+
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.LinkedHashSet;
+import javax.naming.Context;
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchControls;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.protocols.asn1.ASN1OctetString;
+import org.opends.server.protocols.ldap.*;
+import org.opends.server.tools.LDAPReader;
+import org.opends.server.tools.LDAPWriter;
+import org.opends.server.types.*;
+
+import static org.testng.Assert.*;
+
+import static org.opends.server.util.ServerConstants.*;
+
+
+
+/**
+ * This class provides a number of tests to cover the internal LDAP socket
+ * implementation.
+ */
+public class InternalLDAPSocketTestCase
+ extends InternalTestCase
+{
+ /**
+ * Ensures that the Directory Server is running.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @BeforeClass()
+ public void startServer()
+ throws Exception
+ {
+ TestCaseUtils.startServer();
+ }
+
+
+
+ /**
+ * Tests the ability to perform an add operation over the internal LDAP
+ * socket.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testAddOperation()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(false);
+ assertFalse(DirectoryServer.entryExists(DN.decode("o=test")));
+
+ InternalLDAPSocket socket = new InternalLDAPSocket();
+ LDAPReader reader = new LDAPReader(socket);
+ LDAPWriter writer = new LDAPWriter(socket);
+
+ BindRequestProtocolOp bindRequest =
+ new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
+ 3, new ASN1OctetString("password"));
+ LDAPMessage message = new LDAPMessage(1, bindRequest);
+ writer.writeMessage(message);
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
+
+
+ ArrayList<RawAttribute> attrList = new ArrayList<RawAttribute>();
+ attrList.add(RawAttribute.create("objectClass", "organization"));
+ attrList.add(RawAttribute.create("o", "test"));
+
+ AddRequestProtocolOp addRequest =
+ new AddRequestProtocolOp(new ASN1OctetString("o=test"), attrList);
+ writer.writeMessage(new LDAPMessage(2, addRequest));
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getAddResponseProtocolOp().getResultCode(),
+ LDAPResultCode.SUCCESS);
+ assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
+
+ reader.close();
+ writer.close();
+ socket.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform an add operation over the internal LDAP
+ * socket via JNDI.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testAddOperationThroughJNDI()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(false);
+ assertFalse(DirectoryServer.entryExists(DN.decode("o=test")));
+
+
+ Hashtable<String,String> env = new Hashtable<String,String>();
+ env.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ env.put("java.naming.ldap.factory.socket",
+ InternalLDAPSocketFactory.class.getName());
+ env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
+ env.put(Context.SECURITY_CREDENTIALS, "password");
+
+ DirContext context = new InitialDirContext(env);
+
+ Attributes attributes = new BasicAttributes(true);
+
+ Attribute objectClass = new BasicAttribute("objectClass");
+ objectClass.add("top");
+ objectClass.add("organization");
+ attributes.put(objectClass);
+
+ Attribute o = new BasicAttribute("o");
+ o.add("test");
+ attributes.put(o);
+
+ context.createSubcontext("o=test", attributes);
+ assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
+
+ context.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform a compare operation over the internal LDAP
+ * socket.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testCompareOperation()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+ assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
+
+ InternalLDAPSocket socket = new InternalLDAPSocket();
+ LDAPReader reader = new LDAPReader(socket);
+ LDAPWriter writer = new LDAPWriter(socket);
+
+ BindRequestProtocolOp bindRequest =
+ new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
+ 3, new ASN1OctetString("password"));
+ LDAPMessage message = new LDAPMessage(1, bindRequest);
+ writer.writeMessage(message);
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
+
+
+ CompareRequestProtocolOp compareRequest =
+ new CompareRequestProtocolOp(new ASN1OctetString("o=test"), "o",
+ new ASN1OctetString("test"));
+ writer.writeMessage(new LDAPMessage(2, compareRequest));
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getCompareResponseProtocolOp().getResultCode(),
+ LDAPResultCode.COMPARE_TRUE);
+
+ reader.close();
+ writer.close();
+ socket.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform a compare operation over the internal LDAP
+ * socket via JNDI.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testCompareOperationThroughJNDI()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+ assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
+
+
+ Hashtable<String,String> env = new Hashtable<String,String>();
+ env.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ env.put("java.naming.ldap.factory.socket",
+ InternalLDAPSocketFactory.class.getName());
+ env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
+ env.put(Context.SECURITY_CREDENTIALS, "password");
+
+ DirContext context = new InitialDirContext(env);
+
+ SearchControls poorlyNamedSearchControls = new SearchControls();
+ poorlyNamedSearchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
+ poorlyNamedSearchControls.setReturningAttributes(new String[0]);
+
+ NamingEnumeration results = context.search("o=test", "(o=test)",
+ poorlyNamedSearchControls);
+ assertTrue(results.hasMoreElements());
+ assertNotNull(results.nextElement());
+ assertFalse(results.hasMoreElements());
+
+ context.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform a delete operation over the internal LDAP
+ * socket.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testDeleteOperation()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+ assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
+
+ InternalLDAPSocket socket = new InternalLDAPSocket();
+ LDAPReader reader = new LDAPReader(socket);
+ LDAPWriter writer = new LDAPWriter(socket);
+
+ BindRequestProtocolOp bindRequest =
+ new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
+ 3, new ASN1OctetString("password"));
+ LDAPMessage message = new LDAPMessage(1, bindRequest);
+ writer.writeMessage(message);
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
+
+
+ DeleteRequestProtocolOp deleteRequest =
+ new DeleteRequestProtocolOp(new ASN1OctetString("o=test"));
+ writer.writeMessage(new LDAPMessage(2, deleteRequest));
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getDeleteResponseProtocolOp().getResultCode(),
+ LDAPResultCode.SUCCESS);
+ assertFalse(DirectoryServer.entryExists(DN.decode("o=test")));
+
+ reader.close();
+ writer.close();
+ socket.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform a delete operation over the internal LDAP
+ * socket via JNDI.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testDeleteOperationThroughJNDI()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+ assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
+
+
+ Hashtable<String,String> env = new Hashtable<String,String>();
+ env.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ env.put("java.naming.ldap.factory.socket",
+ InternalLDAPSocketFactory.class.getName());
+ env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
+ env.put(Context.SECURITY_CREDENTIALS, "password");
+
+ DirContext context = new InitialDirContext(env);
+
+ context.destroySubcontext("o=test");
+ assertFalse(DirectoryServer.entryExists(DN.decode("o=test")));
+
+ context.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform an extended operation over the internal LDAP
+ * socket using the "Who Am I?" request.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testExtendedOperation()
+ throws Exception
+ {
+ InternalLDAPSocket socket = new InternalLDAPSocket();
+ LDAPReader reader = new LDAPReader(socket);
+ LDAPWriter writer = new LDAPWriter(socket);
+
+ BindRequestProtocolOp bindRequest =
+ new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
+ 3, new ASN1OctetString("password"));
+ LDAPMessage message = new LDAPMessage(1, bindRequest);
+ writer.writeMessage(message);
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
+
+
+ ExtendedRequestProtocolOp extendedRequest =
+ new ExtendedRequestProtocolOp(OID_WHO_AM_I_REQUEST);
+ writer.writeMessage(new LDAPMessage(2, extendedRequest));
+
+ message = reader.readMessage();
+ assertNotNull(message);
+
+ ExtendedResponseProtocolOp extendedResponse =
+ message.getExtendedResponseProtocolOp();
+ assertEquals(extendedResponse.getResultCode(), LDAPResultCode.SUCCESS);
+ assertTrue(extendedResponse.getValue().stringValue().equalsIgnoreCase(
+ "dn:cn=Directory Manager,cn=Root DNs,cn=config"));
+
+ reader.close();
+ writer.close();
+ socket.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform a modify operation over the internal LDAP
+ * socket.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testModifyOperation()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+ assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
+
+ InternalLDAPSocket socket = new InternalLDAPSocket();
+ LDAPReader reader = new LDAPReader(socket);
+ LDAPWriter writer = new LDAPWriter(socket);
+
+ BindRequestProtocolOp bindRequest =
+ new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
+ 3, new ASN1OctetString("password"));
+ LDAPMessage message = new LDAPMessage(1, bindRequest);
+ writer.writeMessage(message);
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
+
+
+ ArrayList<RawModification> mods = new ArrayList<RawModification>();
+ mods.add(RawModification.create(ModificationType.REPLACE, "description",
+ "foo"));
+
+ ModifyRequestProtocolOp modifyRequest =
+ new ModifyRequestProtocolOp(new ASN1OctetString("o=test"), mods);
+ writer.writeMessage(new LDAPMessage(2, modifyRequest));
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getModifyResponseProtocolOp().getResultCode(),
+ LDAPResultCode.SUCCESS);
+
+ reader.close();
+ writer.close();
+ socket.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform a modify operation over the internal LDAP
+ * socket via JNDI.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testModifyOperationThroughJNDI()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+ assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
+
+
+ Hashtable<String,String> env = new Hashtable<String,String>();
+ env.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ env.put("java.naming.ldap.factory.socket",
+ InternalLDAPSocketFactory.class.getName());
+ env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
+ env.put(Context.SECURITY_CREDENTIALS, "password");
+
+ DirContext context = new InitialDirContext(env);
+
+ ModificationItem[] mods =
+ {
+ new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
+ new BasicAttribute("description", "foo"))
+ };
+
+ context.modifyAttributes("o=test", mods);
+ context.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform a modify DN operation over the internal LDAP
+ * socket.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testModifyDNOperation()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+ TestCaseUtils.addEntry(
+ "dn: ou=People,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: People");
+
+ assertTrue(DirectoryServer.entryExists(DN.decode("ou=People,o=test")));
+ assertFalse(DirectoryServer.entryExists(DN.decode("ou=Users,o=test")));
+
+ InternalLDAPSocket socket = new InternalLDAPSocket();
+ LDAPReader reader = new LDAPReader(socket);
+ LDAPWriter writer = new LDAPWriter(socket);
+
+ BindRequestProtocolOp bindRequest =
+ new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
+ 3, new ASN1OctetString("password"));
+ LDAPMessage message = new LDAPMessage(1, bindRequest);
+ writer.writeMessage(message);
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
+
+
+ ModifyDNRequestProtocolOp modifyDNRequest =
+ new ModifyDNRequestProtocolOp(new ASN1OctetString("ou=People,o=test"),
+ new ASN1OctetString("ou=Users"), true);
+ writer.writeMessage(new LDAPMessage(2, modifyDNRequest));
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getModifyDNResponseProtocolOp().getResultCode(),
+ LDAPResultCode.SUCCESS);
+
+ assertFalse(DirectoryServer.entryExists(DN.decode("ou=People,o=test")));
+ assertTrue(DirectoryServer.entryExists(DN.decode("ou=Users,o=test")));
+
+ reader.close();
+ writer.close();
+ socket.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform a modify DN operation over the internal LDAP
+ * socket via JNDI.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testModifyDNOperationThroughJNDI()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+ TestCaseUtils.addEntry(
+ "dn: ou=People,o=test",
+ "objectClass: top",
+ "objectClass: organizationalUnit",
+ "ou: People");
+
+ assertTrue(DirectoryServer.entryExists(DN.decode("ou=People,o=test")));
+ assertFalse(DirectoryServer.entryExists(DN.decode("ou=Users,o=test")));
+
+
+ Hashtable<String,String> env = new Hashtable<String,String>();
+ env.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ env.put("java.naming.ldap.factory.socket",
+ InternalLDAPSocketFactory.class.getName());
+ env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
+ env.put(Context.SECURITY_CREDENTIALS, "password");
+
+ DirContext context = new InitialDirContext(env);
+
+ context.rename("ou=People,o=test", "ou=Users,o=test");
+
+ assertFalse(DirectoryServer.entryExists(DN.decode("ou=People,o=test")));
+ assertTrue(DirectoryServer.entryExists(DN.decode("ou=Users,o=test")));
+
+ context.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform a search operation over the internal LDAP
+ * socket.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testSearchOperation()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+ assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
+
+ InternalLDAPSocket socket = new InternalLDAPSocket();
+ LDAPReader reader = new LDAPReader(socket);
+ LDAPWriter writer = new LDAPWriter(socket);
+
+ BindRequestProtocolOp bindRequest =
+ new BindRequestProtocolOp(new ASN1OctetString("cn=Directory Manager"),
+ 3, new ASN1OctetString("password"));
+ LDAPMessage message = new LDAPMessage(1, bindRequest);
+ writer.writeMessage(message);
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getBindResponseProtocolOp().getResultCode(), 0);
+
+
+ SearchRequestProtocolOp searchRequest =
+ new SearchRequestProtocolOp(new ASN1OctetString("o=test"),
+ SearchScope.BASE_OBJECT,
+ DereferencePolicy.NEVER_DEREF_ALIASES,
+ 0, 0, false,
+ LDAPFilter.decode("(objectClass=*)"),
+ new LinkedHashSet<String>());
+ writer.writeMessage(new LDAPMessage(2, searchRequest));
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getSearchResultEntryProtocolOp().getDN(),
+ DN.decode("o=test"));
+
+ message = reader.readMessage();
+ assertNotNull(message);
+ assertEquals(message.getSearchResultDoneProtocolOp().getResultCode(),
+ LDAPResultCode.SUCCESS);
+
+ reader.close();
+ writer.close();
+ socket.close();
+ }
+
+
+
+ /**
+ * Tests the ability to perform a searcj operation over the internal LDAP
+ * socket via JNDI.
+ *
+ * @throws Exception If an unexpected problem occurs.
+ */
+ @Test()
+ public void testSearchOperationThroughJNDI()
+ throws Exception
+ {
+ TestCaseUtils.initializeTestBackend(true);
+ assertTrue(DirectoryServer.entryExists(DN.decode("o=test")));
+
+
+ Hashtable<String,String> env = new Hashtable<String,String>();
+ env.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ env.put("java.naming.ldap.factory.socket",
+ InternalLDAPSocketFactory.class.getName());
+ env.put(Context.PROVIDER_URL, "ldap://doesntmatter:389/");
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ env.put(Context.SECURITY_PRINCIPAL, "cn=Directory Manager");
+ env.put(Context.SECURITY_CREDENTIALS, "password");
+
+ DirContext context = new InitialDirContext(env);
+
+ SearchControls poorlyNamedSearchControls = new SearchControls();
+ poorlyNamedSearchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
+
+ NamingEnumeration results = context.search("o=test", "(objectClass=*)",
+ poorlyNamedSearchControls);
+ assertTrue(results.hasMoreElements());
+ assertNotNull(results.nextElement());
+ assertFalse(results.hasMoreElements());
+
+ context.close();
+ }
+}
+
--
Gitblit v1.10.0