/*
* 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 2008-2010 Sun Microsystems, Inc.
* Portions Copyright 2013-2015 ForgeRock AS.
*/
package org.opends.admin.ads.util;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.naming.AuthenticationException;
import javax.naming.NamingException;
import javax.naming.NoPermissionException;
import javax.naming.TimeLimitExceededException;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapName;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.admin.ads.ADSContext;
import org.opends.admin.ads.ADSContext.ServerProperty;
import org.opends.admin.ads.ServerDescriptor;
import org.opends.admin.ads.TopologyCacheException;
import org.opends.admin.ads.TopologyCacheException.Type;
import org.opends.admin.ads.TopologyCacheFilter;
import com.forgerock.opendj.cli.Utils;
import static org.opends.server.util.StaticUtils.*;
/**
* Class used to load the configuration of a server. Basically the code
* uses some provided properties and authentication information to connect
* to the server and then generate a ServerDescriptor object based on the
* read configuration.
*/
public class ServerLoader extends Thread
{
private Map<ServerProperty,Object> serverProperties;
private boolean isOver;
private boolean isInterrupted;
private String lastLdapUrl;
private TopologyCacheException lastException;
private ServerDescriptor serverDescriptor;
private ApplicationTrustManager trustManager;
private int timeout;
private String dn;
private String pwd;
private final LinkedHashSet<PreferredConnection> preferredLDAPURLs;
private TopologyCacheFilter filter;
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/**
* Constructor.
* @param serverProperties the server properties of the server we want to
* load.
* @param dn the DN that we must use to bind to the server.
* @param pwd the password that we must use to bind to the server.
* @param trustManager the ApplicationTrustManager to be used when we try
* to connect to the server.
* @param timeout the timeout to establish the connection in milliseconds.
* Use {@code 0} to express no timeout.
* @param preferredLDAPURLs the list of preferred LDAP URLs that we want
* to use to connect to the server. They will be used only if they correspond
* to the URLs that we found in the the server properties.
* @param filter the topology cache filter to be used. This can be used not
* to retrieve all the information.
*/
public ServerLoader(Map<ServerProperty,Object> serverProperties,
String dn, String pwd, ApplicationTrustManager trustManager,
int timeout,
Set<PreferredConnection> preferredLDAPURLs,
TopologyCacheFilter filter)
{
this.serverProperties = serverProperties;
this.dn = dn;
this.pwd = pwd;
this.trustManager = trustManager;
this.timeout = timeout;
this.preferredLDAPURLs = new LinkedHashSet<>(preferredLDAPURLs);
this.filter = filter;
}
/**
* Returns the ServerDescriptor that could be retrieved.
* @return the ServerDescriptor that could be retrieved.
*/
public ServerDescriptor getServerDescriptor()
{
if (serverDescriptor == null)
{
serverDescriptor = ServerDescriptor.createStandalone(serverProperties);
}
serverDescriptor.setLastException(lastException);
return serverDescriptor;
}
/**
* Returns the last exception that occurred while trying to generate
* the ServerDescriptor object.
* @return the last exception that occurred while trying to generate
* the ServerDescriptor object.
*/
public TopologyCacheException getLastException()
{
return lastException;
}
/** {@inheritDoc} */
@Override
public void interrupt()
{
if (!isOver)
{
isInterrupted = true;
String ldapUrl = getLastLdapUrl();
if (ldapUrl == null)
{
LinkedHashSet<PreferredConnection> urls = getLDAPURLsByPreference();
if (!urls.isEmpty())
{
ldapUrl = urls.iterator().next().getLDAPURL();
}
}
lastException = new TopologyCacheException(
TopologyCacheException.Type.TIMEOUT,
new TimeLimitExceededException("Timeout reading server: "+ldapUrl),
trustManager, ldapUrl);
logger.warn(LocalizableMessage.raw("Timeout reading server: "+ldapUrl));
}
super.interrupt();
}
/**
* The method where we try to generate the ServerDescriptor object.
*/
@Override
public void run()
{
lastException = null;
InitialLdapContext ctx = null;
try
{
ctx = createContext();
serverDescriptor = ServerDescriptor.createStandalone(ctx, filter);
serverDescriptor.setAdsProperties(serverProperties);
serverDescriptor.updateAdsPropertiesWithServerProperties();
}
catch (NoPermissionException npe)
{
logger.warn(LocalizableMessage.raw(
"Permissions error reading server: "+getLastLdapUrl(), npe));
if (!isAdministratorDn())
{
lastException = new TopologyCacheException(
TopologyCacheException.Type.NOT_GLOBAL_ADMINISTRATOR, npe,
trustManager, getLastLdapUrl());
}
else
{
lastException =
new TopologyCacheException(
TopologyCacheException.Type.NO_PERMISSIONS, npe,
trustManager, getLastLdapUrl());
}
}
catch (AuthenticationException ae)
{
logger.warn(LocalizableMessage.raw(
"Authentication exception: "+getLastLdapUrl(), ae));
if (!isAdministratorDn())
{
lastException = new TopologyCacheException(
TopologyCacheException.Type.NOT_GLOBAL_ADMINISTRATOR, ae,
trustManager, getLastLdapUrl());
}
else
{
lastException =
new TopologyCacheException(
TopologyCacheException.Type.GENERIC_READING_SERVER, ae,
trustManager, getLastLdapUrl());
}
}
catch (NamingException ne)
{
logger.warn(LocalizableMessage.raw(
"NamingException error reading server: "+getLastLdapUrl(), ne));
Type type = ctx == null
? TopologyCacheException.Type.GENERIC_CREATING_CONNECTION
: TopologyCacheException.Type.GENERIC_READING_SERVER;
lastException = new TopologyCacheException(
type, ne, trustManager, getLastLdapUrl());
}
catch (Throwable t)
{
if (!isInterrupted)
{
logger.warn(LocalizableMessage.raw(
"Generic error reading server: "+getLastLdapUrl(), t));
logger.warn(LocalizableMessage.raw("server Properties: "+serverProperties));
lastException =
new TopologyCacheException(TopologyCacheException.Type.BUG, t);
}
}
finally
{
isOver = true;
close(ctx);
}
}
/**
* Create an InitialLdapContext based in the provide server properties and
* authentication data provided in the constructor.
* @return an InitialLdapContext based in the provide server properties and
* authentication data provided in the constructor.
* @throws NamingException if an error occurred while creating the
* InitialLdapContext.
*/
public InitialLdapContext createContext() throws NamingException
{
InitialLdapContext ctx = null;
if (trustManager != null)
{
trustManager.resetLastRefusedItems();
String host = (String)serverProperties.get(ServerProperty.HOST_NAME);
trustManager.setHost(host);
}
/* Try to connect to the server in a certain order of preference. If an
* URL fails, we will try with the others.
*/
LinkedHashSet<PreferredConnection> conns = getLDAPURLsByPreference();
for (PreferredConnection connection : conns)
{
if (ctx == null)
{
lastLdapUrl = connection.getLDAPURL();
switch (connection.getType())
{
case LDAPS:
ctx = ConnectionUtils.createLdapsContext(lastLdapUrl, dn, pwd,
timeout, null, trustManager,
null);
break;
case START_TLS:
ctx = ConnectionUtils.createStartTLSContext(lastLdapUrl, dn, pwd,
timeout, null, trustManager,
null, null);
break;
default:
ctx = ConnectionUtils.createLdapContext(lastLdapUrl, dn, pwd,
timeout, null);
}
}
}
return ctx;
}
/**
* Returns the last LDAP URL to which we tried to connect.
* @return the last LDAP URL to which we tried to connect.
*/
private String getLastLdapUrl()
{
return lastLdapUrl;
}
/**
* Returns the non-secure LDAP URL for the given server properties. It
* returns NULL if according to the server properties no non-secure LDAP URL
* can be generated (LDAP disabled or port not defined).
* @param serverProperties the server properties to be used to generate
* the non-secure LDAP URL.
* @return the non-secure LDAP URL for the given server properties.
*/
private String getLdapUrl(Map<ServerProperty,Object> serverProperties)
{
if (isLdapEnabled(serverProperties))
{
return "ldap://" + getHostNameForLdapUrl(serverProperties) + ":"
+ serverProperties.get(ServerProperty.LDAP_PORT);
}
return null;
}
/**
* Returns the StartTLS LDAP URL for the given server properties. It
* returns NULL if according to the server properties no StartTLS LDAP URL
* can be generated (StartTLS disabled or port not defined).
* @param serverProperties the server properties to be used to generate
* the StartTLS LDAP URL.
* @return the StartTLS LDAP URL for the given server properties.
*/
private String getStartTlsLdapUrl(Map<ServerProperty,Object> serverProperties)
{
if (isLdapEnabled(serverProperties) && isStartTlsEnabled(serverProperties))
{
return "ldap://" + getHostNameForLdapUrl(serverProperties) + ":"
+ serverProperties.get(ServerProperty.LDAP_PORT);
}
return null;
}
/**
* Returns the LDAPs URL for the given server properties. It
* returns NULL if according to the server properties no LDAPS URL
* can be generated (LDAPS disabled or port not defined).
* @param serverProperties the server properties to be used to generate
* the LDAPS URL.
* @return the LDAPS URL for the given server properties.
*/
private String getLdapsUrl(Map<ServerProperty,Object> serverProperties)
{
boolean ldapsEnabled = isLdapsEnabled(serverProperties);
if (ldapsEnabled)
{
return "ldaps://" + getHostNameForLdapUrl(serverProperties) + ":"
+ serverProperties.get(ServerProperty.LDAPS_PORT);
}
return null;
}
/**
* Returns the administration connector URL for the given server properties.
* It returns NULL if according to the server properties no administration
* connector URL can be generated.
* @param serverProperties the server properties to be used to generate
* the administration connector URL.
* @return the administration connector URL for the given server properties.
*/
private String getAdminConnectorUrl(
Map<ServerProperty,Object> serverProperties)
{
boolean portDefined;
if (isPropertyEnabled(serverProperties, ServerProperty.ADMIN_ENABLED))
{
Object v = serverProperties.get(ServerProperty.ADMIN_PORT);
portDefined = v != null;
}
else
{
portDefined = false;
}
if (portDefined)
{
return "ldaps://" + getHostNameForLdapUrl(serverProperties) + ":"
+ serverProperties.get(ServerProperty.ADMIN_PORT);
}
return null;
}
private boolean isLdapEnabled(Map<ServerProperty, Object> serverProperties)
{
return isPropertyEnabled(serverProperties, ServerProperty.LDAP_ENABLED);
}
private boolean isLdapsEnabled(Map<ServerProperty, Object> serverProperties)
{
return isPropertyEnabled(serverProperties, ServerProperty.LDAPS_ENABLED);
}
private boolean isStartTlsEnabled(Map<ServerProperty, Object> serverProperties)
{
return isPropertyEnabled(serverProperties, ServerProperty.STARTTLS_ENABLED);
}
private boolean isPropertyEnabled(Map<ServerProperty, Object> serverProperties, ServerProperty property)
{
Object v = serverProperties.get(property);
return v != null && "true".equalsIgnoreCase(v.toString());
}
/**
* Returns the host name to be used to generate an LDAP URL based on the
* contents of the provided server properties.
* @param serverProperties the server properties.
* @return the host name to be used to generate an LDAP URL based on the
* contents of the provided server properties.
*/
private String getHostNameForLdapUrl(
Map<ServerProperty,Object> serverProperties)
{
String host = (String)serverProperties.get(ServerProperty.HOST_NAME);
return Utils.getHostNameForLdapUrl(host);
}
/**
* Returns whether the DN provided in the constructor is a Global
* Administrator DN or not.
* @return <CODE>true</CODE> if the DN provided in the constructor is a Global
* Administrator DN and <CODE>false</CODE> otherwise.
*/
private boolean isAdministratorDn()
{
try
{
LdapName theDn = new LdapName(dn);
LdapName containerDn =
new LdapName(ADSContext.getAdministratorContainerDN());
return theDn.startsWith(containerDn);
}
catch (Throwable t)
{
logger.warn(LocalizableMessage.raw("Error parsing authentication DNs.", t));
}
return false;
}
/**
* Returns the list of LDAP URLs that can be used to connect to the server.
* They are ordered so that the first URL is the preferred URL to be used.
* @return the list of LDAP URLs that can be used to connect to the server.
* They are ordered so that the first URL is the preferred URL to be used.
*/
private LinkedHashSet<PreferredConnection> getLDAPURLsByPreference()
{
LinkedHashSet<PreferredConnection> ldapUrls = new LinkedHashSet<>();
String adminConnectorUrl = getAdminConnectorUrl(serverProperties);
String ldapsUrl = getLdapsUrl(serverProperties);
String startTLSUrl = getStartTlsLdapUrl(serverProperties);
String ldapUrl = getLdapUrl(serverProperties);
// Check the preferred connections passed in the constructor.
for (PreferredConnection connection : preferredLDAPURLs)
{
String url = connection.getLDAPURL();
if (url.equalsIgnoreCase(adminConnectorUrl))
{
ldapUrls.add(connection);
}
else if (url.equalsIgnoreCase(ldapsUrl) &&
connection.getType() == PreferredConnection.Type.LDAPS)
{
ldapUrls.add(connection);
}
else if (url.equalsIgnoreCase(startTLSUrl) &&
connection.getType() == PreferredConnection.Type.START_TLS)
{
ldapUrls.add(connection);
}
else if (url.equalsIgnoreCase(ldapUrl) &&
connection.getType() == PreferredConnection.Type.LDAP)
{
ldapUrls.add(connection);
}
}
if (adminConnectorUrl != null)
{
ldapUrls.add(
new PreferredConnection(adminConnectorUrl,
PreferredConnection.Type.LDAPS));
}
if (ldapsUrl != null)
{
ldapUrls.add(
new PreferredConnection(ldapsUrl, PreferredConnection.Type.LDAPS));
}
if (startTLSUrl != null)
{
ldapUrls.add(new PreferredConnection(startTLSUrl,
PreferredConnection.Type.START_TLS));
}
if (ldapUrl != null)
{
ldapUrls.add(new PreferredConnection(ldapUrl,
PreferredConnection.Type.LDAP));
}
return ldapUrls;
}
}