/* * 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 legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * 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 legal-notices/CDDLv1_0.txt. * 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 * * * Copyright 2009 Sun Microsystems, Inc. * Portions Copyright 2014-2015 ForgeRock AS */ package org.opends.server.protocols.internal; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.io.ASN1; import org.forgerock.opendj.io.ASN1Reader; import org.forgerock.opendj.ldap.ByteSequenceReader; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.ByteStringBuilder; import org.opends.server.core.*; import org.opends.server.protocols.ldap.*; import org.opends.server.types.*; import static org.forgerock.opendj.ldap.DecodeException.*; import static org.opends.messages.ProtocolMessages.*; import static org.opends.server.protocols.internal.InternalClientConnection.*; import static org.opends.server.protocols.internal.Requests.*; 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; private final ASN1Reader reader; /** The internal LDAP socket with which this output stream is associated. */ private final InternalLDAPSocket socket; /** The immediate data being written. */ private ByteSequenceReader byteBuffer; /** * The save buffer used to store any unprocessed data waiting * to be read as ASN.1 elements. (Usually due to writing incomplete * ASN.1 elements.) */ private final ByteStringBuilder saveBuffer; private final ByteSequenceReader saveBufferReader; /** * An adaptor class for reading from a save buffer and the bytes * being written sequentially using the InputStream interface. * * Since the bytes being written are only available duing the write * call, any unused data will be appended to the save buffer before * returning from the write method. This reader will always read the * save buffer first before the actual bytes being written to ensure * bytes are read in the same order as they are written. */ private class CombinedBufferInputStream extends InputStream { /** {@inheritDoc} */ @Override public int available() { // The number of available bytes is the sum of the save buffer // and the last read data in the NIO ByteStringBuilder. return saveBufferReader.remaining() + byteBuffer.remaining(); } /** {@inheritDoc} */ @Override public int read() { if(saveBufferReader.remaining() > 0) { // Try saved buffer first return 0xFF & saveBufferReader.readByte(); } if(byteBuffer.remaining() > 0) { // Must still be on the channel buffer return 0xFF & byteBuffer.readByte(); } return -1; } /** {@inheritDoc} */ @Override public int read(byte[] bytes) { return read(bytes, 0, bytes.length); } /** {@inheritDoc} */ @Override public int read(byte[] value, int off, int length) { int bytesCopied=0; int len; if(saveBufferReader.remaining() > 0) { // Copy out of the last saved buffer first len = Math.min(saveBufferReader.remaining(), length); saveBufferReader.readBytes(value, off, len); bytesCopied += len; } if(bytesCopied < length && byteBuffer.remaining() > 0) { // Copy out of the channel buffer if we haven't got // everything we needed. len = Math.min(byteBuffer.remaining(), length - bytesCopied); byteBuffer.readBytes(value, off + bytesCopied, len); bytesCopied += len; } return bytesCopied; } /** {@inheritDoc} */ @Override public long skip(long length) { int bytesSkipped=0; int len; if(saveBufferReader.remaining() > 0) { // Skip in the last saved buffer first len = Math.min(saveBufferReader.remaining(), (int)length); saveBufferReader.position(saveBufferReader.position() + len); bytesSkipped += len; } if(bytesSkipped < length && byteBuffer.remaining() > 0) { //Skip in the channel buffer if we haven't skipped enough. len = Math.min(byteBuffer.remaining(), (int)length - bytesSkipped); byteBuffer.position(byteBuffer.position() + len); bytesSkipped += len; } return bytesSkipped; } } /** * 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; this.closed = false; this.saveBuffer = new ByteStringBuilder(); this.saveBufferReader = saveBuffer.asReader(); CombinedBufferInputStream bufferStream = new CombinedBufferInputStream(); this.reader = ASN1.getReader(bufferStream); } /** * 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) { LocalizableMessage m = ERR_INTERNALOS_CLOSED.get(); throw new IOException(m.toString()); } byteBuffer = ByteString.wrap(b, off, len).asReader(); try { while(reader.elementAvailable()) { LDAPMessage msg = LDAPReader.readMessage(reader); processMessage(msg); } } catch(Exception e) { throw new IOException(e.getMessage()); } // Clear the save buffer if we have read all of it if(saveBufferReader.remaining() == 0) { saveBuffer.clear(); saveBufferReader.rewind(); } // Append any unused data in the channel buffer to the save buffer if(byteBuffer.remaining() > 0) { saveBuffer.appendBytes(byteBuffer, byteBuffer.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 { write(new byte[]{(byte)b}, 0, 1); } /** * 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 message The LDAP message to process. * * @throws IOException If a problem occurs while attempting to * decode the provided ASN.1 element as an * LDAP message. */ private void processMessage(LDAPMessage message) throws IOException { 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: LocalizableMessage 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(); InternalClientConnection conn = socket.getConnection(); AddOperationBasis op = new AddOperationBasis(conn, InternalClientConnection.nextOperationID(), messageID, message.getControls(), request.getDN(), request.getAttributes()); op.run(); AddResponseProtocolOp addResponse = new AddResponseProtocolOp(op.getResultCode().intValue(), op.getErrorMessage().toMessage(), op.getMatchedDN(), op.getReferralURLs()); List<Control> responseControls = op.getResponseControls(); 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) { LocalizableMessage 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; } InternalClientConnection conn = socket.getConnection(); BindOperationBasis op = new BindOperationBasis(conn, nextOperationID(), messageID, message.getControls(), String.valueOf(request.getProtocolVersion()), request.getDN(), request.getSimplePassword()); op.run(); BindResponseProtocolOp bindResponse = new BindResponseProtocolOp(op.getResultCode().intValue(), op.getErrorMessage().toMessage(), op.getMatchedDN(), op.getReferralURLs()); List<Control> responseControls = op.getResponseControls(); 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(); InternalClientConnection conn = socket.getConnection(); CompareOperationBasis op = new CompareOperationBasis(conn, nextOperationID(), messageID, message.getControls(), request.getDN(), request.getAttributeType(), request.getAssertionValue()); op.run(); CompareResponseProtocolOp compareResponse = new CompareResponseProtocolOp( op.getResultCode().intValue(), op.getErrorMessage().toMessage(), op.getMatchedDN(), op.getReferralURLs()); List<Control> responseControls = op.getResponseControls(); 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(); InternalClientConnection conn = socket.getConnection(); DeleteOperationBasis op = new DeleteOperationBasis(conn, nextOperationID(), messageID, message.getControls(), request.getDN()); op.run(); DeleteResponseProtocolOp deleteResponse = new DeleteResponseProtocolOp( op.getResultCode().intValue(), op.getErrorMessage().toMessage(), op.getMatchedDN(), op.getReferralURLs()); List<Control> responseControls = op.getResponseControls(); 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)) { LocalizableMessage m = ERR_INTERNALOS_STARTTLS_NOT_SUPPORTED.get(); ExtendedResponseProtocolOp extendedResponse = new ExtendedResponseProtocolOp( LDAPResultCode.UNWILLING_TO_PERFORM, m); socket.getInputStream().addLDAPMessage( new LDAPMessage(messageID, extendedResponse)); return; } InternalClientConnection conn = socket.getConnection(); ExtendedOperationBasis op = new ExtendedOperationBasis(conn, nextOperationID(), messageID, message.getControls(), request.getOID(), request.getValue()); op.run(); ExtendedResponseProtocolOp extendedResponse = new ExtendedResponseProtocolOp( op.getResultCode().intValue(), op.getErrorMessage().toMessage(), op.getMatchedDN(), op.getReferralURLs(), op.getResponseOID(), op.getResponseValue()); List<Control> responseControls = op.getResponseControls(); 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(); InternalClientConnection conn = socket.getConnection(); ModifyOperationBasis op = new ModifyOperationBasis(conn, nextOperationID(), messageID, message.getControls(), request.getDN(), request.getModifications()); op.run(); ModifyResponseProtocolOp modifyResponse = new ModifyResponseProtocolOp( op.getResultCode().intValue(), op.getErrorMessage().toMessage(), op.getMatchedDN(), op.getReferralURLs()); List<Control> responseControls = op.getResponseControls(); 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(); InternalClientConnection conn = socket.getConnection(); ModifyDNOperationBasis op = new ModifyDNOperationBasis(conn, nextOperationID(), messageID, message.getControls(), request.getEntryDN(), request.getNewRDN(), request.deleteOldRDN(), request.getNewSuperior()); op.run(); ModifyDNResponseProtocolOp modifyDNResponse = new ModifyDNResponseProtocolOp( op.getResultCode().intValue(), op.getErrorMessage().toMessage(), op.getMatchedDN(), op.getReferralURLs()); List<Control> responseControls = op.getResponseControls(); 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(); InternalClientConnection conn = socket.getConnection(); DN baseDN = null; SearchFilter filter; try { baseDN = DN.valueOf(request.getBaseDN().toString()); filter = request.getFilter().toSearchFilter(); } catch (DirectoryException e) { final String cause = baseDN == null ? "baseDN" : "filter"; throw error(LocalizableMessage.raw("Could not decode " + cause), e); } SearchRequest sr = newSearchRequest(baseDN, request.getScope(), filter) .setDereferenceAliasesPolicy(request.getDereferencePolicy()) .setSizeLimit(request.getSizeLimit()) .setTimeLimit(request.getTimeLimit()) .setTypesOnly(request.getTypesOnly()) .addAttribute(request.getAttributes()) .addControl(message.getControls()); InternalSearchOperation op = new InternalSearchOperation( conn, nextOperationID(), messageID, sr, this); op.run(); SearchResultDoneProtocolOp searchDone = new SearchResultDoneProtocolOp( op.getResultCode().intValue(), op.getErrorMessage().toMessage(), op.getMatchedDN(), op.getReferralURLs()); List<Control> responseControls = op.getResponseControls(); 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. */ @Override @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) { List<Control> entryControls = searchEntry.getControls(); 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. */ @Override @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) { List<Control> entryControls = searchReference.getControls(); 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 getClass().getSimpleName(); } }