001/*
002 * Copyright 2012-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2012-2017 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.SSLSocketFactory;
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 SSLSocketFactory[] startTLSSocketFactory;
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    startTLSSocketFactory = new SSLSocketFactory[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      final StringBuilder groupNameBuffer = new StringBuilder();
269      if (serverNamePrefixes != null)
270      {
271        final String prefix = serverNamePrefixes[i].replace('-', ' ').trim();
272        groupNameBuffer.append(StaticUtils.capitalize(prefix, true));
273      }
274
275      if (serverNameSuffixes != null)
276      {
277        if (groupNameBuffer.length() > 0)
278        {
279          groupNameBuffer.append(' ');
280        }
281
282        final String suffix = serverNameSuffixes[i].replace('-', ' ').trim();
283        groupNameBuffer.append(StaticUtils.capitalize(suffix, true));
284      }
285
286      groupNameBuffer.append(' ');
287      groupNameBuffer.append(INFO_MULTI_LDAP_TOOL_GROUP_CONN_AND_AUTH.get());
288      final String groupName = groupNameBuffer.toString();
289
290
291      host[i] = new StringArgument(null, genArgName(i, "hostname"), true, 1,
292           INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(),
293           INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost");
294      host[i].setArgumentGroupName(groupName);
295      parser.addArgument(host[i]);
296
297      port[i] = new IntegerArgument(null, genArgName(i, "port"), true, 1,
298           INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(),
299           INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65535, 389);
300      port[i].setArgumentGroupName(groupName);
301      parser.addArgument(port[i]);
302
303      bindDN[i] = new DNArgument(null, genArgName(i, "bindDN"), false, 1,
304           INFO_LDAP_TOOL_PLACEHOLDER_DN.get(),
305           INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get());
306      bindDN[i].setArgumentGroupName(groupName);
307      parser.addArgument(bindDN[i]);
308
309      bindPassword[i] = new StringArgument(null, genArgName(i, "bindPassword"),
310           false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
311           INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get());
312      bindPassword[i].setSensitive(true);
313      bindPassword[i].setArgumentGroupName(groupName);
314      parser.addArgument(bindPassword[i]);
315
316      bindPasswordFile[i] = new FileArgument(null,
317           genArgName(i, "bindPasswordFile"), false, 1,
318           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
319           INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true,
320           false);
321      bindPasswordFile[i].setArgumentGroupName(groupName);
322      parser.addArgument(bindPasswordFile[i]);
323
324      useSSL[i] = new BooleanArgument(null, genArgName(i, "useSSL"), 1,
325           INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get());
326      useSSL[i].setArgumentGroupName(groupName);
327      parser.addArgument(useSSL[i]);
328
329      useStartTLS[i] = new BooleanArgument(null, genArgName(i, "useStartTLS"),
330           1, INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get());
331      useStartTLS[i].setArgumentGroupName(groupName);
332      parser.addArgument(useStartTLS[i]);
333
334      trustAll[i] = new BooleanArgument(null, genArgName(i, "trustAll"), 1,
335           INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get());
336      trustAll[i].setArgumentGroupName(groupName);
337      parser.addArgument(trustAll[i]);
338
339      keyStorePath[i] = new StringArgument(null, genArgName(i, "keyStorePath"),
340           false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
341           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get());
342      keyStorePath[i].setArgumentGroupName(groupName);
343      parser.addArgument(keyStorePath[i]);
344
345      keyStorePassword[i] = new StringArgument(null,
346           genArgName(i, "keyStorePassword"), false, 1,
347           INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
348           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get());
349      keyStorePassword[i].setSensitive(true);
350      keyStorePassword[i].setArgumentGroupName(groupName);
351      parser.addArgument(keyStorePassword[i]);
352
353      keyStorePasswordFile[i] = new FileArgument(null,
354           genArgName(i, "keyStorePasswordFile"), false, 1,
355           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
356           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get(), true,
357           true, true, false);
358      keyStorePasswordFile[i].setArgumentGroupName(groupName);
359      parser.addArgument(keyStorePasswordFile[i]);
360
361      keyStoreFormat[i] = new StringArgument(null,
362           genArgName(i, "keyStoreFormat"), false, 1,
363           INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
364           INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get());
365      keyStoreFormat[i].setArgumentGroupName(groupName);
366      parser.addArgument(keyStoreFormat[i]);
367
368      trustStorePath[i] = new StringArgument(null,
369           genArgName(i, "trustStorePath"), false, 1,
370           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
371           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get());
372      trustStorePath[i].setArgumentGroupName(groupName);
373      parser.addArgument(trustStorePath[i]);
374
375      trustStorePassword[i] = new StringArgument(null,
376           genArgName(i, "trustStorePassword"), false, 1,
377           INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
378           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get());
379      trustStorePassword[i].setSensitive(true);
380      trustStorePassword[i].setArgumentGroupName(groupName);
381      parser.addArgument(trustStorePassword[i]);
382
383      trustStorePasswordFile[i] = new FileArgument(null,
384           genArgName(i, "trustStorePasswordFile"), false, 1,
385           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
386           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get(), true,
387           true, true, false);
388      trustStorePasswordFile[i].setArgumentGroupName(groupName);
389      parser.addArgument(trustStorePasswordFile[i]);
390
391      trustStoreFormat[i] = new StringArgument(null,
392           genArgName(i, "trustStoreFormat"), false, 1,
393           INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
394           INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get());
395      trustStoreFormat[i].setArgumentGroupName(groupName);
396      parser.addArgument(trustStoreFormat[i]);
397
398      certificateNickname[i] = new StringArgument(null,
399           genArgName(i, "certNickname"), false, 1,
400           INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(),
401           INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get());
402      certificateNickname[i].setArgumentGroupName(groupName);
403      parser.addArgument(certificateNickname[i]);
404
405      saslOption[i] = new StringArgument(null, genArgName(i, "saslOption"),
406           false, 0, INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(),
407           INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get());
408      saslOption[i].setArgumentGroupName(groupName);
409      parser.addArgument(saslOption[i]);
410
411      parser.addDependentArgumentSet(bindDN[i], bindPassword[i],
412           bindPasswordFile[i]);
413
414      parser.addExclusiveArgumentSet(useSSL[i], useStartTLS[i]);
415      parser.addExclusiveArgumentSet(bindPassword[i], bindPasswordFile[i]);
416      parser.addExclusiveArgumentSet(keyStorePassword[i],
417           keyStorePasswordFile[i]);
418      parser.addExclusiveArgumentSet(trustStorePassword[i],
419           trustStorePasswordFile[i]);
420      parser.addExclusiveArgumentSet(trustAll[i], trustStorePath[i]);
421    }
422
423    addNonLDAPArguments(parser);
424  }
425
426
427
428  /**
429   * Constructs the name to use for an argument from the given base and the
430   * appropriate prefix and suffix.
431   *
432   * @param  index  The index into the set of prefixes and suffixes.
433   * @param  base   The base name for the argument.
434   *
435   * @return  The constructed argument name.
436   */
437  private String genArgName(final int index, final String base)
438  {
439    final StringBuilder buffer = new StringBuilder();
440
441    if (serverNamePrefixes != null)
442    {
443      buffer.append(serverNamePrefixes[index]);
444
445      if (base.equals("saslOption"))
446      {
447        buffer.append("SASLOption");
448      }
449      else
450      {
451        buffer.append(StaticUtils.capitalize(base));
452      }
453    }
454    else
455    {
456      buffer.append(base);
457    }
458
459    if (serverNameSuffixes != null)
460    {
461      buffer.append(serverNameSuffixes[index]);
462    }
463
464    return buffer.toString();
465  }
466
467
468
469  /**
470   * Adds the arguments needed by this command-line tool to the provided
471   * argument parser which are not related to connecting or authenticating to
472   * the directory server.
473   *
474   * @param  parser  The argument parser to which the arguments should be added.
475   *
476   * @throws  ArgumentException  If a problem occurs while adding the arguments.
477   */
478  public abstract void addNonLDAPArguments(final ArgumentParser parser)
479         throws ArgumentException;
480
481
482
483  /**
484   * {@inheritDoc}
485   */
486  @Override()
487  public final void doExtendedArgumentValidation()
488         throws ArgumentException
489  {
490    doExtendedNonLDAPArgumentValidation();
491  }
492
493
494
495  /**
496   * Performs any necessary processing that should be done to ensure that the
497   * provided set of command-line arguments were valid.  This method will be
498   * called after the basic argument parsing has been performed and after all
499   * LDAP-specific argument validation has been processed, and immediately
500   * before the {@link CommandLineTool#doToolProcessing} method is invoked.
501   *
502   * @throws  ArgumentException  If there was a problem with the command-line
503   *                             arguments provided to this program.
504   */
505  public void doExtendedNonLDAPArgumentValidation()
506         throws ArgumentException
507  {
508    // No processing will be performed by default.
509  }
510
511
512
513  /**
514   * Retrieves the connection options that should be used for connections that
515   * are created with this command line tool.  Subclasses may override this
516   * method to use a custom set of connection options.
517   *
518   * @return  The connection options that should be used for connections that
519   *          are created with this command line tool.
520   */
521  public LDAPConnectionOptions getConnectionOptions()
522  {
523    return new LDAPConnectionOptions();
524  }
525
526
527
528  /**
529   * Retrieves a connection that may be used to communicate with the indicated
530   * directory server.
531   * <BR><BR>
532   * Note that this method is threadsafe and may be invoked by multiple threads
533   * accessing the same instance only while that instance is in the process of
534   * invoking the {@link #doToolProcessing} method.
535   *
536   * @param  serverIndex  The zero-based index of the server to which the
537   *                      connection should be established.
538   *
539   * @return  A connection that may be used to communicate with the indicated
540   *          directory server.
541   *
542   * @throws  LDAPException  If a problem occurs while creating the connection.
543   */
544  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
545  public final LDAPConnection getConnection(final int serverIndex)
546         throws LDAPException
547  {
548    final LDAPConnection connection = getUnauthenticatedConnection(serverIndex);
549
550    try
551    {
552      if (bindRequest[serverIndex] != null)
553      {
554        connection.bind(bindRequest[serverIndex]);
555      }
556    }
557    catch (LDAPException le)
558    {
559      Debug.debugException(le);
560      connection.close();
561      throw le;
562    }
563
564    return connection;
565  }
566
567
568
569  /**
570   * Retrieves an unauthenticated connection that may be used to communicate
571   * with the indicated directory server.
572   * <BR><BR>
573   * Note that this method is threadsafe and may be invoked by multiple threads
574   * accessing the same instance only while that instance is in the process of
575   * invoking the {@link #doToolProcessing} method.
576   *
577   * @param  serverIndex  The zero-based index of the server to which the
578   *                      connection should be established.
579   *
580   * @return  An unauthenticated connection that may be used to communicate with
581   *          the indicated directory server.
582   *
583   * @throws  LDAPException  If a problem occurs while creating the connection.
584   */
585  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
586  public final LDAPConnection getUnauthenticatedConnection(
587                                   final int serverIndex)
588         throws LDAPException
589  {
590    if (serverSet[serverIndex] == null)
591    {
592      serverSet[serverIndex]   = createServerSet(serverIndex);
593      bindRequest[serverIndex] = createBindRequest(serverIndex);
594    }
595
596    final LDAPConnection connection = serverSet[serverIndex].getConnection();
597
598    if (useStartTLS[serverIndex].isPresent())
599    {
600      try
601      {
602        final ExtendedResult extendedResult =
603             connection.processExtendedOperation(new StartTLSExtendedRequest(
604                  startTLSSocketFactory[serverIndex]));
605        if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
606        {
607          throw new LDAPException(extendedResult.getResultCode(),
608               ERR_LDAP_TOOL_START_TLS_FAILED.get(
609                    extendedResult.getDiagnosticMessage()));
610        }
611      }
612      catch (LDAPException le)
613      {
614        Debug.debugException(le);
615        connection.close();
616        throw le;
617      }
618    }
619
620    return connection;
621  }
622
623
624
625  /**
626   * Retrieves a connection pool that may be used to communicate with the
627   * indicated directory server.
628   * <BR><BR>
629   * Note that this method is threadsafe and may be invoked by multiple threads
630   * accessing the same instance only while that instance is in the process of
631   * invoking the {@link #doToolProcessing} method.
632   *
633   * @param  serverIndex         The zero-based index of the server to which the
634   *                             connection should be established.
635   * @param  initialConnections  The number of connections that should be
636   *                             initially established in the pool.
637   * @param  maxConnections      The maximum number of connections to maintain
638   *                             in the pool.
639   *
640   * @return  A connection that may be used to communicate with the indicated
641   *          directory server.
642   *
643   * @throws  LDAPException  If a problem occurs while creating the connection
644   *                         pool.
645   */
646  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
647  public final LDAPConnectionPool getConnectionPool(
648                                       final int serverIndex,
649                                       final int initialConnections,
650                                       final int maxConnections)
651            throws LDAPException
652  {
653    if (serverSet[serverIndex] == null)
654    {
655      serverSet[serverIndex]   = createServerSet(serverIndex);
656      bindRequest[serverIndex] = createBindRequest(serverIndex);
657    }
658
659    PostConnectProcessor postConnectProcessor = null;
660    if (useStartTLS[serverIndex].isPresent())
661    {
662      postConnectProcessor = new StartTLSPostConnectProcessor(
663           startTLSSocketFactory[serverIndex]);
664    }
665
666    return new LDAPConnectionPool(serverSet[serverIndex],
667         bindRequest[serverIndex], initialConnections, maxConnections,
668         postConnectProcessor);
669  }
670
671
672
673  /**
674   * Creates the server set to use when creating connections or connection
675   * pools.
676   *
677   * @param  serverIndex  The zero-based index of the server to which the
678   *                      connection should be established.
679   *
680   * @return  The server set to use when creating connections or connection
681   *          pools.
682   *
683   * @throws  LDAPException  If a problem occurs while creating the server set.
684   */
685  public final ServerSet createServerSet(final int serverIndex)
686         throws LDAPException
687  {
688    final SSLUtil sslUtil = createSSLUtil(serverIndex);
689
690    SocketFactory socketFactory = null;
691    if (useSSL[serverIndex].isPresent())
692    {
693      try
694      {
695        socketFactory = sslUtil.createSSLSocketFactory();
696      }
697      catch (Exception e)
698      {
699        Debug.debugException(e);
700        throw new LDAPException(ResultCode.LOCAL_ERROR,
701             ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
702                  StaticUtils.getExceptionMessage(e)), e);
703      }
704    }
705    else if (useStartTLS[serverIndex].isPresent())
706    {
707      try
708      {
709        startTLSSocketFactory[serverIndex] = sslUtil.createSSLSocketFactory();
710      }
711      catch (Exception e)
712      {
713        Debug.debugException(e);
714        throw new LDAPException(ResultCode.LOCAL_ERROR,
715             ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
716                  StaticUtils.getExceptionMessage(e)), e);
717      }
718    }
719
720    return new SingleServerSet(host[serverIndex].getValue(),
721         port[serverIndex].getValue(), socketFactory, getConnectionOptions());
722  }
723
724
725
726  /**
727   * Creates the SSLUtil instance to use for secure communication.
728   *
729   * @param  serverIndex  The zero-based index of the server to which the
730   *                      connection should be established.
731   *
732   * @return  The SSLUtil instance to use for secure communication, or
733   *          {@code null} if secure communication is not needed.
734   *
735   * @throws  LDAPException  If a problem occurs while creating the SSLUtil
736   *                         instance.
737   */
738  public final SSLUtil createSSLUtil(final int serverIndex)
739         throws LDAPException
740  {
741    if (useSSL[serverIndex].isPresent() || useStartTLS[serverIndex].isPresent())
742    {
743      KeyManager keyManager = null;
744      if (keyStorePath[serverIndex].isPresent())
745      {
746        char[] pw = null;
747        if (keyStorePassword[serverIndex].isPresent())
748        {
749          pw = keyStorePassword[serverIndex].getValue().toCharArray();
750        }
751        else if (keyStorePasswordFile[serverIndex].isPresent())
752        {
753          try
754          {
755            pw = keyStorePasswordFile[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_KEY_STORE_PASSWORD.get(
763                      StaticUtils.getExceptionMessage(e)), e);
764          }
765        }
766
767        try
768        {
769          keyManager = new KeyStoreKeyManager(
770               keyStorePath[serverIndex].getValue(), pw,
771               keyStoreFormat[serverIndex].getValue(),
772               certificateNickname[serverIndex].getValue());
773        }
774        catch (Exception e)
775        {
776          Debug.debugException(e);
777          throw new LDAPException(ResultCode.LOCAL_ERROR,
778               ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
779                    StaticUtils.getExceptionMessage(e)), e);
780        }
781      }
782
783      TrustManager trustManager;
784      if (trustAll[serverIndex].isPresent())
785      {
786        trustManager = new TrustAllTrustManager(false);
787      }
788      else if (trustStorePath[serverIndex].isPresent())
789      {
790        char[] pw = null;
791        if (trustStorePassword[serverIndex].isPresent())
792        {
793          pw = trustStorePassword[serverIndex].getValue().toCharArray();
794        }
795        else if (trustStorePasswordFile[serverIndex].isPresent())
796        {
797          try
798          {
799            pw = trustStorePasswordFile[serverIndex].getNonBlankFileLines().
800                 get(0).toCharArray();
801          }
802          catch (Exception e)
803          {
804            Debug.debugException(e);
805            throw new LDAPException(ResultCode.LOCAL_ERROR,
806                 ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
807                      StaticUtils.getExceptionMessage(e)), e);
808          }
809        }
810
811        trustManager = new TrustStoreTrustManager(
812             trustStorePath[serverIndex].getValue(), pw,
813             trustStoreFormat[serverIndex].getValue(), true);
814      }
815      else
816      {
817        trustManager = promptTrustManager.get();
818        if (trustManager == null)
819        {
820          final PromptTrustManager m = new PromptTrustManager();
821          promptTrustManager.compareAndSet(null, m);
822          trustManager = promptTrustManager.get();
823        }
824      }
825
826      return new SSLUtil(keyManager, trustManager);
827    }
828    else
829    {
830      return null;
831    }
832  }
833
834
835
836  /**
837   * Creates the bind request to use to authenticate to the indicated server.
838   *
839   * @param  serverIndex  The zero-based index of the server to which the
840   *                      connection should be established.
841   *
842   * @return  The bind request to use to authenticate to the indicated server,
843   *          or {@code null} if no bind should be performed.
844   *
845   * @throws  LDAPException  If a problem occurs while creating the bind
846   *                         request.
847   */
848  public final BindRequest createBindRequest(final int serverIndex)
849         throws LDAPException
850  {
851    final String pw;
852    if (bindPassword[serverIndex].isPresent())
853    {
854      pw = bindPassword[serverIndex].getValue();
855    }
856    else if (bindPasswordFile[serverIndex].isPresent())
857    {
858      try
859      {
860        pw = bindPasswordFile[serverIndex].getNonBlankFileLines().get(0);
861      }
862      catch (Exception e)
863      {
864        Debug.debugException(e);
865        throw new LDAPException(ResultCode.LOCAL_ERROR,
866             ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
867                  StaticUtils.getExceptionMessage(e)), e);
868      }
869    }
870    else
871    {
872      pw = null;
873    }
874
875    if (saslOption[serverIndex].isPresent())
876    {
877      final String dnStr;
878      if (bindDN[serverIndex].isPresent())
879      {
880        dnStr = bindDN[serverIndex].getValue().toString();
881      }
882      else
883      {
884        dnStr = null;
885      }
886
887      return SASLUtils.createBindRequest(dnStr, pw, null,
888           saslOption[serverIndex].getValues());
889    }
890    else if (bindDN[serverIndex].isPresent())
891    {
892      return new SimpleBindRequest(bindDN[serverIndex].getValue(), pw);
893    }
894    else
895    {
896      return null;
897    }
898  }
899}