/* * 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 2014 ForgeRock AS */ package org.forgerock.opendj.grizzly; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.List; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import org.forgerock.opendj.io.LDAPWriter; import org.forgerock.opendj.ldap.IntermediateResponseHandler; import org.forgerock.opendj.ldap.LdapException; import org.forgerock.opendj.ldap.LdapPromise; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.ldap.SSLContextBuilder; import org.forgerock.opendj.ldap.TrustManagers; import org.forgerock.opendj.ldap.requests.AbandonRequest; import org.forgerock.opendj.ldap.requests.BindClient; import org.forgerock.opendj.ldap.requests.BindRequest; import org.forgerock.opendj.ldap.requests.GenericBindRequest; import org.forgerock.opendj.ldap.requests.Request; import org.forgerock.opendj.ldap.requests.UnbindRequest; import org.forgerock.opendj.ldap.responses.ExtendedResult; import org.forgerock.opendj.ldap.responses.Responses; import org.forgerock.opendj.ldap.responses.Result; import org.forgerock.opendj.ldap.spi.AbstractLdapConnectionImpl; import org.forgerock.opendj.ldap.spi.BindResultLdapPromiseImpl; import org.forgerock.opendj.ldap.spi.ResultLdapPromiseImpl; import org.forgerock.util.Reject; import org.glassfish.grizzly.CompletionHandler; import org.glassfish.grizzly.filterchain.Filter; import org.glassfish.grizzly.filterchain.FilterChain; import org.glassfish.grizzly.ssl.SSLEngineConfigurator; import org.glassfish.grizzly.ssl.SSLFilter; import static org.forgerock.opendj.ldap.LdapException.*; import static org.forgerock.opendj.ldap.spi.LdapPromises.*; final class GrizzlyLDAPConnection extends AbstractLdapConnectionImpl { /** * A dummy SSL client engine configurator as SSLFilter only needs client * config. This prevents Grizzly from needlessly using JVM defaults which * may be incorrectly configured. */ private static final SSLEngineConfigurator DUMMY_SSL_ENGINE_CONFIGURATOR; static { try { DUMMY_SSL_ENGINE_CONFIGURATOR = new SSLEngineConfigurator(new SSLContextBuilder().setTrustManager( TrustManagers.distrustAll()).getSSLContext()); } catch (GeneralSecurityException e) { // This should never happen. throw new IllegalStateException("Unable to create Dummy SSL Engine Configurator", e); } } private final org.glassfish.grizzly.Connection connection; @SuppressWarnings("rawtypes") public GrizzlyLDAPConnection(final org.glassfish.grizzly.Connection connection, final GrizzlyLDAPConnectionFactory attachedFactory) { super(attachedFactory); this.connection = connection; } @Override protected LdapPromise abandonAsync0(final int messageID, final AbandonRequest request) { final LDAPWriter writer = GrizzlyUtils.getWriter(); try { writer.writeAbandonRequest(messageID, request); connection.write(writer.getASN1Writer().getBuffer(), null); return newSuccessfulLdapPromise((Void) null, messageID); } catch (final IOException e) { return newFailedLdapPromise(newLdapException(adaptRequestIOException(e))); } finally { GrizzlyUtils.recycleWriter(writer); } } @Override protected void bindAsync0(final int messageID, final BindRequest request, final BindClient bindClient, final IntermediateResponseHandler intermediateResponseHandler) throws LdapException { checkConnectionIsValid(); final LDAPWriter writer = GrizzlyUtils.getWriter(); try { // Use the bind client to get the initial request instead of // using the bind request passed to this method. final GenericBindRequest initialRequest = bindClient.nextBindRequest(); writer.writeBindRequest(messageID, 3, initialRequest); connection.write(writer.getASN1Writer().getBuffer(), null); } catch (final IOException e) { throw newLdapException(adaptRequestIOException(e)); } finally { GrizzlyUtils.recycleWriter(writer); } } @Override protected void close0(final int messageID, UnbindRequest unbindRequest, String reason) { Reject.ifNull(unbindRequest); // This is the final client initiated close then release the connection // and release resources. final LDAPWriter writer = GrizzlyUtils.getWriter(); try { writer.writeUnbindRequest(messageID, unbindRequest); connection.write(writer.getASN1Writer().getBuffer(), null); } catch (final Exception ignore) { /* * Underlying channel probably blown up. Ignore all errors, * including possibly runtime exceptions (see OPENDJ-672). */ } finally { GrizzlyUtils.recycleWriter(writer); } getFactory().getTimeoutChecker().removeListener(this); connection.closeSilently(); } @Override protected void writeRequest(final int messageID, final R request, final IntermediateResponseHandler intermediateResponseHandler, final RequestWriter requestWriter) { final LDAPWriter writer = GrizzlyUtils.getWriter(); try { requestWriter.writeRequest(messageID, writer, request); connection.write(writer.getASN1Writer().getBuffer(), null); } catch (final IOException e) { removePendingResult(messageID).setResultOrError(adaptRequestIOException(e)); } finally { GrizzlyUtils.recycleWriter(writer); } } /** * Installs a new Grizzly filter (e.g. SSL/SASL) beneath the top-level LDAP * filter. * * @param filter * The filter to be installed. */ void installFilter(final Filter filter) { synchronized (state) { GrizzlyUtils.addFilterToConnection(filter, connection); } } @Override protected boolean isTLSEnabled() { synchronized (state) { final FilterChain currentFilterChain = (FilterChain) connection.getProcessor(); for (final Filter filter : currentFilterChain) { if (filter instanceof SSLFilter) { return true; } } return false; } } void startTLS(final SSLContext sslContext, final List protocols, final List cipherSuites, final CompletionHandler completionHandler) throws IOException { synchronized (state) { if (isTLSEnabled()) { throw new IllegalStateException("TLS already enabled"); } final SSLEngineConfigurator sslEngineConfigurator = new SSLEngineConfigurator(sslContext, true, false, false); sslEngineConfigurator.setEnabledProtocols(protocols.isEmpty() ? null : protocols .toArray(new String[protocols.size()])); sslEngineConfigurator.setEnabledCipherSuites(cipherSuites.isEmpty() ? null : cipherSuites .toArray(new String[cipherSuites.size()])); final SSLFilter sslFilter = new SSLFilter(DUMMY_SSL_ENGINE_CONFIGURATOR, sslEngineConfigurator); installFilter(sslFilter); sslFilter.handshake(connection, completionHandler); } } private Result adaptRequestIOException(final IOException e) { // FIXME: what other sort of IOExceptions can be thrown? // FIXME: Is this the best result code? final Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_ENCODING_ERROR).setCause(e); connectionErrorOccurred(false, errorResult); return errorResult; } @Override protected ResultLdapPromiseImpl getPendingResult(int messageID) { return super.getPendingResult(messageID); }; @Override protected ResultLdapPromiseImpl removePendingResult( final Integer messageID) { return super.removePendingResult(messageID); } @Override protected void connectionErrorOccurred(boolean isDisconnectNotification, Result reason) { super.connectionErrorOccurred(isDisconnectNotification, reason); } @Override protected void handleUnsolicitedNotification(final ExtendedResult notification) { super.handleUnsolicitedNotification(notification); } int continuePendingBindRequest(BindResultLdapPromiseImpl promise) throws LdapException { final int newMsgID = newMessageID(); checkConnectionIsValid(); addPendingResult(newMsgID, promise); return newMsgID; } }