Summary
-------
Define a DNS provider service interface which can be used by
com.sun.jndi.LdapCtx to resolve the physical LDAP servers for a given domain.
A library or application can provide an implementation of this service in
order to route ldap requests to individual servers which don't resolve
correctly via traditional SRV lookups.
Problem
-------
The current resolution system does not offer enough flexibility to take certain
LDAP DNS configurations into account.
For example _ldap._tcp cannot be expected to point to an LDAPS endpoint,
RFC 2782 doesn't discuss the use of SSL, and there are various approaches to
its configuration. This means applications may need to tailor their server
resolution on a per domain basis.
Solution
-------
Add a new service (com.sun.jndi.ldap.LdapDnsProviderService) which can load
implementations of a new abstract class (javax.naming.spi.ldap.LdapDnsProvider)
via the ServiceLoader if the application has the (new) "ldapDnsProvider"
RuntimePermission or if no SecurityManager is installed. If no LdapDnsProvider
implementation is provided (or the provided implementations fail to resolve
an endpoint), a default provider which mimics current behaviour is used.
Specification
-------
This change is in the java.naming module. The javax.naming module will add an export for javax.naming.spi.ldap.
src/java.naming/share/classes/javax/naming/directory/InitialDirContext.java
[changing]
```
/**
* Constructs an initial DirContext using the supplied environment.
* Environment properties are discussed in the
* {@code javax.naming.InitialContext} class description.
*
+ * <p> If the {@code java.naming.provider.url} property of the supplied
+ * environment consists of a URL (or a list of URLs) using the ldap
+ * protocol the resulting {@link javax.naming.ldap.LdapContext} will use
+ * an LDAP server resolved by the configured {@link
+ * javax.naming.ldap.spi.LdapDnsProvider LdapDnsProviders}:
+ * <ol>
+ * <li>If this is the first {@code InitialDirContext} created with a
+ * {@code java.naming.provider.url} using the ldap protocol then the
+ * {@linkplain java.util.ServiceLoader ServiceLoader} mechanism is
+ * used to locate {@linkplain javax.naming.ldap.spi.LdapDnsProvider
+ * LdapDnsProvider} implementations using the system class loader.
+ * The order that providers are located is implementation specific
+ * and an implementation is free to cache the located providers.
+ * <li>The {@code lookupEndpoints} method of each provider, if instantiated,
+ * is invoked once with a combination of each of the URLs in the the
+ * {@code java.naming.provider.url} property and the environment until
+ * a provider returns non-empty or all providers have been exhausted.
+ * If none of the
+ * {@linkplain javax.naming.ldap.spi.LdapDnsProvider LdapDnsProviders}
+ * return a non-empty
+ * {@linkplain javax.naming.ldap.spi.LdapDnsProviderResult result} then
+ * the implementation will make a best-effort attempt to determine an
+ * endpoint. A
+ * {@linkplain java.util.ServiceConfigurationError ServiceConfigurationError},
+ * {@code Error} or {@code RuntimeException} thrown when loading or
+ * calling an {@linkplain javax.naming.ldap.spi.LdapDnsProvider
+ * LdapDnsProvider}, if encountered, will be propagated to the calling
+ * thread.
+ * </ol>
+ *
* <p> This constructor will not modify {@code environment}
* or save a reference to it, but may save a clone.
* Caller should not modify mutable keys and values in
* {@code environment} after it has been passed to the constructor.
*
* @param environment
* environment used to create the initial DirContext.
* Null indicates an empty environment.
*
* @throws NamingException if a naming exception is encountered
*/
public InitialDirContext(Hashtable<?,?> environment)
throws NamingException
{
super(environment);
}
```
src/java.naming/share/classes/javax/naming/spi/ldap/LdapDnsProvider.java
[adding]
```
/**
* Service-provider class for DNS lookups when performing LDAP operations.
*
* <p> An LDAP DNS provider is a concrete subclass of this class that
* has a zero-argument constructor. LDAP DNS providers are located using the
* ServiceLoader facility, as specified by
* {@linkplain javax.naming.directory.InitialDirContext InitialDirectContext}.
*
* The
* {@link java.util.ServiceLoader ServiceLoader} is used to create and register
* implementations of {@code LdapDnsProvider}.
*
* <p> An LDAP DNS provider can be used in environments where the default
* DNS resolution mechanism is not sufficient to accurately pinpoint the
* correct LDAP servers needed to perform LDAP operations. For example, in an
* environment containing a mix of {@code ldap} and {@code ldaps} servers
* you may want the {@linkplain javax.naming.ldap.LdapContext LdapContext}
* to query {@code ldaps} servers only.
*
* @since 12
*/
public abstract class LdapDnsProvider {
// The {@code RuntimePermission("ldapDnsProvider")} is
// necessary to subclass and instantiate the {@code LdapDnsProvider} class.
private static final RuntimePermission DNSPROVIDER_PERMISSION =
new RuntimePermission("ldapDnsProvider");
/**
* Creates a new instance of {@code LdapDnsProvider}.
*
* @throws SecurityException if a security manager is present and its
* {@code checkPermission} method doesn't allow
* the {@code RuntimePermission("ldapDnsProvider")}.
*/
protected LdapDnsProvider() {
this(checkPermission());
}
private LdapDnsProvider(Void unused) {
// nothing to do.
}
private static Void checkPermission() {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(DNSPROVIDER_PERMISSION);
}
return null;
}
/**
* Lookup the endpoints and domain name for the given {@link Context}
* {@link Context#PROVIDER_URL provider URL} and environment. The resolved
* endpoints and domain name are returned as an
* {@link LdapDnsProviderResult}.
*
* <p> An endpoint is a {@code String} representation of an LDAP URL which
* points to an LDAP server to be used for LDAP operations. The syntax of
* an LDAP URL is defined by <a href="http://www.ietf.org/rfc/rfc2255.txt">
* <i>RFC 2255: The LDAP URL Format</i></a>.
*
* @param url The {@link Context} {@link Context#PROVIDER_URL provider URL}
* @param env The {@link Context} environment.
*
* @return an {@link LdapDnsProviderResult} or empty {@code Optional}
* if the lookup fails.
*
* @throws NamingException if the {@code url} is not valid or an error
* occurred while performing the lookup.
* @throws NullPointerException if either {@code url} or {@code env} are
* {@code null}.
*/
public abstract Optional<LdapDnsProviderResult> lookupEndpoints(
String url, Hashtable<?,?> env) throws NamingException;
}
```
src/java.naming/share/classes/javax/naming/spi/ldap/LdapDnsProviderResult.java
[adding]
```
/**
* The result of a DNS lookup for an LDAP URL.
*
* <p> This class is used by an {@link LdapDnsProvider} to return the result
* of a DNS lookup for a given LDAP URL. The result consists of a domain name
* and its associated ldap server endpoints.
*
* <p> A {@code null} {@code domainName} is equivalent to and represented
* by an empty string.
*
* @since 12
*/
public final class LdapDnsProviderResult {
private final String domainName;
private final List<String> endpoints;
/**
* Construct an LdapDnsProviderResult consisting of a resolved domain name
* and the ldap server endpoints that serve the domain.
*
* @param domainName the resolved domain name; can be null.
* @param endpoints the possibly empty list of resolved ldap server
* endpoints
*
* @throws NullPointerException if {@code endpoints} contains {@code null}
* elements.
* @throws ClassCastException if {@code endpoints} contains non-
* {@code String} elements.
*/
public LdapDnsProviderResult(String domainName, List<String> endpoints) {
this.domainName = (domainName == null) ? "" : domainName;
this.endpoints = List.copyOf(endpoints);
}
/**
* Returns the domain name resolved from the ldap URL. This method returns
* the empty string if the {@code LdapDnsProviderResult} is created with a
* null domain name.
*
* @return the resolved domain name
*/
public String getDomainName() {
return domainName;
}
/**
* Returns the possibly empty list of individual server endpoints resolved
* from the ldap URL.
*
* @return a possibly empty unmodifiable {@link List} containing the
* resolved ldap server endpoints
*/
public List<String> getEndpoints() {
return endpoints;
}
}
```