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