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