001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.admin.ads.util;
018
019import java.util.LinkedHashSet;
020import java.util.Map;
021import java.util.Set;
022
023import javax.naming.AuthenticationException;
024import javax.naming.NamingException;
025import javax.naming.NoPermissionException;
026import javax.naming.TimeLimitExceededException;
027import javax.naming.ldap.LdapName;
028
029import org.forgerock.i18n.LocalizableMessage;
030import org.forgerock.i18n.slf4j.LocalizedLogger;
031import org.opends.admin.ads.ADSContext;
032import org.opends.admin.ads.ADSContext.ServerProperty;
033import org.opends.admin.ads.ServerDescriptor;
034import org.opends.admin.ads.TopologyCacheException;
035import org.opends.admin.ads.TopologyCacheException.Type;
036import org.opends.admin.ads.TopologyCacheFilter;
037
038import com.forgerock.opendj.cli.Utils;
039
040/**
041 * Class used to load the configuration of a server.  Basically the code
042 * uses some provided properties and authentication information to connect
043 * to the server and then generate a ServerDescriptor object based on the
044 * read configuration.
045 */
046public class ServerLoader extends Thread
047{
048  private final Map<ServerProperty, Object> serverProperties;
049  private boolean isOver;
050  private boolean isInterrupted;
051  private String lastLdapUrl;
052  private TopologyCacheException lastException;
053  private ServerDescriptor serverDescriptor;
054  private final ApplicationTrustManager trustManager;
055  private final int timeout;
056  private final String dn;
057  private final String pwd;
058  private final LinkedHashSet<PreferredConnection> preferredLDAPURLs;
059  private final TopologyCacheFilter filter;
060
061  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
062
063  /**
064   * Constructor.
065   * @param serverProperties the server properties of the server we want to
066   * load.
067   * @param dn the DN that we must use to bind to the server.
068   * @param pwd the password that we must use to bind to the server.
069   * @param trustManager the ApplicationTrustManager to be used when we try
070   * to connect to the server.
071   * @param timeout the timeout to establish the connection in milliseconds.
072   * Use {@code 0} to express no timeout.
073   * @param preferredLDAPURLs the list of preferred LDAP URLs that we want
074   * to use to connect to the server.  They will be used only if they correspond
075   * to the URLs that we found in the the server properties.
076   * @param filter the topology cache filter to be used.  This can be used not
077   * to retrieve all the information.
078   */
079  public ServerLoader(Map<ServerProperty,Object> serverProperties,
080      String dn, String pwd, ApplicationTrustManager trustManager,
081      int timeout,
082      Set<PreferredConnection> preferredLDAPURLs,
083      TopologyCacheFilter filter)
084  {
085    this.serverProperties = serverProperties;
086    this.dn = dn;
087    this.pwd = pwd;
088    this.trustManager = trustManager;
089    this.timeout = timeout;
090    this.preferredLDAPURLs = new LinkedHashSet<>(preferredLDAPURLs);
091    this.filter = filter;
092  }
093
094  /**
095   * Returns the ServerDescriptor that could be retrieved.
096   * @return the ServerDescriptor that could be retrieved.
097   */
098  public ServerDescriptor getServerDescriptor()
099  {
100    if (serverDescriptor == null)
101    {
102      serverDescriptor = ServerDescriptor.createStandalone(serverProperties);
103    }
104    serverDescriptor.setLastException(lastException);
105    return serverDescriptor;
106  }
107
108  /**
109   * Returns the last exception that occurred while trying to generate
110   * the ServerDescriptor object.
111   * @return the last exception that occurred while trying to generate
112   * the ServerDescriptor object.
113   */
114  public TopologyCacheException getLastException()
115  {
116    return lastException;
117  }
118
119  @Override
120  public void interrupt()
121  {
122    if (!isOver)
123    {
124      isInterrupted = true;
125      String ldapUrl = lastLdapUrl;
126      if (ldapUrl == null)
127      {
128        LinkedHashSet<PreferredConnection> urls = getLDAPURLsByPreference();
129        if (!urls.isEmpty())
130        {
131          ldapUrl = urls.iterator().next().getLDAPURL();
132        }
133      }
134      lastException = new TopologyCacheException(
135          TopologyCacheException.Type.TIMEOUT,
136          new TimeLimitExceededException("Timeout reading server: "+ldapUrl),
137          trustManager, ldapUrl);
138      logger.warn(LocalizableMessage.raw("Timeout reading server: "+ldapUrl));
139    }
140    super.interrupt();
141  }
142
143  /** The method where we try to generate the ServerDescriptor object. */
144  @Override
145  public void run()
146  {
147    lastException = null;
148    boolean connCreated = false;
149    try (ConnectionWrapper conn = createConnectionWrapper())
150    {
151      connCreated = true;
152      serverDescriptor = ServerDescriptor.createStandalone(conn.getLdapContext(), filter);
153      serverDescriptor.setAdsProperties(serverProperties);
154      serverDescriptor.updateAdsPropertiesWithServerProperties();
155    }
156    catch (NoPermissionException e)
157    {
158      logger.warn(LocalizableMessage.raw("Permissions error reading server: " + lastLdapUrl, e));
159      Type type = isAdministratorDn()
160          ? TopologyCacheException.Type.NO_PERMISSIONS
161          : TopologyCacheException.Type.NOT_GLOBAL_ADMINISTRATOR;
162      lastException = new TopologyCacheException(type, e, trustManager, lastLdapUrl);
163    }
164    catch (AuthenticationException e)
165    {
166      logger.warn(LocalizableMessage.raw("Authentication exception: " + lastLdapUrl, e));
167      Type type = isAdministratorDn()
168          ? TopologyCacheException.Type.GENERIC_READING_SERVER
169          : TopologyCacheException.Type.NOT_GLOBAL_ADMINISTRATOR;
170      lastException = new TopologyCacheException(type, e, trustManager, lastLdapUrl);
171    }
172    catch (NamingException e)
173    {
174      logger.warn(LocalizableMessage.raw("NamingException error reading server: " + lastLdapUrl, e));
175      Type type = connCreated
176          ? TopologyCacheException.Type.GENERIC_READING_SERVER
177          : TopologyCacheException.Type.GENERIC_CREATING_CONNECTION;
178      lastException = new TopologyCacheException(type, e, trustManager, lastLdapUrl);
179    }
180    catch (Throwable t)
181    {
182      if (!isInterrupted)
183      {
184        logger.warn(LocalizableMessage.raw("Generic error reading server: " + lastLdapUrl, t));
185        logger.warn(LocalizableMessage.raw("server Properties: " + serverProperties));
186        lastException = new TopologyCacheException(TopologyCacheException.Type.BUG, t);
187      }
188    }
189    finally
190    {
191      isOver = true;
192    }
193  }
194
195  /**
196   * Returns a Connection Wrapper.
197   *
198   * @return the connection wrapper
199   * @throws NamingException
200   *           If an error occurs.
201   */
202  public ConnectionWrapper createConnectionWrapper() throws NamingException
203  {
204    if (trustManager != null)
205    {
206      trustManager.resetLastRefusedItems();
207
208      String host = (String)serverProperties.get(ServerProperty.HOST_NAME);
209      trustManager.setHost(host);
210    }
211
212    /* Try to connect to the server in a certain order of preference.  If an
213     * URL fails, we will try with the others.
214     */
215    for (PreferredConnection connection : getLDAPURLsByPreference())
216    {
217      lastLdapUrl = connection.getLDAPURL();
218      ConnectionWrapper conn = new ConnectionWrapper(lastLdapUrl, connection.getType(), dn, pwd, timeout, trustManager);
219      if (conn.getLdapContext() != null)
220      {
221        return conn;
222      }
223    }
224    return null;
225  }
226
227  /**
228   * Returns the non-secure LDAP URL for the given server properties.  It
229   * returns NULL if according to the server properties no non-secure LDAP URL
230   * can be generated (LDAP disabled or port not defined).
231   * @param serverProperties the server properties to be used to generate
232   * the non-secure LDAP URL.
233   * @return the non-secure LDAP URL for the given server properties.
234   */
235  private String getLdapUrl(Map<ServerProperty,Object> serverProperties)
236  {
237    if (isLdapEnabled(serverProperties))
238    {
239      return "ldap://" + getHostNameForLdapUrl(serverProperties) + ":"
240          + serverProperties.get(ServerProperty.LDAP_PORT);
241    }
242    return null;
243  }
244
245  /**
246   * Returns the StartTLS LDAP URL for the given server properties.  It
247   * returns NULL if according to the server properties no StartTLS LDAP URL
248   * can be generated (StartTLS disabled or port not defined).
249   * @param serverProperties the server properties to be used to generate
250   * the StartTLS LDAP URL.
251   * @return the StartTLS LDAP URL for the given server properties.
252   */
253  private String getStartTlsLdapUrl(Map<ServerProperty,Object> serverProperties)
254  {
255    if (isStartTlsEnabled(serverProperties))
256    {
257      return "ldap://" + getHostNameForLdapUrl(serverProperties) + ":"
258          + serverProperties.get(ServerProperty.LDAP_PORT);
259    }
260    return null;
261  }
262
263  /**
264   * Returns the LDAPs URL for the given server properties.  It
265   * returns NULL if according to the server properties no LDAPS URL
266   * can be generated (LDAPS disabled or port not defined).
267   * @param serverProperties the server properties to be used to generate
268   * the LDAPS URL.
269   * @return the LDAPS URL for the given server properties.
270   */
271  private String getLdapsUrl(Map<ServerProperty,Object> serverProperties)
272  {
273    if (isLdapsEnabled(serverProperties))
274    {
275      return "ldaps://" + getHostNameForLdapUrl(serverProperties) + ":"
276          + serverProperties.get(ServerProperty.LDAPS_PORT);
277    }
278    return null;
279  }
280
281  /**
282   * Returns the administration connector URL for the given server properties.
283   * It returns NULL if according to the server properties no administration
284   * connector URL can be generated.
285   * @param serverProperties the server properties to be used to generate
286   * the administration connector URL.
287   * @return the administration connector URL for the given server properties.
288   */
289  private String getAdminConnectorUrl(
290    Map<ServerProperty,Object> serverProperties)
291  {
292    if (isPropertyEnabled(serverProperties, ServerProperty.ADMIN_ENABLED))
293    {
294      Object adminPort = serverProperties.get(ServerProperty.ADMIN_PORT);
295      if (adminPort != null)
296      {
297        return "ldaps://" + getHostNameForLdapUrl(serverProperties) + ":" + adminPort;
298      }
299    }
300    return null;
301  }
302
303  private boolean isLdapEnabled(Map<ServerProperty, Object> serverProperties)
304  {
305    return isPropertyEnabled(serverProperties, ServerProperty.LDAP_ENABLED);
306  }
307
308  private boolean isLdapsEnabled(Map<ServerProperty, Object> serverProperties)
309  {
310    return isPropertyEnabled(serverProperties, ServerProperty.LDAPS_ENABLED);
311  }
312
313  private boolean isStartTlsEnabled(Map<ServerProperty, Object> serverProperties)
314  {
315    return isLdapEnabled(serverProperties) && isPropertyEnabled(serverProperties, ServerProperty.STARTTLS_ENABLED);
316  }
317
318  private boolean isPropertyEnabled(Map<ServerProperty, Object> serverProperties, ServerProperty property)
319  {
320    Object v = serverProperties.get(property);
321    return v != null && "true".equalsIgnoreCase(v.toString());
322  }
323
324  /**
325   * Returns the host name to be used to generate an LDAP URL based on the
326   * contents of the provided server properties.
327   * @param serverProperties the server properties.
328   * @return the host name to be used to generate an LDAP URL based on the
329   * contents of the provided server properties.
330   */
331  private String getHostNameForLdapUrl(
332      Map<ServerProperty,Object> serverProperties)
333  {
334    String host = (String)serverProperties.get(ServerProperty.HOST_NAME);
335    return Utils.getHostNameForLdapUrl(host);
336  }
337
338  /**
339   * Returns whether the DN provided in the constructor is a Global
340   * Administrator DN or not.
341   * @return <CODE>true</CODE> if the DN provided in the constructor is a Global
342   * Administrator DN and <CODE>false</CODE> otherwise.
343   */
344  private boolean isAdministratorDn()
345  {
346    try
347    {
348      LdapName theDn = new LdapName(dn);
349      LdapName containerDn =
350        new LdapName(ADSContext.getAdministratorContainerDN());
351      return theDn.startsWith(containerDn);
352    }
353    catch (Throwable t)
354    {
355      logger.warn(LocalizableMessage.raw("Error parsing authentication DNs.", t));
356      return false;
357    }
358  }
359
360  /**
361   * Returns the list of LDAP URLs that can be used to connect to the server.
362   * They are ordered so that the first URL is the preferred URL to be used.
363   * @return the list of LDAP URLs that can be used to connect to the server.
364   * They are ordered so that the first URL is the preferred URL to be used.
365   */
366  private LinkedHashSet<PreferredConnection> getLDAPURLsByPreference()
367  {
368    LinkedHashSet<PreferredConnection> ldapUrls = new LinkedHashSet<>();
369
370    String adminConnectorUrl = getAdminConnectorUrl(serverProperties);
371    String ldapsUrl = getLdapsUrl(serverProperties);
372    String startTLSUrl = getStartTlsLdapUrl(serverProperties);
373    String ldapUrl = getLdapUrl(serverProperties);
374
375    // Check the preferred connections passed in the constructor.
376    for (PreferredConnection connection : preferredLDAPURLs)
377    {
378      String url = connection.getLDAPURL();
379      if (url.equalsIgnoreCase(adminConnectorUrl))
380      {
381        ldapUrls.add(connection);
382      }
383      else if (url.equalsIgnoreCase(ldapsUrl) &&
384          connection.getType() == PreferredConnection.Type.LDAPS)
385      {
386        ldapUrls.add(connection);
387      }
388      else if (url.equalsIgnoreCase(startTLSUrl) &&
389          connection.getType() == PreferredConnection.Type.START_TLS)
390      {
391        ldapUrls.add(connection);
392      }
393      else if (url.equalsIgnoreCase(ldapUrl) &&
394          connection.getType() == PreferredConnection.Type.LDAP)
395      {
396        ldapUrls.add(connection);
397      }
398    }
399
400    if (adminConnectorUrl != null)
401    {
402      ldapUrls.add(new PreferredConnection(adminConnectorUrl, PreferredConnection.Type.LDAPS));
403    }
404    if (ldapsUrl != null)
405    {
406      ldapUrls.add(new PreferredConnection(ldapsUrl, PreferredConnection.Type.LDAPS));
407    }
408    if (startTLSUrl != null)
409    {
410      ldapUrls.add(new PreferredConnection(startTLSUrl, PreferredConnection.Type.START_TLS));
411    }
412    if (ldapUrl != null)
413    {
414      ldapUrls.add(new PreferredConnection(ldapUrl, PreferredConnection.Type.LDAP));
415    }
416    return ldapUrls;
417  }
418}