| opendj-sdk/opends/src/messages/messages/protocol.properties | ●●●●● patch | view | raw | blame | history | |
| opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPInputStream.java | ●●●●● patch | view | raw | blame | history | |
| opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPOutputStream.java | ●●●●● patch | view | raw | blame | history | |
| opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocket.java | ●●●●● patch | view | raw | blame | history | |
| opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocketFactory.java | ●●●●● patch | view | raw | blame | history | |
| opendj-sdk/opends/src/server/org/opends/server/protocols/internal/NullLDAPMessage.java | ●●●●● patch | view | raw | blame | history | |
| opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalLDAPSocketTestCase.java | ●●●●● patch | view | raw | blame | history |
opendj-sdk/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 opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPInputStream.java
New file @@ -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"; } } opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPOutputStream.java
New file @@ -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"; } } opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocket.java
New file @@ -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"; } } opendj-sdk/opends/src/server/org/opends/server/protocols/internal/InternalLDAPSocketFactory.java
New file @@ -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"; } } opendj-sdk/opends/src/server/org/opends/server/protocols/internal/NullLDAPMessage.java
New file @@ -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()); } } opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/internal/InternalLDAPSocketTestCase.java
New file @@ -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(); } }