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