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