/* See LICENSE for licensing and NOTICE for copyright. */ package org.ldaptive.provider.jndi; import java.security.GeneralSecurityException; import java.util.HashMap; import java.util.Map; import javax.net.ssl.SSLSocketFactory; import org.ldaptive.ConnectionConfig; import org.ldaptive.LdapURL; import org.ldaptive.provider.Provider; import org.ldaptive.provider.ProviderConnectionFactory; import org.ldaptive.ssl.TLSSocketFactory; import org.ldaptive.ssl.ThreadLocalTLSSocketFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Exposes a connection factory for creating connections with JNDI. * * @author Middleware Services */ public class JndiProvider implements Provider<JndiProviderConfig> { /** * The value of this property is a fully qualified class name of the factory class which creates the initial context * for the LDAP service provider. The value of this constant is {@value}. */ public static final String CONTEXT_FACTORY = "java.naming.factory.initial"; /** * The value of this property is a string that specifies the protocol version for the provider. The value of this * constant is {@value}. */ public static final String VERSION = "java.naming.ldap.version"; /** * The value of this property is a URL string that specifies the hostname and port number of the LDAP server, and the * root distinguished name of the naming context to use. The value of this constant is {@value}. */ public static final String PROVIDER_URL = "java.naming.provider.url"; /** * The value of this property is a string that specifies the security protocol for the provider to use. The value of * this constant is {@value}. */ public static final String PROTOCOL = "java.naming.security.protocol"; /** * The value of this property is a string identifying the class name of a socket factory. The value of this constant * is {@value}. */ public static final String SOCKET_FACTORY = "java.naming.ldap.factory.socket"; /** * The value of this property is a string that specifies the time in milliseconds that a connection attempt will abort * if the connection cannot be made. The value of this constant is {@value}. */ public static final String CONNECT_TIMEOUT = "com.sun.jndi.ldap.connect.timeout"; /** * The value of this property is a string that specifies the time in milliseconds that an operation will abort if a * response is not received. The value of this constant is {@value}. */ public static final String READ_TIMEOUT = "com.sun.jndi.ldap.read.timeout"; /** * The value of this property is a java.io.OutputStream object into which a hexadecimal dump of the incoming and * outgoing LDAP ASN.1 BER packets is written. The value of this constant is {@value}. */ public static final String TRACE = "com.sun.jndi.ldap.trace.ber"; /** Logger for this class. */ protected final Logger logger = LoggerFactory.getLogger(getClass()); /** Provider configuration. */ private JndiProviderConfig config = new JndiProviderConfig(); @Override public ProviderConnectionFactory<JndiProviderConfig> getConnectionFactory(final ConnectionConfig cc) { ProviderConnectionFactory<JndiProviderConfig> cf; if (cc.getUseStartTLS()) { cf = getJndiStartTLSConnectionFactory(cc, config.getEnvironment()); } else { cf = getJndiConnectionFactory(cc, config.getEnvironment()); } return cf; } @Override public JndiProviderConfig getProviderConfig() { return config; } @Override public void setProviderConfig(final JndiProviderConfig jpc) { config = jpc; } @Override public JndiProvider newInstance() { return new JndiProvider(); } /** * Returns a jndi startTLS connection factory using the properties found in the supplied connection config. If the * supplied env is null, the environment is retrieved from {@link #getDefaultEnvironment(ConnectionConfig, String)}. * * @param cc connection config * @param env context environment or null to use the default * * @return jndi startTLS connection factory */ protected JndiStartTLSConnectionFactory getJndiStartTLSConnectionFactory( final ConnectionConfig cc, final Map<String, Object> env) { // hostname verification always occurs for startTLS after the handshake SSLSocketFactory factory = config.getSslSocketFactory(); if (factory == null && cc.getSslConfig() != null && !cc.getSslConfig().isEmpty()) { final TLSSocketFactory sf = new TLSSocketFactory(); sf.setSslConfig(cc.getSslConfig()); try { sf.initialize(); } catch (GeneralSecurityException e) { throw new IllegalArgumentException(e); } factory = sf; } return new JndiStartTLSConnectionFactory( cc.getLdapUrl(), cc.getConnectionStrategy(), config, env != null ? env : getDefaultEnvironment(cc, null), config.getClassLoader(), factory, config.getHostnameVerifier()); } /** * Returns a jndi connection factory using the properties found in the supplied connection config. If the supplied env * is null, the environment is retrieved from {@link #getDefaultEnvironment(ConnectionConfig, String)}. * * @param cc connection config * @param env context environment or null to use the default * * @return jndi connection factory */ protected JndiConnectionFactory getJndiConnectionFactory(final ConnectionConfig cc, final Map<String, Object> env) { SSLSocketFactory factory = config.getSslSocketFactory(); if (factory == null && (cc.getUseSSL() || cc.getLdapUrl().toLowerCase().contains("ldaps://"))) { // LDAPS hostname verification does not occur by default // set a default hostname verifier final LdapURL ldapUrl = new LdapURL(cc.getLdapUrl()); factory = ThreadLocalTLSSocketFactory.getHostnameVerifierFactory(cc.getSslConfig(), ldapUrl.getHostnames()); } ClassLoader classLoader = config.getClassLoader(); if (classLoader == null && factory != null) { try { Thread.currentThread().getContextClassLoader().loadClass(factory.getClass().getName()); } catch (ClassNotFoundException e) { classLoader = factory.getClass().getClassLoader(); logger.debug("Could not find {}, using class loader {}", factory.getClass(), classLoader); } } return new JndiConnectionFactory( cc.getLdapUrl(), cc.getConnectionStrategy(), config, env != null ? env : getDefaultEnvironment(cc, factory != null ? factory.getClass().getName() : null), classLoader); } /** * Returns the configuration environment for a JNDI ldap context using the properties found in the supplied connection * config. * * @param cc connection config * @param factory class name of the socket factory to use for LDAPS * * @return JNDI ldap context environment */ protected Map<String, Object> getDefaultEnvironment(final ConnectionConfig cc, final String factory) { final Map<String, Object> env = new HashMap<>(); env.put(CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(VERSION, "3"); if (cc.getUseSSL()) { env.put(PROTOCOL, "ssl"); } if (factory != null && (cc.getUseSSL() || cc.getLdapUrl().toLowerCase().contains("ldaps://"))) { env.put(JndiProvider.SOCKET_FACTORY, factory); } if (cc.getConnectTimeout() != null) { env.put(CONNECT_TIMEOUT, Long.toString(cc.getConnectTimeout().toMillis())); } if (cc.getResponseTimeout() != null) { env.put(READ_TIMEOUT, Long.toString(cc.getResponseTimeout().toMillis())); } if (config.getTracePackets() != null) { env.put(JndiProvider.TRACE, config.getTracePackets()); } if (!config.getProperties().isEmpty()) { for (Map.Entry<String, Object> entry : config.getProperties().entrySet()) { env.put(entry.getKey(), entry.getValue()); } } return env; } }