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