--- a/java/org/apache/catalina/realm/LdapTlsContextFactory.java
+++ a/java/org/apache/catalina/realm/LdapTlsContextFactory.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.realm;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.DirContext;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+import javax.naming.ldap.StartTlsRequest;
+import javax.naming.ldap.StartTlsResponse;
+import javax.naming.spi.InitialContextFactory;
+import javax.net.ssl.SSLSession;
+
+import org.apache.catalina.util.StringManager;
+
+/**
+ * Implements an {@link InitialContextFactory} which will create
+ * {@link LdapContext} instances which have tls enabled.
+ *
+ * Pooling will be disabled as recommended by sun.
+ *
+ * @author Felix Schumacher
+ *
+ */
+public class LdapTlsContextFactory implements InitialContextFactory {
+
+ /**
+ * The string manager for this package.
+ */
+ protected static StringManager sm = StringManager
+ .getManager(Constants.Package);
+
+ /**
+ * Proxies a {@link LdapContext} and handles instantiation and close
+ * specially to start TLS and end it.
+ */
+ private static final class ProxyLdapContext implements InvocationHandler {
+ private final LdapContext delegate;
+ private final StartTlsResponse tls;
+
+ @SuppressWarnings("unchecked")
+ private ProxyLdapContext(Hashtable origEnv) throws NamingException {
+ Hashtable env = new Hashtable(origEnv);
+ /*
+ * We want to have login to happen after TLS was established, so
+ * save credentials for later usage and remove them from env.
+ */
+ Map credentials = new HashMap();
+ for (String key : Arrays.asList(Context.SECURITY_AUTHENTICATION,
+ Context.SECURITY_CREDENTIALS, Context.SECURITY_PRINCIPAL,
+ Context.SECURITY_PROTOCOL)) {
+ Object entry = env.remove(key);
+ if (entry != null) {
+ credentials.put(key, entry);
+ }
+ }
+ delegate = new InitialLdapContext(env, null);
+ tls = (StartTlsResponse) delegate
+ .extendedOperation(new StartTlsRequest());
+ try {
+ SSLSession negotiate = tls.negotiate();
+ Logger.getLogger(this.getClass().getCanonicalName()).fine(
+ sm.getString("LDAP connection protocol is {0}",
+ negotiate.getProtocol()));
+ } catch (IOException e) {
+ throw new NamingException(e.getMessage());
+ }
+ /*
+ * now is the time to reinstate the credentials into env
+ */
+ for (Map.Entry savedEntry : credentials.entrySet()) {
+ delegate.addToEnvironment(savedEntry.getKey(), savedEntry
+ .getValue());
+ }
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ if ("close".equals(method.getName())) {
+ return doClose(delegate);
+ }
+ return method.invoke(delegate, args);
+ }
+
+ /**
+ * Wrapper to the original close method. It will try to end tls before
+ * closing the underlying connection.
+ *
+ * @param delegate
+ * underlying connection
+ * @return always null
+ * @throws InvocationTargetException
+ * if an {@link IOException} or a {@link NamingException}
+ * was catched while closing the connection
+ */
+ private Object doClose(LdapContext delegate)
+ throws InvocationTargetException {
+ try {
+ if (tls != null) {
+ try {
+ tls.close();
+ } catch (IOException e) {
+ throw new InvocationTargetException(e);
+ }
+ }
+ } finally {
+ try {
+ if (delegate != null) {
+ delegate.close();
+ }
+ } catch (NamingException e) {
+ throw new InvocationTargetException(e);
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Environment key under which the JNDI context factory is stored which
+ * shall be used inside the proxy.
+ */
+ public static final String REAL_INITIAL_CONTEXT_FACTORY = "REAL_INITIAL_CONTEXT_FACTORY";
+
+ /**
+ * The JNDI context factory used to acquire our InitialContext. By default,
+ * assumes use of an LDAP server using the standard JNDI LDAP provider.
+ */
+ private static final String DEFAULT_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Context getInitialContext(final Hashtable environment)
+ throws NamingException {
+ final Hashtable proxyEnv = new Hashtable(environment);
+ final Object realFactory;
+ if (environment.contains(REAL_INITIAL_CONTEXT_FACTORY)) {
+ realFactory = environment.get(REAL_INITIAL_CONTEXT_FACTORY);
+ } else {
+ realFactory = DEFAULT_CONTEXT_FACTORY;
+ }
+ proxyEnv.put(Context.INITIAL_CONTEXT_FACTORY, realFactory);
+ proxyEnv.put("com.sun.jndi.ldap.connect.pool", "false");
+ return (Context) Proxy.newProxyInstance(this.getClass()
+ .getClassLoader(), new Class>[] { DirContext.class },
+ new ProxyLdapContext(proxyEnv));
+ }
+
+}
--- a/java/org/apache/catalina/realm/LocalStrings.properties
+++ a/java/org/apache/catalina/realm/LocalStrings.properties
@@ -98,3 +98,4 @@ combinedRealm.addRealm=Add "{0}" realm, making a total of "{1}" realms
combinedRealm.realmStartFail=Failed to start "{0}" realm
lockOutRealm.authLockedUser=An attempt was made to authenticate the locked user "{0}"
lockOutRealm.removeWarning=User "{0}" was removed from the failed users cache after {1} seconds to keep the cache size within the limit set
+tlsContextFactory.protocolInfo=connection protocol is {0}