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