/* * 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 * * * Copyright 2009 Sun Microsystems, Inc. */ package com.sun.opends.sdk.ldap; import static com.sun.opends.sdk.ldap.LDAPConstants.*; import org.opends.sdk.ldap.LDAPEncoder; import org.opends.sdk.ldap.ResolvedSchema; import java.io.EOFException; import java.io.IOException; import java.net.InetSocketAddress; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.SSLContext; import javax.security.sasl.SaslException; import org.opends.sdk.*; import org.opends.sdk.controls.Control; import org.opends.sdk.controls.ControlDecoder; import org.opends.sdk.extensions.StartTLSRequest; import org.opends.sdk.requests.*; import org.opends.sdk.responses.*; import org.opends.sdk.sasl.SASLBindRequest; import org.opends.sdk.sasl.SASLContext; import org.opends.sdk.schema.Schema; import com.sun.grizzly.filterchain.Filter; import com.sun.grizzly.filterchain.FilterChain; import com.sun.grizzly.filterchain.StreamTransformerFilter; import com.sun.grizzly.ssl.*; import com.sun.grizzly.streams.StreamWriter; import com.sun.opends.sdk.util.Validator; /** * LDAP connection implementation. *

* TODO: handle illegal state exceptions. */ public final class LDAPConnection extends AbstractAsynchronousConnection implements AsynchronousConnection { private final class LDAPMessageHandlerImpl extends AbstractLDAPMessageHandler { /** * {@inheritDoc} */ @Override public void handleAddResult(int messageID, Result result) { AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .remove(messageID); if (pendingRequest != null) { if (pendingRequest instanceof LDAPFutureResultImpl) { LDAPFutureResultImpl future = (LDAPFutureResultImpl) pendingRequest; if (future.getRequest() instanceof AddRequest) { future.setResultOrError(result); return; } } handleIncorrectResponse(pendingRequest); } } /** * {@inheritDoc} */ @Override public void handleBindResult(int messageID, BindResult result) { AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .remove(messageID); if (pendingRequest != null) { if (pendingRequest instanceof LDAPBindFutureResultImpl) { LDAPBindFutureResultImpl future = ((LDAPBindFutureResultImpl) pendingRequest); BindRequest request = future.getRequest(); if (request instanceof SASLBindRequest) { SASLBindRequest saslBind = (SASLBindRequest) request; SASLContext saslContext = future.getSASLContext(); if ((result.getResultCode() == ResultCode.SUCCESS || result .getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS) && !saslContext.isComplete()) { try { saslContext.evaluateCredentials(result .getServerSASLCredentials()); } catch (SaslException e) { pendingBindOrStartTLS = -1; // FIXME: I18N need to have a better error message. // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_LOCAL_ERROR) .setDiagnosticMessage( "An error occurred during SASL authentication") .setCause(e); future.adaptErrorResult(errorResult); return; } } if (result.getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS) { // The server is expecting a multi stage bind response. messageID = nextMsgID.getAndIncrement(); ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); try { synchronized (writeLock) { pendingRequests.put(messageID, future); try { LDAPEncoder.encodeBindRequest(asn1Writer, messageID, 3, saslBind, saslContext .getSASLCredentials()); asn1Writer.flush(); } catch (IOException e) { pendingRequests.remove(messageID); // FIXME: what other sort of IOExceptions can be // thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_ENCODING_ERROR) .setCause(e); connectionErrorOccurred(errorResult); future.adaptErrorResult(errorResult); } } } finally { connFactory.releaseASN1Writer(asn1Writer); } return; } if ((result.getResultCode() == ResultCode.SUCCESS) && saslContext.isSecure()) { // The connection needs to be secured by the SASL // mechanism. installFilter(SASLFilter.getInstance(saslContext, connection)); } } pendingBindOrStartTLS = -1; future.setResultOrError(result); } else { handleIncorrectResponse(pendingRequest); } } } /** * {@inheritDoc} */ @Override public void handleCompareResult(int messageID, CompareResult result) { AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .remove(messageID); if (pendingRequest != null) { if (pendingRequest instanceof LDAPCompareFutureResultImpl) { LDAPCompareFutureResultImpl future = (LDAPCompareFutureResultImpl) pendingRequest; future.setResultOrError(result); } else { handleIncorrectResponse(pendingRequest); } } } /** * {@inheritDoc} */ @Override public void handleDeleteResult(int messageID, Result result) { AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .remove(messageID); if (pendingRequest != null) { if (pendingRequest instanceof LDAPFutureResultImpl) { LDAPFutureResultImpl future = (LDAPFutureResultImpl) pendingRequest; if (future.getRequest() instanceof DeleteRequest) { future.setResultOrError(result); return; } } handleIncorrectResponse(pendingRequest); } } /** * {@inheritDoc} */ public void handleException(Throwable throwable) { Result errorResult; if (throwable instanceof EOFException) { // FIXME: Is this the best result code? errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_SERVER_DOWN).setCause(throwable); } else { // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_DECODING_ERROR).setCause(throwable); } connectionErrorOccurred(errorResult); } /** * {@inheritDoc} */ @Override public void handleExtendedResult(int messageID, GenericExtendedResult result) { if (messageID == 0) { if ((result.getResponseName() != null) && result.getResponseName().equals( OID_NOTICE_OF_DISCONNECTION)) { Result errorResult = Responses.newResult( result.getResultCode()).setDiagnosticMessage( result.getDiagnosticMessage()); close(null, true, errorResult); return; } else { // Unsolicited notification received. synchronized (writeLock) { if (isClosed) { // Don't notify after connection is closed. return; } for (ConnectionEventListener listener : listeners) { listener .connectionReceivedUnsolicitedNotification(result); } } } } AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .remove(messageID); if (pendingRequest instanceof LDAPExtendedFutureResultImpl) { LDAPExtendedFutureResultImpl extendedFuture = ((LDAPExtendedFutureResultImpl) pendingRequest); try { handleExtendedResult0(extendedFuture, result); } catch (DecodeException de) { // FIXME: should the connection be closed as well? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_DECODING_ERROR) .setDiagnosticMessage(de.getLocalizedMessage()).setCause( de); extendedFuture.adaptErrorResult(errorResult); } } else { handleIncorrectResponse(pendingRequest); } } /** * {@inheritDoc} */ @Override public void handleIntermediateResponse(int messageID, GenericIntermediateResponse response) { AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .remove(messageID); if (pendingRequest != null) { handleIncorrectResponse(pendingRequest); // FIXME: intermediate responses can occur for all operations. // if (pendingRequest instanceof LDAPExtendedFutureResultImpl) // { // LDAPExtendedFutureResultImpl extendedFuture = // ((LDAPExtendedFutureResultImpl) pendingRequest); // ExtendedRequest request = extendedFuture.getRequest(); // // try // { // IntermediateResponse decodedResponse = // request.getExtendedOperation() // .decodeIntermediateResponse( // response.getResponseName(), // response.getResponseValue()); // extendedFuture.handleIntermediateResponse(decodedResponse); // } // catch (DecodeException de) // { // pendingRequest.failure(de); // } // } // else // { // handleIncorrectResponse(pendingRequest); // } } } /** * {@inheritDoc} */ @Override public void handleModifyDNResult(int messageID, Result result) { AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .remove(messageID); if (pendingRequest != null) { if (pendingRequest instanceof LDAPFutureResultImpl) { LDAPFutureResultImpl future = (LDAPFutureResultImpl) pendingRequest; if (future.getRequest() instanceof ModifyDNRequest) { future.setResultOrError(result); return; } } handleIncorrectResponse(pendingRequest); } } /** * {@inheritDoc} */ @Override public void handleModifyResult(int messageID, Result result) { AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .remove(messageID); if (pendingRequest != null) { if (pendingRequest instanceof LDAPFutureResultImpl) { LDAPFutureResultImpl future = (LDAPFutureResultImpl) pendingRequest; if (future.getRequest() instanceof ModifyRequest) { future.setResultOrError(result); return; } } handleIncorrectResponse(pendingRequest); } } /** * {@inheritDoc} */ @Override public void handleSearchResult(int messageID, Result result) { AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .remove(messageID); if (pendingRequest != null) { if (pendingRequest instanceof LDAPSearchFutureResultImpl) { ((LDAPSearchFutureResultImpl) pendingRequest) .setResultOrError(result); } else { handleIncorrectResponse(pendingRequest); } } } /** * {@inheritDoc} */ @Override public void handleSearchResultEntry(int messageID, SearchResultEntry entry) { AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .get(messageID); if (pendingRequest != null) { if (pendingRequest instanceof LDAPSearchFutureResultImpl) { ((LDAPSearchFutureResultImpl) pendingRequest) .handleSearchResultEntry(entry); } else { handleIncorrectResponse(pendingRequest); } } } /** * {@inheritDoc} */ @Override public void handleSearchResultReference(int messageID, SearchResultReference reference) { AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .get(messageID); if (pendingRequest != null) { if (pendingRequest instanceof LDAPSearchFutureResultImpl) { ((LDAPSearchFutureResultImpl) pendingRequest) .handleSearchResultReference(reference); } else { handleIncorrectResponse(pendingRequest); } } } @Override public Control decodeResponseControl(int messageID, String oid, boolean isCritical, ByteString value, Schema schema) throws DecodeException { ControlDecoder decoder = connFactory.getControlDecoder(oid); if (decoder != null) { return decoder.decode(isCritical, value, schema); } return super.decodeResponseControl(messageID, oid, isCritical, value, schema); } public ResolvedSchema resolveSchema(String dn) throws DecodeException { DN initialDN; try { initialDN = DN.valueOf(dn, schema); } catch (LocalizedIllegalArgumentException e) { throw DecodeException.error(e.getMessageObject()); } return new ResolvedSchemaImpl(schema, initialDN); } public Schema getDefaultSchema() { return schema; } } private static final class ResolvedSchemaImpl implements ResolvedSchema { private final DN initialDN; private final Schema schema; private ResolvedSchemaImpl(Schema schema, DN initialDN) { this.schema = schema; this.initialDN = initialDN; } public AttributeDescription decodeAttributeDescription( String attributeDescription) throws DecodeException { try { return AttributeDescription.valueOf(attributeDescription, schema); } catch (LocalizedIllegalArgumentException e) { throw DecodeException.error(e.getMessageObject()); } } public DN decodeDN(String dn) throws DecodeException { try { return DN.valueOf(dn, schema); } catch (LocalizedIllegalArgumentException e) { throw DecodeException.error(e.getMessageObject()); } } public RDN decodeRDN(String rdn) throws DecodeException { try { return RDN.valueOf(rdn, schema); } catch (LocalizedIllegalArgumentException e) { throw DecodeException.error(e.getMessageObject()); } } public DN getInitialDN() { return initialDN; } public Schema getSchema() { return schema; } } private final Schema schema; private final com.sun.grizzly.Connection connection; private Result connectionInvalidReason; private final LDAPConnectionFactoryImpl connFactory; private FilterChain customFilterChain; private final LDAPMessageHandler handler = new LDAPMessageHandlerImpl(); private boolean isClosed = false; private final List listeners = new CopyOnWriteArrayList(); private final AtomicInteger nextMsgID = new AtomicInteger(1); private volatile int pendingBindOrStartTLS = -1; private final ConcurrentHashMap> pendingRequests = new ConcurrentHashMap>(); private final InetSocketAddress serverAddress; private StreamWriter streamWriter; private final Object writeLock = new Object(); /** * Creates a new LDAP connection. * * @param connection * The Grizzly connection. * @param serverAddress * The address of the server. * @param schema * The schema which will be used to decode responses from the * server. * @param connFactory * The associated connection factory. */ LDAPConnection(com.sun.grizzly.Connection connection, InetSocketAddress serverAddress, Schema schema, LDAPConnectionFactoryImpl connFactory) { this.connection = connection; this.serverAddress = serverAddress; this.schema = schema; this.connFactory = connFactory; this.streamWriter = getFilterChainStreamWriter(); } /** * {@inheritDoc} */ public void abandon(AbandonRequest request) { AbstractLDAPFutureResultImpl pendingRequest = pendingRequests .remove(request.getMessageID()); if (pendingRequest != null) { pendingRequest.cancel(false); int messageID = nextMsgID.getAndIncrement(); ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); try { synchronized (writeLock) { if (connectionInvalidReason != null) { return; } if (pendingBindOrStartTLS > 0) { // This is not allowed. We will just ignore this // abandon request. } try { LDAPEncoder.encodeAbandonRequest(asn1Writer, messageID, request); asn1Writer.flush(); } catch (IOException e) { // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e); connectionErrorOccurred(errorResult); } } } finally { connFactory.releaseASN1Writer(asn1Writer); } } } /** * {@inheritDoc} */ public FutureResult add(AddRequest request, ResultHandler handler) { int messageID = nextMsgID.getAndIncrement(); LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request, handler, this); ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); try { synchronized (writeLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (pendingBindOrStartTLS > 0) { future.setResultOrError(Responses.newResult( ResultCode.OPERATIONS_ERROR).setDiagnosticMessage( "Bind or Start TLS operation in progress")); return future; } pendingRequests.put(messageID, future); try { LDAPEncoder.encodeAddRequest(asn1Writer, messageID, request); asn1Writer.flush(); } catch (IOException e) { pendingRequests.remove(messageID); // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e); connectionErrorOccurred(errorResult); future.adaptErrorResult(errorResult); } } } finally { connFactory.releaseASN1Writer(asn1Writer); } return future; } /** * {@inheritDoc} */ public void addConnectionEventListener( ConnectionEventListener listener) throws IllegalStateException, NullPointerException { Validator.ensureNotNull(listener); listeners.add(listener); } /** * {@inheritDoc} */ public FutureResult bind(BindRequest request, ResultHandler handler) { int messageID = nextMsgID.getAndIncrement(); LDAPBindFutureResultImpl future = new LDAPBindFutureResultImpl(messageID, request, handler, this); ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); try { synchronized (writeLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (pendingBindOrStartTLS > 0) { future.setResultOrError(Responses.newBindResult( ResultCode.OPERATIONS_ERROR).setDiagnosticMessage( "Bind or Start TLS operation in progress")); return future; } if (!pendingRequests.isEmpty()) { future.setResultOrError(Responses.newBindResult( ResultCode.OPERATIONS_ERROR).setDiagnosticMessage( "There are other operations pending on this connection")); return future; } pendingRequests.put(messageID, future); pendingBindOrStartTLS = messageID; try { if (request instanceof SASLBindRequest) { try { SASLBindRequest saslBind = (SASLBindRequest) request; SASLContext saslContext = saslBind .getClientContext(serverAddress.getHostName()); future.setSASLContext(saslContext); LDAPEncoder.encodeBindRequest(asn1Writer, messageID, 3, saslBind, saslContext.getSASLCredentials()); } catch (SaslException e) { // FIXME: I18N need to have a better error message. // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_LOCAL_ERROR) .setDiagnosticMessage( "An error occurred during SASL authentication") .setCause(e); future.adaptErrorResult(errorResult); return future; } } else if (request instanceof SimpleBindRequest) { LDAPEncoder.encodeBindRequest(asn1Writer, messageID, 3, (SimpleBindRequest) request); } else { pendingRequests.remove(messageID); future.setResultOrError(Responses.newBindResult( ResultCode.CLIENT_SIDE_AUTH_UNKNOWN) .setDiagnosticMessage("Auth type not supported")); } asn1Writer.flush(); } catch (IOException e) { pendingRequests.remove(messageID); // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e); connectionErrorOccurred(errorResult); future.adaptErrorResult(errorResult); } } } finally { connFactory.releaseASN1Writer(asn1Writer); } return future; } /** * {@inheritDoc} */ public void close(UnbindRequest request, String reason) throws NullPointerException { // FIXME: I18N need to internationalize this message. Validator.ensureNotNull(request); close(request, false, Responses.newResult( ResultCode.CLIENT_SIDE_USER_CANCELLED).setDiagnosticMessage( "Connection closed by client" + (reason != null ? ": " + reason : ""))); } /** * {@inheritDoc} */ public FutureResult compare(CompareRequest request, ResultHandler handler) { int messageID = nextMsgID.getAndIncrement(); LDAPCompareFutureResultImpl future = new LDAPCompareFutureResultImpl( messageID, request, handler, this); ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); try { synchronized (writeLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (pendingBindOrStartTLS > 0) { future.setResultOrError(Responses.newCompareResult( ResultCode.OPERATIONS_ERROR).setDiagnosticMessage( "Bind or Start TLS operation in progress")); return future; } pendingRequests.put(messageID, future); try { LDAPEncoder.encodeCompareRequest(asn1Writer, messageID, request); asn1Writer.flush(); } catch (IOException e) { pendingRequests.remove(messageID); // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e); connectionErrorOccurred(errorResult); future.adaptErrorResult(errorResult); } } } finally { connFactory.releaseASN1Writer(asn1Writer); } return future; } /** * {@inheritDoc} */ public FutureResult delete(DeleteRequest request, ResultHandler handler) { int messageID = nextMsgID.getAndIncrement(); LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request, handler, this); ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); try { synchronized (writeLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (pendingBindOrStartTLS > 0) { future.setResultOrError(Responses.newResult( ResultCode.OPERATIONS_ERROR).setDiagnosticMessage( "Bind or Start TLS operation in progress")); return future; } pendingRequests.put(messageID, future); try { LDAPEncoder.encodeDeleteRequest(asn1Writer, messageID, request); asn1Writer.flush(); } catch (IOException e) { pendingRequests.remove(messageID); // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e); connectionErrorOccurred(errorResult); future.adaptErrorResult(errorResult); } } } finally { connFactory.releaseASN1Writer(asn1Writer); } return future; } /** * {@inheritDoc} */ public FutureResult extendedRequest( ExtendedRequest request, ResultHandler handler) { int messageID = nextMsgID.getAndIncrement(); LDAPExtendedFutureResultImpl future = new LDAPExtendedFutureResultImpl( messageID, request, handler, this); ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); try { synchronized (writeLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (pendingBindOrStartTLS > 0) { future.setResultOrError(request.getExtendedOperation() .decodeResponse(ResultCode.OPERATIONS_ERROR, "", "Bind or Start TLS operation in progress")); return future; } if (request.getRequestName().equals( StartTLSRequest.OID_START_TLS_REQUEST)) { if (!pendingRequests.isEmpty()) { future.setResultOrError(request.getExtendedOperation() .decodeResponse(ResultCode.OPERATIONS_ERROR, "", "There are pending operations on this connection")); return future; } if (isTLSEnabled()) { future.setResultOrError(request.getExtendedOperation() .decodeResponse(ResultCode.OPERATIONS_ERROR, "", "This connection is already TLS enabled")); } pendingBindOrStartTLS = messageID; } pendingRequests.put(messageID, future); try { LDAPEncoder.encodeExtendedRequest(asn1Writer, messageID, request); asn1Writer.flush(); } catch (IOException e) { pendingRequests.remove(messageID); // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e); connectionErrorOccurred(errorResult); future.adaptErrorResult(errorResult); } } } finally { connFactory.releaseASN1Writer(asn1Writer); } return future; } /** * {@inheritDoc} */ public FutureResult modify(ModifyRequest request, ResultHandler handler) { int messageID = nextMsgID.getAndIncrement(); LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request, handler, this); ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); try { synchronized (writeLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (pendingBindOrStartTLS > 0) { future.setResultOrError(Responses.newResult( ResultCode.OPERATIONS_ERROR).setDiagnosticMessage( "Bind or Start TLS operation in progress")); return future; } pendingRequests.put(messageID, future); try { LDAPEncoder.encodeModifyRequest(asn1Writer, messageID, request); asn1Writer.flush(); } catch (IOException e) { pendingRequests.remove(messageID); // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e); connectionErrorOccurred(errorResult); future.adaptErrorResult(errorResult); } } } finally { connFactory.releaseASN1Writer(asn1Writer); } return future; } /** * {@inheritDoc} */ public FutureResult modifyDN(ModifyDNRequest request, ResultHandler handler) { int messageID = nextMsgID.getAndIncrement(); LDAPFutureResultImpl future = new LDAPFutureResultImpl(messageID, request, handler, this); ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); try { synchronized (writeLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (pendingBindOrStartTLS > 0) { future.setResultOrError(Responses.newResult( ResultCode.OPERATIONS_ERROR).setDiagnosticMessage( "Bind or Start TLS operation in progress")); return future; } pendingRequests.put(messageID, future); try { LDAPEncoder.encodeModifyDNRequest(asn1Writer, messageID, request); asn1Writer.flush(); } catch (IOException e) { pendingRequests.remove(messageID); // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e); connectionErrorOccurred(errorResult); future.adaptErrorResult(errorResult); } } } finally { connFactory.releaseASN1Writer(asn1Writer); } return future; } /** * {@inheritDoc} */ public void removeConnectionEventListener( ConnectionEventListener listener) throws NullPointerException { Validator.ensureNotNull(listener); listeners.remove(listener); } /** * {@inheritDoc} */ public FutureResult search(SearchRequest request, ResultHandler resultHandler, SearchResultHandler searchResulthandler) { int messageID = nextMsgID.getAndIncrement(); LDAPSearchFutureResultImpl future = new LDAPSearchFutureResultImpl( messageID, request, resultHandler, searchResulthandler, this); ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); try { synchronized (writeLock) { if (connectionInvalidReason != null) { future.adaptErrorResult(connectionInvalidReason); return future; } if (pendingBindOrStartTLS > 0) { future.setResultOrError(Responses.newResult( ResultCode.OPERATIONS_ERROR).setDiagnosticMessage( "Bind or Start TLS operation in progress")); return future; } pendingRequests.put(messageID, future); try { LDAPEncoder.encodeSearchRequest(asn1Writer, messageID, request); asn1Writer.flush(); } catch (IOException e) { pendingRequests.remove(messageID); // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e); connectionErrorOccurred(errorResult); future.adaptErrorResult(errorResult); } } } finally { connFactory.releaseASN1Writer(asn1Writer); } return future; } /** * Returns the LDAP message handler associated with this connection. * * @return The LDAP message handler associated with this connection. */ LDAPMessageHandler getLDAPMessageHandler() { return handler; } /** * Indicates whether or not TLS is enabled on this connection. * * @return {@code true} if TLS is enabled on this connection, * otherwise {@code false}. */ boolean isTLSEnabled() { FilterChain currentFilterChain = (FilterChain) connection .getProcessor(); return currentFilterChain.get(2) instanceof SSLFilter; } private void close(UnbindRequest unbindRequest, boolean isDisconnectNotification, Result reason) { boolean notifyClose = false; boolean notifyErrorOccurred = false; synchronized (writeLock) { if (isClosed) { // Already closed. return; } if (unbindRequest != null) { // User closed. isClosed = true; notifyClose = true; } else { notifyErrorOccurred = true; } if (connectionInvalidReason != null) { // Already invalid. return; } // Mark the connection as invalid. connectionInvalidReason = reason; } // First abort all outstanding requests. for (AbstractLDAPFutureResultImpl future : pendingRequests .values()) { if (pendingBindOrStartTLS <= 0) { ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); int messageID = nextMsgID.getAndIncrement(); AbandonRequest abandon = Requests.newAbandonRequest(future .getRequestID()); try { LDAPEncoder.encodeAbandonRequest(asn1Writer, messageID, abandon); asn1Writer.flush(); } catch (IOException e) { // Underlying channel probably blown up. Just ignore. } finally { connFactory.releaseASN1Writer(asn1Writer); } } future.adaptErrorResult(reason); } pendingRequests.clear(); // Now try cleanly closing the connection if possible. try { ASN1StreamWriter asn1Writer = connFactory .getASN1Writer(streamWriter); if (unbindRequest == null) { unbindRequest = Requests.newUnbindRequest(); } try { LDAPEncoder.encodeUnbindRequest(asn1Writer, nextMsgID .getAndIncrement(), unbindRequest); asn1Writer.flush(); } finally { connFactory.releaseASN1Writer(asn1Writer); } } catch (IOException e) { // Underlying channel prob blown up. Just ignore. } try { streamWriter.close(); } catch (IOException e) { // Ignore. } try { connection.close(); } catch (IOException e) { // Ignore. } // Notify listeners. if (notifyClose) { // TODO: uncomment if close notification is required. // for (ConnectionEventListener listener : listeners) // { // listener.connectionClosed(this); // } } if (notifyErrorOccurred) { for (ConnectionEventListener listener : listeners) { listener.connectionErrorOccurred(isDisconnectNotification, ErrorResultException.wrap(reason)); } } } private void connectionErrorOccurred(Result reason) { close(null, false, reason); } // TODO uncomment if we decide these methods are useful. /** * {@inheritDoc} */ public boolean isClosed() { return isClosed; } /** * {@inheritDoc} */ public boolean isValid() { return connectionInvalidReason == null && !isClosed; } // // // // /** // * {@inheritDoc} // */ // public boolean isValid(long timeout, TimeUnit unit) // throws InterruptedException, TimeoutException // { // // FIXME: no support for timeout. // return isValid(); // } private StreamWriter getFilterChainStreamWriter() { StreamWriter writer = connection.getStreamWriter(); FilterChain currentFilterChain = (FilterChain) connection .getProcessor(); for (Filter filter : currentFilterChain) { if (filter instanceof StreamTransformerFilter) { writer = ((StreamTransformerFilter) filter) .getStreamWriter(writer); } } return writer; } // Needed in order to expose type information. private void handleExtendedResult0( LDAPExtendedFutureResultImpl future, GenericExtendedResult result) throws DecodeException { R decodedResponse = future.decodeResponse(result.getResultCode(), result.getMatchedDN(), result.getDiagnosticMessage(), result .getResponseName(), result.getResponseValue()); if (future.getRequest() instanceof StartTLSRequest) { if (result.getResultCode() == ResultCode.SUCCESS) { StartTLSRequest request = (StartTLSRequest) future.getRequest(); try { startTLS(request.getSSLContext()); } catch (ErrorResultException e) { future.adaptErrorResult(e.getResult()); return; } } pendingBindOrStartTLS = -1; } future.setResultOrError(decodedResponse); } private void handleIncorrectResponse( AbstractLDAPFutureResultImpl pendingRequest) { // FIXME: I18N need to have a better error message. Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_DECODING_ERROR).setDiagnosticMessage( "LDAP response message did not match request"); pendingRequest.adaptErrorResult(errorResult); connectionErrorOccurred(errorResult); } private void startTLS(SSLContext sslContext) throws ErrorResultException { SSLHandshaker sslHandshaker = connFactory.getSslHandshaker(); SSLFilter sslFilter; SSLEngineConfigurator sslEngineConfigurator; if (sslContext == connFactory.getSSLContext()) { // Use factory SSL objects since it is the same SSLContext sslFilter = connFactory.getSSlFilter(); sslEngineConfigurator = connFactory.getSSlEngineConfigurator(); } else { sslEngineConfigurator = new SSLEngineConfigurator(sslContext, true, false, false); sslFilter = new SSLFilter(sslEngineConfigurator, sslHandshaker); } installFilter(sslFilter); performSSLHandshake(sslHandshaker, sslEngineConfigurator); } void performSSLHandshake(SSLHandshaker sslHandshaker, SSLEngineConfigurator sslEngineConfigurator) throws ErrorResultException { SSLStreamReader reader = new SSLStreamReader(connection .getStreamReader()); SSLStreamWriter writer = new SSLStreamWriter(connection .getStreamWriter()); try { sslHandshaker.handshake(reader, writer, sslEngineConfigurator) .get(); } catch (ExecutionException ee) { // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_CONNECT_ERROR).setCause(ee.getCause()); connectionErrorOccurred(errorResult); throw ErrorResultException.wrap(errorResult); } catch (Exception e) { // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? Result errorResult = Responses.newResult( ResultCode.CLIENT_SIDE_CONNECT_ERROR).setCause(e); connectionErrorOccurred(errorResult); throw ErrorResultException.wrap(errorResult); } } synchronized void installFilter(Filter filter) { if (customFilterChain == null) { customFilterChain = connFactory.getDefaultFilterChainFactory() .create(); connection.setProcessor(customFilterChain); } // Install the SSLFilter in the custom filter chain Filter oldFilter = customFilterChain.remove(customFilterChain .size() - 1); customFilterChain.add(filter); customFilterChain.add(oldFilter); // Update stream writer streamWriter = getFilterChainStreamWriter(); } }