001/*
002 * Copyright 2012-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2012-2014 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util;
022
023
024
025import java.io.OutputStream;
026import java.util.concurrent.atomic.AtomicReference;
027import javax.net.SocketFactory;
028import javax.net.ssl.KeyManager;
029import javax.net.ssl.SSLContext;
030import javax.net.ssl.TrustManager;
031
032import com.unboundid.ldap.sdk.BindRequest;
033import com.unboundid.ldap.sdk.ExtendedResult;
034import com.unboundid.ldap.sdk.LDAPConnection;
035import com.unboundid.ldap.sdk.LDAPConnectionOptions;
036import com.unboundid.ldap.sdk.LDAPConnectionPool;
037import com.unboundid.ldap.sdk.LDAPException;
038import com.unboundid.ldap.sdk.PostConnectProcessor;
039import com.unboundid.ldap.sdk.ResultCode;
040import com.unboundid.ldap.sdk.ServerSet;
041import com.unboundid.ldap.sdk.SimpleBindRequest;
042import com.unboundid.ldap.sdk.SingleServerSet;
043import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor;
044import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
045import com.unboundid.util.args.ArgumentException;
046import com.unboundid.util.args.ArgumentParser;
047import com.unboundid.util.args.BooleanArgument;
048import com.unboundid.util.args.DNArgument;
049import com.unboundid.util.args.FileArgument;
050import com.unboundid.util.args.IntegerArgument;
051import com.unboundid.util.args.StringArgument;
052import com.unboundid.util.ssl.KeyStoreKeyManager;
053import com.unboundid.util.ssl.PromptTrustManager;
054import com.unboundid.util.ssl.SSLUtil;
055import com.unboundid.util.ssl.TrustAllTrustManager;
056import com.unboundid.util.ssl.TrustStoreTrustManager;
057
058import static com.unboundid.util.UtilityMessages.*;
059
060
061
062/**
063 * This class provides a basis for developing command-line tools that have the
064 * ability to communicate with multiple directory servers, potentially with
065 * very different settings for each.  For example, it may be used to help create
066 * tools that move or compare data from one server to another.
067 * <BR><BR>
068 * Each server will be identified by a prefix and/or suffix that will be added
069 * to the argument name (e.g., if the first server has a prefix of "source",
070 * then the "hostname" argument will actually be "sourceHostname").  The
071 * base names for the arguments this class supports include:
072 * <UL>
073 *   <LI>hostname -- Specifies the address of the directory server.  If this
074 *       isn't specified, then a default of "localhost" will be used.</LI>
075 *   <LI>port -- specifies the port number of the directory server.  If this
076 *       isn't specified, then a default port of 389 will be used.</LI>
077 *   <LI>bindDN -- Specifies the DN to use to bind to the directory server using
078 *       simple authentication.  If this isn't specified, then simple
079 *       authentication will not be performed.</LI>
080 *   <LI>bindPassword -- Specifies the password to use when binding with simple
081 *       authentication or a password-based SASL mechanism.</LI>
082 *   <LI>bindPasswordFile -- Specifies the path to a file containing the
083 *       password to use when binding with simple authentication or a
084 *       password-based SASL mechanism.</LI>
085 *   <LI>useSSL -- Indicates that communication with the server should be
086 *       secured using SSL.</LI>
087 *   <LI>useStartTLS -- Indicates that communication with the server should be
088 *       secured using StartTLS.</LI>
089 *   <LI>trustAll -- Indicates that the client should trust any certificate
090 *       that the server presents to it.</LI>
091 *   <LI>keyStorePath -- Specifies the path to the key store to use to obtain
092 *       client certificates.</LI>
093 *   <LI>keyStorePassword -- Specifies the password to use to access the
094 *       contents of the key store.</LI>
095 *   <LI>keyStorePasswordFile -- Specifies the path ot a file containing the
096 *       password to use to access the contents of the key store.</LI>
097 *   <LI>keyStoreFormat -- Specifies the format to use for the key store
098 *       file.</LI>
099 *   <LI>trustStorePath -- Specifies the path to the trust store to use to
100 *       obtain client certificates.</LI>
101 *   <LI>trustStorePassword -- Specifies the password to use to access the
102 *       contents of the trust store.</LI>
103 *   <LI>trustStorePasswordFile -- Specifies the path ot a file containing the
104 *       password to use to access the contents of the trust store.</LI>
105 *   <LI>trustStoreFormat -- Specifies the format to use for the trust store
106 *       file.</LI>
107 *   <LI>certNickname -- Specifies the nickname of the client certificate to
108 *       use when performing SSL client authentication.</LI>
109 *   <LI>saslOption -- Specifies a SASL option to use when performing SASL
110 *       authentication.</LI>
111 * </UL>
112 * If SASL authentication is to be used, then a "mech" SASL option must be
113 * provided to specify the name of the SASL mechanism to use.  Depending on the
114 * SASL mechanism, additional SASL options may be required or optional.
115 */
116@Extensible()
117@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
118public abstract class MultiServerLDAPCommandLineTool
119       extends CommandLineTool
120{
121  // The set of prefixes and suffixes that will be used for server names.
122  private final int numServers;
123  private final String[] serverNamePrefixes;
124  private final String[] serverNameSuffixes;
125
126  // The set of arguments used to hold information about connection properties.
127  private final BooleanArgument[] trustAll;
128  private final BooleanArgument[] useSSL;
129  private final BooleanArgument[] useStartTLS;
130  private final DNArgument[]      bindDN;
131  private final FileArgument[]    bindPasswordFile;
132  private final FileArgument[]    keyStorePasswordFile;
133  private final FileArgument[]    trustStorePasswordFile;
134  private final IntegerArgument[] port;
135  private final StringArgument[]  bindPassword;
136  private final StringArgument[]  certificateNickname;
137  private final StringArgument[]  host;
138  private final StringArgument[]  keyStoreFormat;
139  private final StringArgument[]  keyStorePath;
140  private final StringArgument[]  keyStorePassword;
141  private final StringArgument[]  saslOption;
142  private final StringArgument[]  trustStoreFormat;
143  private final StringArgument[]  trustStorePath;
144  private final StringArgument[]  trustStorePassword;
145
146  // Variables used when creating and authenticating connections.
147  private final BindRequest[] bindRequest;
148  private final ServerSet[]   serverSet;
149  private final SSLContext[]  startTLSContext;
150
151  // The prompt trust manager that will be shared by all connections created for
152  // which it is appropriate.  This will allow them to benefit from the common
153  // cache.
154  private final AtomicReference<PromptTrustManager> promptTrustManager;
155
156
157
158  /**
159   * Creates a new instance of this multi-server LDAP command-line tool.  At
160   * least one of the set of server name prefixes and suffixes must be
161   * non-{@code null}.  If both are non-{@code null}, then they must have the
162   * same number of elements.
163   *
164   * @param  outStream           The output stream to use for standard output.
165   *                             It may be {@code System.out} for the JVM's
166   *                             default standard output stream, {@code null} if
167   *                             no output should be generated, or a custom
168   *                             output stream if the output should be sent to
169   *                             an alternate location.
170   * @param  errStream           The output stream to use for standard error.
171   *                             It may be {@code System.err} for the JVM's
172   *                             default standard error stream, {@code null} if
173   *                             no output should be generated, or a custom
174   *                             output stream if the output should be sent to
175   *                             an alternate location.
176   * @param  serverNamePrefixes  The prefixes to include before the names of
177   *                             each of the parameters to identify each server.
178   *                             It may be {@code null} if only suffixes should
179   *                             be used.
180   * @param  serverNameSuffixes  The suffixes to include after the names of each
181   *                             of the parameters to identify each server.  It
182   *                             may be {@code null} if only prefixes should be
183   *                             used.
184   *
185   * @throws  LDAPSDKUsageException  If both the sets of server name prefixes
186   *                                 and suffixes are {@code null} or empty, or
187   *                                 if both sets are non-{@code null} but have
188   *                                 different numbers of elements.
189   */
190  public MultiServerLDAPCommandLineTool(final OutputStream outStream,
191                                        final OutputStream errStream,
192                                        final String[] serverNamePrefixes,
193                                        final String[] serverNameSuffixes)
194         throws LDAPSDKUsageException
195  {
196    super(outStream, errStream);
197
198    promptTrustManager = new AtomicReference<PromptTrustManager>();
199
200    this.serverNamePrefixes = serverNamePrefixes;
201    this.serverNameSuffixes = serverNameSuffixes;
202
203    if (serverNamePrefixes == null)
204    {
205      if (serverNameSuffixes == null)
206      {
207        throw new LDAPSDKUsageException(
208             ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_NULL.get());
209      }
210      else
211      {
212        numServers = serverNameSuffixes.length;
213      }
214    }
215    else
216    {
217      numServers = serverNamePrefixes.length;
218
219      if ((serverNameSuffixes != null) &&
220          (serverNamePrefixes.length != serverNameSuffixes.length))
221      {
222        throw new LDAPSDKUsageException(
223             ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_MISMATCH.get());
224      }
225    }
226
227    if (numServers == 0)
228    {
229      throw new LDAPSDKUsageException(
230           ERR_MULTI_LDAP_TOOL_PREFIXES_AND_SUFFIXES_EMPTY.get());
231    }
232
233    trustAll               = new BooleanArgument[numServers];
234    useSSL                 = new BooleanArgument[numServers];
235    useStartTLS            = new BooleanArgument[numServers];
236    bindDN                 = new DNArgument[numServers];
237    bindPasswordFile       = new FileArgument[numServers];
238    keyStorePasswordFile   = new FileArgument[numServers];
239    trustStorePasswordFile = new FileArgument[numServers];
240    port                   = new IntegerArgument[numServers];
241    bindPassword           = new StringArgument[numServers];
242    certificateNickname    = new StringArgument[numServers];
243    host                   = new StringArgument[numServers];
244    keyStoreFormat         = new StringArgument[numServers];
245    keyStorePath           = new StringArgument[numServers];
246    keyStorePassword       = new StringArgument[numServers];
247    saslOption             = new StringArgument[numServers];
248    trustStoreFormat       = new StringArgument[numServers];
249    trustStorePath         = new StringArgument[numServers];
250    trustStorePassword     = new StringArgument[numServers];
251
252    bindRequest     = new BindRequest[numServers];
253    serverSet       = new ServerSet[numServers];
254    startTLSContext = new SSLContext[numServers];
255  }
256
257
258
259  /**
260   * {@inheritDoc}
261   */
262  @Override()
263  public final void addToolArguments(final ArgumentParser parser)
264         throws ArgumentException
265  {
266    for (int i=0; i < numServers; i++)
267    {
268      host[i] = new StringArgument(null, genArgName(i, "hostname"), true, 1,
269           INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(),
270           INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost");
271      parser.addArgument(host[i]);
272
273      port[i] = new IntegerArgument(null, genArgName(i, "port"), true, 1,
274           INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(),
275           INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65535, 389);
276      parser.addArgument(port[i]);
277
278      bindDN[i] = new DNArgument(null, genArgName(i, "bindDN"), false, 1,
279           INFO_LDAP_TOOL_PLACEHOLDER_DN.get(),
280           INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get());
281      parser.addArgument(bindDN[i]);
282
283      bindPassword[i] = new StringArgument(null, genArgName(i, "bindPassword"),
284           false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
285           INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get());
286      parser.addArgument(bindPassword[i]);
287
288      bindPasswordFile[i] = new FileArgument(null,
289           genArgName(i, "bindPasswordFile"), false, 1,
290           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
291           INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true,
292           false);
293      parser.addArgument(bindPasswordFile[i]);
294
295      useSSL[i] = new BooleanArgument(null, genArgName(i, "useSSL"), 1,
296           INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get());
297      parser.addArgument(useSSL[i]);
298
299      useStartTLS[i] = new BooleanArgument(null, genArgName(i, "useStartTLS"),
300           1, INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get());
301      parser.addArgument(useStartTLS[i]);
302
303      trustAll[i] = new BooleanArgument(null, genArgName(i, "trustAll"), 1,
304           INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get());
305      parser.addArgument(trustAll[i]);
306
307      keyStorePath[i] = new StringArgument(null, genArgName(i, "keyStorePath"),
308           false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
309           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get());
310      parser.addArgument(keyStorePath[i]);
311
312      keyStorePassword[i] = new StringArgument(null,
313           genArgName(i, "keyStorePassword"), false, 1,
314           INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
315           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get());
316      parser.addArgument(keyStorePassword[i]);
317
318      keyStorePasswordFile[i] = new FileArgument(null,
319           genArgName(i, "keyStorePasswordFile"), false, 1,
320           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
321           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get(), true,
322           true, true, false);
323      parser.addArgument(keyStorePasswordFile[i]);
324
325      keyStoreFormat[i] = new StringArgument(null,
326           genArgName(i, "keyStoreFormat"), false, 1,
327           INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
328           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get());
329      parser.addArgument(keyStoreFormat[i]);
330
331      trustStorePath[i] = new StringArgument(null,
332           genArgName(i, "trustStorePath"), false, 1,
333           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
334           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get());
335      parser.addArgument(trustStorePath[i]);
336
337      trustStorePassword[i] = new StringArgument(null,
338           genArgName(i, "trustStorePassword"), false, 1,
339           INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
340           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get());
341      parser.addArgument(trustStorePassword[i]);
342
343      trustStorePasswordFile[i] = new FileArgument(null,
344           genArgName(i, "trustStorePasswordFile"), false, 1,
345           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
346           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get(), true,
347           true, true, false);
348      parser.addArgument(trustStorePasswordFile[i]);
349
350      trustStoreFormat[i] = new StringArgument(null,
351           genArgName(i, "trustStoreFormat"), false, 1,
352           INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
353           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get());
354      parser.addArgument(trustStoreFormat[i]);
355
356      certificateNickname[i] = new StringArgument(null,
357           genArgName(i, "certNickname"), false, 1,
358           INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(),
359           INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get());
360      parser.addArgument(certificateNickname[i]);
361
362      saslOption[i] = new StringArgument(null, genArgName(i, "saslOption"),
363           false, 0, INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(),
364           INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get());
365      parser.addArgument(saslOption[i]);
366
367      parser.addDependentArgumentSet(bindDN[i], bindPassword[i],
368           bindPasswordFile[i]);
369
370      parser.addExclusiveArgumentSet(useSSL[i], useStartTLS[i]);
371      parser.addExclusiveArgumentSet(bindPassword[i], bindPasswordFile[i]);
372      parser.addExclusiveArgumentSet(keyStorePassword[i],
373           keyStorePasswordFile[i]);
374      parser.addExclusiveArgumentSet(trustStorePassword[i],
375           trustStorePasswordFile[i]);
376      parser.addExclusiveArgumentSet(trustAll[i], trustStorePath[i]);
377    }
378
379    addNonLDAPArguments(parser);
380  }
381
382
383
384  /**
385   * Constructs the name to use for an argument from the given base and the
386   * appropriate prefix and suffix.
387   *
388   * @param  index  The index into the set of prefixes and suffixes.
389   * @param  base   The base name for the argument.
390   *
391   * @return  The constructed argument name.
392   */
393  private String genArgName(final int index, final String base)
394  {
395    final StringBuilder buffer = new StringBuilder();
396
397    if (serverNamePrefixes != null)
398    {
399      buffer.append(serverNamePrefixes[index]);
400
401      if (base.equals("saslOption"))
402      {
403        buffer.append("SASLOption");
404      }
405      else
406      {
407        buffer.append(StaticUtils.capitalize(base));
408      }
409    }
410    else
411    {
412      buffer.append(base);
413    }
414
415    if (serverNameSuffixes != null)
416    {
417      buffer.append(serverNameSuffixes[index]);
418    }
419
420    return buffer.toString();
421  }
422
423
424
425  /**
426   * Adds the arguments needed by this command-line tool to the provided
427   * argument parser which are not related to connecting or authenticating to
428   * the directory server.
429   *
430   * @param  parser  The argument parser to which the arguments should be added.
431   *
432   * @throws  ArgumentException  If a problem occurs while adding the arguments.
433   */
434  public abstract void addNonLDAPArguments(final ArgumentParser parser)
435         throws ArgumentException;
436
437
438
439  /**
440   * {@inheritDoc}
441   */
442  @Override()
443  public final void doExtendedArgumentValidation()
444         throws ArgumentException
445  {
446    doExtendedNonLDAPArgumentValidation();
447  }
448
449
450
451  /**
452   * Performs any necessary processing that should be done to ensure that the
453   * provided set of command-line arguments were valid.  This method will be
454   * called after the basic argument parsing has been performed and after all
455   * LDAP-specific argument validation has been processed, and immediately
456   * before the {@link CommandLineTool#doToolProcessing} method is invoked.
457   *
458   * @throws  ArgumentException  If there was a problem with the command-line
459   *                             arguments provided to this program.
460   */
461  public void doExtendedNonLDAPArgumentValidation()
462         throws ArgumentException
463  {
464    // No processing will be performed by default.
465  }
466
467
468
469  /**
470   * Retrieves the connection options that should be used for connections that
471   * are created with this command line tool.  Subclasses may override this
472   * method to use a custom set of connection options.
473   *
474   * @return  The connection options that should be used for connections that
475   *          are created with this command line tool.
476   */
477  public LDAPConnectionOptions getConnectionOptions()
478  {
479    return new LDAPConnectionOptions();
480  }
481
482
483
484  /**
485   * Retrieves a connection that may be used to communicate with the indicated
486   * directory server.
487   * <BR><BR>
488   * Note that this method is threadsafe and may be invoked by multiple threads
489   * accessing the same instance only while that instance is in the process of
490   * invoking the {@link #doToolProcessing} method.
491   *
492   * @param  serverIndex  The zero-based index of the server to which the
493   *                      connection should be established.
494   *
495   * @return  A connection that may be used to communicate with the indicated
496   *          directory server.
497   *
498   * @throws  LDAPException  If a problem occurs while creating the connection.
499   */
500  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
501  public final LDAPConnection getConnection(final int serverIndex)
502         throws LDAPException
503  {
504    final LDAPConnection connection = getUnauthenticatedConnection(serverIndex);
505
506    try
507    {
508      if (bindRequest[serverIndex] != null)
509      {
510        connection.bind(bindRequest[serverIndex]);
511      }
512    }
513    catch (LDAPException le)
514    {
515      Debug.debugException(le);
516      connection.close();
517      throw le;
518    }
519
520    return connection;
521  }
522
523
524
525  /**
526   * Retrieves an unauthenticated connection that may be used to communicate
527   * with the indicated directory server.
528   * <BR><BR>
529   * Note that this method is threadsafe and may be invoked by multiple threads
530   * accessing the same instance only while that instance is in the process of
531   * invoking the {@link #doToolProcessing} method.
532   *
533   * @param  serverIndex  The zero-based index of the server to which the
534   *                      connection should be established.
535   *
536   * @return  An unauthenticated connection that may be used to communicate with
537   *          the indicated directory server.
538   *
539   * @throws  LDAPException  If a problem occurs while creating the connection.
540   */
541  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
542  public final LDAPConnection getUnauthenticatedConnection(
543                                   final int serverIndex)
544         throws LDAPException
545  {
546    if (serverSet[serverIndex] == null)
547    {
548      serverSet[serverIndex]   = createServerSet(serverIndex);
549      bindRequest[serverIndex] = createBindRequest(serverIndex);
550    }
551
552    final LDAPConnection connection = serverSet[serverIndex].getConnection();
553
554    if (useStartTLS[serverIndex].isPresent())
555    {
556      try
557      {
558        final ExtendedResult extendedResult =
559             connection.processExtendedOperation(
560                  new StartTLSExtendedRequest(startTLSContext[serverIndex]));
561        if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
562        {
563          throw new LDAPException(extendedResult.getResultCode(),
564               ERR_LDAP_TOOL_START_TLS_FAILED.get(
565                    extendedResult.getDiagnosticMessage()));
566        }
567      }
568      catch (LDAPException le)
569      {
570        Debug.debugException(le);
571        connection.close();
572        throw le;
573      }
574    }
575
576    return connection;
577  }
578
579
580
581  /**
582   * Retrieves a connection pool that may be used to communicate with the
583   * indicated directory server.
584   * <BR><BR>
585   * Note that this method is threadsafe and may be invoked by multiple threads
586   * accessing the same instance only while that instance is in the process of
587   * invoking the {@link #doToolProcessing} method.
588   *
589   * @param  serverIndex         The zero-based index of the server to which the
590   *                             connection should be established.
591   * @param  initialConnections  The number of connections that should be
592   *                             initially established in the pool.
593   * @param  maxConnections      The maximum number of connections to maintain
594   *                             in the pool.
595   *
596   * @return  A connection that may be used to communicate with the indicated
597   *          directory server.
598   *
599   * @throws  LDAPException  If a problem occurs while creating the connection
600   *                         pool.
601   */
602  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
603  public final LDAPConnectionPool getConnectionPool(
604                                       final int serverIndex,
605                                       final int initialConnections,
606                                       final int maxConnections)
607            throws LDAPException
608  {
609    if (serverSet[serverIndex] == null)
610    {
611      serverSet[serverIndex]   = createServerSet(serverIndex);
612      bindRequest[serverIndex] = createBindRequest(serverIndex);
613    }
614
615    PostConnectProcessor postConnectProcessor = null;
616    if (useStartTLS[serverIndex].isPresent())
617    {
618      postConnectProcessor =
619           new StartTLSPostConnectProcessor(startTLSContext[serverIndex]);
620    }
621
622    return new LDAPConnectionPool(serverSet[serverIndex],
623         bindRequest[serverIndex], initialConnections, maxConnections,
624         postConnectProcessor);
625  }
626
627
628
629  /**
630   * Creates the server set to use when creating connections or connection
631   * pools.
632   *
633   * @param  serverIndex  The zero-based index of the server to which the
634   *                      connection should be established.
635   *
636   * @return  The server set to use when creating connections or connection
637   *          pools.
638   *
639   * @throws  LDAPException  If a problem occurs while creating the server set.
640   */
641  public final ServerSet createServerSet(final int serverIndex)
642         throws LDAPException
643  {
644    final SSLUtil sslUtil = createSSLUtil(serverIndex);
645
646    SocketFactory socketFactory = null;
647    if (useSSL[serverIndex].isPresent())
648    {
649      try
650      {
651        socketFactory = sslUtil.createSSLSocketFactory();
652      }
653      catch (Exception e)
654      {
655        Debug.debugException(e);
656        throw new LDAPException(ResultCode.LOCAL_ERROR,
657             ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
658                  StaticUtils.getExceptionMessage(e)), e);
659      }
660    }
661    else if (useStartTLS[serverIndex].isPresent())
662    {
663      try
664      {
665        startTLSContext[serverIndex] = sslUtil.createSSLContext();
666      }
667      catch (Exception e)
668      {
669        Debug.debugException(e);
670        throw new LDAPException(ResultCode.LOCAL_ERROR,
671             ERR_LDAP_TOOL_CANNOT_CREATE_SSL_CONTEXT.get(
672                  StaticUtils.getExceptionMessage(e)), e);
673      }
674    }
675
676    return new SingleServerSet(host[serverIndex].getValue(),
677         port[serverIndex].getValue(), socketFactory, getConnectionOptions());
678  }
679
680
681
682  /**
683   * Creates the SSLUtil instance to use for secure communication.
684   *
685   * @param  serverIndex  The zero-based index of the server to which the
686   *                      connection should be established.
687   *
688   * @return  The SSLUtil instance to use for secure communication, or
689   *          {@code null} if secure communication is not needed.
690   *
691   * @throws  LDAPException  If a problem occurs while creating the SSLUtil
692   *                         instance.
693   */
694  public final SSLUtil createSSLUtil(final int serverIndex)
695         throws LDAPException
696  {
697    if (useSSL[serverIndex].isPresent() || useStartTLS[serverIndex].isPresent())
698    {
699      KeyManager keyManager = null;
700      if (keyStorePath[serverIndex].isPresent())
701      {
702        char[] pw = null;
703        if (keyStorePassword[serverIndex].isPresent())
704        {
705          pw = keyStorePassword[serverIndex].getValue().toCharArray();
706        }
707        else if (keyStorePasswordFile[serverIndex].isPresent())
708        {
709          try
710          {
711            pw = keyStorePasswordFile[serverIndex].getNonBlankFileLines().
712                 get(0).toCharArray();
713          }
714          catch (Exception e)
715          {
716            Debug.debugException(e);
717            throw new LDAPException(ResultCode.LOCAL_ERROR,
718                 ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get(
719                      StaticUtils.getExceptionMessage(e)), e);
720          }
721        }
722
723        try
724        {
725          keyManager = new KeyStoreKeyManager(
726               keyStorePath[serverIndex].getValue(), pw,
727               keyStoreFormat[serverIndex].getValue(),
728               certificateNickname[serverIndex].getValue());
729        }
730        catch (Exception e)
731        {
732          Debug.debugException(e);
733          throw new LDAPException(ResultCode.LOCAL_ERROR,
734               ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
735                    StaticUtils.getExceptionMessage(e)), e);
736        }
737      }
738
739      TrustManager trustManager;
740      if (trustAll[serverIndex].isPresent())
741      {
742        trustManager = new TrustAllTrustManager(false);
743      }
744      else if (trustStorePath[serverIndex].isPresent())
745      {
746        char[] pw = null;
747        if (trustStorePassword[serverIndex].isPresent())
748        {
749          pw = trustStorePassword[serverIndex].getValue().toCharArray();
750        }
751        else if (trustStorePasswordFile[serverIndex].isPresent())
752        {
753          try
754          {
755            pw = trustStorePasswordFile[serverIndex].getNonBlankFileLines().
756                 get(0).toCharArray();
757          }
758          catch (Exception e)
759          {
760            Debug.debugException(e);
761            throw new LDAPException(ResultCode.LOCAL_ERROR,
762                 ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
763                      StaticUtils.getExceptionMessage(e)), e);
764          }
765        }
766
767        trustManager = new TrustStoreTrustManager(
768             trustStorePath[serverIndex].getValue(), pw,
769             trustStoreFormat[serverIndex].getValue(), true);
770      }
771      else
772      {
773        trustManager = promptTrustManager.get();
774        if (trustManager == null)
775        {
776          final PromptTrustManager m = new PromptTrustManager();
777          promptTrustManager.compareAndSet(null, m);
778          trustManager = promptTrustManager.get();
779        }
780      }
781
782      return new SSLUtil(keyManager, trustManager);
783    }
784    else
785    {
786      return null;
787    }
788  }
789
790
791
792  /**
793   * Creates the bind request to use to authenticate to the indicated server.
794   *
795   * @param  serverIndex  The zero-based index of the server to which the
796   *                      connection should be established.
797   *
798   * @return  The bind request to use to authenticate to the indicated server,
799   *          or {@code null} if no bind should be performed.
800   *
801   * @throws  LDAPException  If a problem occurs while creating the bind
802   *                         request.
803   */
804  public final BindRequest createBindRequest(final int serverIndex)
805         throws LDAPException
806  {
807    final String pw;
808    if (bindPassword[serverIndex].isPresent())
809    {
810      pw = bindPassword[serverIndex].getValue();
811    }
812    else if (bindPasswordFile[serverIndex].isPresent())
813    {
814      try
815      {
816        pw = bindPasswordFile[serverIndex].getNonBlankFileLines().get(0);
817      }
818      catch (Exception e)
819      {
820        Debug.debugException(e);
821        throw new LDAPException(ResultCode.LOCAL_ERROR,
822             ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
823                  StaticUtils.getExceptionMessage(e)), e);
824      }
825    }
826    else
827    {
828      pw = null;
829    }
830
831    if (saslOption[serverIndex].isPresent())
832    {
833      final String dnStr;
834      if (bindDN[serverIndex].isPresent())
835      {
836        dnStr = bindDN[serverIndex].getValue().toString();
837      }
838      else
839      {
840        dnStr = null;
841      }
842
843      return SASLUtils.createBindRequest(dnStr, pw, null,
844           saslOption[serverIndex].getValues());
845    }
846    else if (bindDN[serverIndex].isPresent())
847    {
848      return new SimpleBindRequest(bindDN[serverIndex].getValue(), pw);
849    }
850    else
851    {
852      return null;
853    }
854  }
855}