001/*
002 * Copyright 2008-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-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.ArrayList;
027import java.util.Collections;
028import java.util.LinkedHashSet;
029import java.util.List;
030import java.util.Set;
031import java.util.concurrent.atomic.AtomicReference;
032import javax.net.SocketFactory;
033import javax.net.ssl.KeyManager;
034import javax.net.ssl.SSLSocketFactory;
035import javax.net.ssl.TrustManager;
036
037import com.unboundid.ldap.sdk.AggregatePostConnectProcessor;
038import com.unboundid.ldap.sdk.BindRequest;
039import com.unboundid.ldap.sdk.Control;
040import com.unboundid.ldap.sdk.EXTERNALBindRequest;
041import com.unboundid.ldap.sdk.ExtendedResult;
042import com.unboundid.ldap.sdk.LDAPConnection;
043import com.unboundid.ldap.sdk.LDAPConnectionOptions;
044import com.unboundid.ldap.sdk.LDAPConnectionPool;
045import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck;
046import com.unboundid.ldap.sdk.LDAPException;
047import com.unboundid.ldap.sdk.PostConnectProcessor;
048import com.unboundid.ldap.sdk.ResultCode;
049import com.unboundid.ldap.sdk.RoundRobinServerSet;
050import com.unboundid.ldap.sdk.ServerSet;
051import com.unboundid.ldap.sdk.SimpleBindRequest;
052import com.unboundid.ldap.sdk.SingleServerSet;
053import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor;
054import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
055import com.unboundid.util.args.Argument;
056import com.unboundid.util.args.ArgumentException;
057import com.unboundid.util.args.ArgumentParser;
058import com.unboundid.util.args.BooleanArgument;
059import com.unboundid.util.args.DNArgument;
060import com.unboundid.util.args.FileArgument;
061import com.unboundid.util.args.IntegerArgument;
062import com.unboundid.util.args.StringArgument;
063import com.unboundid.util.ssl.AggregateTrustManager;
064import com.unboundid.util.ssl.JVMDefaultTrustManager;
065import com.unboundid.util.ssl.KeyStoreKeyManager;
066import com.unboundid.util.ssl.PromptTrustManager;
067import com.unboundid.util.ssl.SSLUtil;
068import com.unboundid.util.ssl.TrustAllTrustManager;
069import com.unboundid.util.ssl.TrustStoreTrustManager;
070
071import static com.unboundid.util.UtilityMessages.*;
072
073
074
075/**
076 * This class provides a basis for developing command-line tools that
077 * communicate with an LDAP directory server.  It provides a common set of
078 * options for connecting and authenticating to a directory server, and then
079 * provides a mechanism for obtaining connections and connection pools to use
080 * when communicating with that server.
081 * <BR><BR>
082 * The arguments that this class supports include:
083 * <UL>
084 *   <LI>"-h {address}" or "--hostname {address}" -- Specifies the address of
085 *       the directory server.  If this isn't specified, then a default of
086 *       "localhost" will be used.</LI>
087 *   <LI>"-p {port}" or "--port {port}" -- Specifies the port number of the
088 *       directory server.  If this isn't specified, then a default port of 389
089 *       will be used.</LI>
090 *   <LI>"-D {bindDN}" or "--bindDN {bindDN}" -- Specifies the DN to use to bind
091 *       to the directory server using simple authentication.  If this isn't
092 *       specified, then simple authentication will not be performed.</LI>
093 *   <LI>"-w {password}" or "--bindPassword {password}" -- Specifies the
094 *       password to use when binding with simple authentication or a
095 *       password-based SASL mechanism.</LI>
096 *   <LI>"-j {path}" or "--bindPasswordFile {path}" -- Specifies the path to the
097 *       file containing the password to use when binding with simple
098 *       authentication or a password-based SASL mechanism.</LI>
099 *   <LI>"--promptForBindPassword" -- Indicates that the tool should
100 *       interactively prompt the user for the bind password.</LI>
101 *   <LI>"-Z" or "--useSSL" -- Indicates that the communication with the server
102 *       should be secured using SSL.</LI>
103 *   <LI>"-q" or "--useStartTLS" -- Indicates that the communication with the
104 *       server should be secured using StartTLS.</LI>
105 *   <LI>"-X" or "--trustAll" -- Indicates that the client should trust any
106 *       certificate that the server presents to it.</LI>
107 *   <LI>"-K {path}" or "--keyStorePath {path}" -- Specifies the path to the
108 *       key store to use to obtain client certificates.</LI>
109 *   <LI>"-W {password}" or "--keyStorePassword {password}" -- Specifies the
110 *       password to use to access the contents of the key store.</LI>
111 *   <LI>"-u {path}" or "--keyStorePasswordFile {path}" -- Specifies the path to
112 *       the file containing the password to use to access the contents of the
113 *       key store.</LI>
114 *   <LI>"--promptForKeyStorePassword" -- Indicates that the tool should
115 *       interactively prompt the user for the key store password.</LI>
116 *   <LI>"--keyStoreFormat {format}" -- Specifies the format to use for the key
117 *       store file.</LI>
118 *   <LI>"-P {path}" or "--trustStorePath {path}" -- Specifies the path to the
119 *       trust store to use when determining whether to trust server
120 *       certificates.</LI>
121 *   <LI>"-T {password}" or "--trustStorePassword {password}" -- Specifies the
122 *       password to use to access the contents of the trust store.</LI>
123 *   <LI>"-U {path}" or "--trustStorePasswordFile {path}" -- Specifies the path
124 *       to the file containing the password to use to access the contents of
125 *       the trust store.</LI>
126 *   <LI>"--promptForTrustStorePassword" -- Indicates that the tool should
127 *       interactively prompt the user for the trust store password.</LI>
128 *   <LI>"--trustStoreFormat {format}" -- Specifies the format to use for the
129 *       trust store file.</LI>
130 *   <LI>"-N {nickname}" or "--certNickname {nickname}" -- Specifies the
131 *       nickname of the client certificate to use when performing SSL client
132 *       authentication.</LI>
133 *   <LI>"-o {name=value}" or "--saslOption {name=value}" -- Specifies a SASL
134 *       option to use when performing SASL authentication.</LI>
135 * </UL>
136 * If SASL authentication is to be used, then a "mech" SASL option must be
137 * provided to specify the name of the SASL mechanism to use (e.g.,
138 * "--saslOption mech=EXTERNAL" indicates that the EXTERNAL mechanism should be
139 * used).  Depending on the SASL mechanism, additional SASL options may be
140 * required or optional.  They include:
141 * <UL>
142 *   <LI>
143 *     mech=ANONYMOUS
144 *     <UL>
145 *       <LI>Required SASL options:  </LI>
146 *       <LI>Optional SASL options:  trace</LI>
147 *     </UL>
148 *   </LI>
149 *   <LI>
150 *     mech=CRAM-MD5
151 *     <UL>
152 *       <LI>Required SASL options:  authID</LI>
153 *       <LI>Optional SASL options:  </LI>
154 *     </UL>
155 *   </LI>
156 *   <LI>
157 *     mech=DIGEST-MD5
158 *     <UL>
159 *       <LI>Required SASL options:  authID</LI>
160 *       <LI>Optional SASL options:  authzID, realm</LI>
161 *     </UL>
162 *   </LI>
163 *   <LI>
164 *     mech=EXTERNAL
165 *     <UL>
166 *       <LI>Required SASL options:  </LI>
167 *       <LI>Optional SASL options:  </LI>
168 *     </UL>
169 *   </LI>
170 *   <LI>
171 *     mech=GSSAPI
172 *     <UL>
173 *       <LI>Required SASL options:  authID</LI>
174 *       <LI>Optional SASL options:  authzID, configFile, debug, protocol,
175 *                realm, kdcAddress, useTicketCache, requireCache,
176 *                renewTGT, ticketCachePath</LI>
177 *     </UL>
178 *   </LI>
179 *   <LI>
180 *     mech=PLAIN
181 *     <UL>
182 *       <LI>Required SASL options:  authID</LI>
183 *       <LI>Optional SASL options:  authzID</LI>
184 *     </UL>
185 *   </LI>
186 * </UL>
187 * <BR><BR>
188 * Note that in general, methods in this class are not threadsafe.  However, the
189 * {@link #getConnection()} and {@link #getConnectionPool(int,int)} methods may
190 * be invoked concurrently by multiple threads accessing the same instance only
191 * while that instance is in the process of invoking the
192 * {@link #doToolProcessing()} method.
193 */
194@Extensible()
195@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
196public abstract class LDAPCommandLineTool
197       extends CommandLineTool
198{
199  // Arguments used to communicate with an LDAP directory server.
200  private BooleanArgument helpSASL                    = null;
201  private BooleanArgument promptForBindPassword       = null;
202  private BooleanArgument promptForKeyStorePassword   = null;
203  private BooleanArgument promptForTrustStorePassword = null;
204  private BooleanArgument trustAll                    = null;
205  private BooleanArgument useSASLExternal             = null;
206  private BooleanArgument useSSL                      = null;
207  private BooleanArgument useStartTLS                 = null;
208  private DNArgument      bindDN                      = null;
209  private FileArgument    bindPasswordFile            = null;
210  private FileArgument    keyStorePasswordFile        = null;
211  private FileArgument    trustStorePasswordFile      = null;
212  private IntegerArgument port                        = null;
213  private StringArgument  bindPassword                = null;
214  private StringArgument  certificateNickname         = null;
215  private StringArgument  host                        = null;
216  private StringArgument  keyStoreFormat              = null;
217  private StringArgument  keyStorePath                = null;
218  private StringArgument  keyStorePassword            = null;
219  private StringArgument  saslOption                  = null;
220  private StringArgument  trustStoreFormat            = null;
221  private StringArgument  trustStorePath              = null;
222  private StringArgument  trustStorePassword          = null;
223
224  // Variables used when creating and authenticating connections.
225  private BindRequest      bindRequest           = null;
226  private ServerSet        serverSet             = null;
227  private SSLSocketFactory startTLSSocketFactory = null;
228
229  // An atomic reference to an aggregate trust manager that will check a
230  // JVM-default set of trusted issuers, and then its own cache, before
231  // prompting the user about whether to trust the presented certificate chain.
232  // Re-using this trust manager will allow the tool to benefit from a common
233  // cache if multiple connections are needed.
234  private final AtomicReference<AggregateTrustManager> promptTrustManager;
235
236
237
238  /**
239   * Creates a new instance of this LDAP-enabled command-line tool with the
240   * provided information.
241   *
242   * @param  outStream  The output stream to use for standard output.  It may be
243   *                    {@code System.out} for the JVM's default standard output
244   *                    stream, {@code null} if no output should be generated,
245   *                    or a custom output stream if the output should be sent
246   *                    to an alternate location.
247   * @param  errStream  The output stream to use for standard error.  It may be
248   *                    {@code System.err} for the JVM's default standard error
249   *                    stream, {@code null} if no output should be generated,
250   *                    or a custom output stream if the output should be sent
251   *                    to an alternate location.
252   */
253  public LDAPCommandLineTool(final OutputStream outStream,
254                             final OutputStream errStream)
255  {
256    super(outStream, errStream);
257
258    promptTrustManager = new AtomicReference<>();
259  }
260
261
262
263  /**
264   * Retrieves a set containing the long identifiers used for LDAP-related
265   * arguments injected by this class.
266   *
267   * @param  tool  The tool to use to help make the determination.
268   *
269   * @return  A set containing the long identifiers used for LDAP-related
270   *          arguments injected by this class.
271   */
272  static Set<String> getLongLDAPArgumentIdentifiers(
273                          final LDAPCommandLineTool tool)
274  {
275    final LinkedHashSet<String> ids =
276         new LinkedHashSet<>(StaticUtils.computeMapCapacity(21));
277
278    ids.add("hostname");
279    ids.add("port");
280
281    if (tool.supportsAuthentication())
282    {
283      ids.add("bindDN");
284      ids.add("bindPassword");
285      ids.add("bindPasswordFile");
286      ids.add("promptForBindPassword");
287    }
288
289    ids.add("useSSL");
290    ids.add("useStartTLS");
291    ids.add("trustAll");
292    ids.add("keyStorePath");
293    ids.add("keyStorePassword");
294    ids.add("keyStorePasswordFile");
295    ids.add("promptForKeyStorePassword");
296    ids.add("keyStoreFormat");
297    ids.add("trustStorePath");
298    ids.add("trustStorePassword");
299    ids.add("trustStorePasswordFile");
300    ids.add("promptForTrustStorePassword");
301    ids.add("trustStoreFormat");
302    ids.add("certNickname");
303
304    if (tool.supportsAuthentication())
305    {
306      ids.add("saslOption");
307      ids.add("useSASLExternal");
308      ids.add("helpSASL");
309    }
310
311    return Collections.unmodifiableSet(ids);
312  }
313
314
315
316  /**
317   * Retrieves a set containing any short identifiers that should be suppressed
318   * in the set of generic tool arguments so that they can be used by a
319   * tool-specific argument instead.
320   *
321   * @return  A set containing any short identifiers that should be suppressed
322   *          in the set of generic tool arguments so that they can be used by a
323   *          tool-specific argument instead.  It may be empty but must not be
324   *          {@code null}.
325   */
326  protected Set<Character> getSuppressedShortIdentifiers()
327  {
328    return Collections.emptySet();
329  }
330
331
332
333  /**
334   * Retrieves the provided character if it is not included in the set of
335   * suppressed short identifiers.
336   *
337   * @param  id  The character to return if it is not in the set of suppressed
338   *             short identifiers.  It must not be {@code null}.
339   *
340   * @return  The provided character, or {@code null} if it is in the set of
341   *          suppressed short identifiers.
342   */
343  private Character getShortIdentifierIfNotSuppressed(final Character id)
344  {
345    if (getSuppressedShortIdentifiers().contains(id))
346    {
347      return null;
348    }
349    else
350    {
351      return id;
352    }
353  }
354
355
356
357  /**
358   * {@inheritDoc}
359   */
360  @Override()
361  public final void addToolArguments(final ArgumentParser parser)
362         throws ArgumentException
363  {
364    final String argumentGroup;
365    final boolean supportsAuthentication = supportsAuthentication();
366    if (supportsAuthentication)
367    {
368      argumentGroup = INFO_LDAP_TOOL_ARG_GROUP_CONNECT_AND_AUTH.get();
369    }
370    else
371    {
372      argumentGroup = INFO_LDAP_TOOL_ARG_GROUP_CONNECT.get();
373    }
374
375
376    host = new StringArgument(getShortIdentifierIfNotSuppressed('h'),
377         "hostname", true, (supportsMultipleServers() ? 0 : 1),
378         INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(),
379         INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost");
380    host.setArgumentGroupName(argumentGroup);
381    parser.addArgument(host);
382
383    port = new IntegerArgument(getShortIdentifierIfNotSuppressed('p'), "port",
384         true, (supportsMultipleServers() ? 0 : 1),
385         INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(),
386         INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65_535, 389);
387    port.setArgumentGroupName(argumentGroup);
388    parser.addArgument(port);
389
390    if (supportsAuthentication)
391    {
392      bindDN = new DNArgument(getShortIdentifierIfNotSuppressed('D'), "bindDN",
393           false, 1, INFO_LDAP_TOOL_PLACEHOLDER_DN.get(),
394           INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get());
395      bindDN.setArgumentGroupName(argumentGroup);
396      if (includeAlternateLongIdentifiers())
397      {
398        bindDN.addLongIdentifier("bind-dn", true);
399      }
400      parser.addArgument(bindDN);
401
402      bindPassword = new StringArgument(getShortIdentifierIfNotSuppressed('w'),
403           "bindPassword", false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
404           INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get());
405      bindPassword.setSensitive(true);
406      bindPassword.setArgumentGroupName(argumentGroup);
407      if (includeAlternateLongIdentifiers())
408      {
409        bindPassword.addLongIdentifier("bind-password", true);
410      }
411      parser.addArgument(bindPassword);
412
413      bindPasswordFile = new FileArgument(
414           getShortIdentifierIfNotSuppressed('j'), "bindPasswordFile", false, 1,
415           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
416           INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true,
417           false);
418      bindPasswordFile.setArgumentGroupName(argumentGroup);
419      if (includeAlternateLongIdentifiers())
420      {
421        bindPasswordFile.addLongIdentifier("bind-password-file", true);
422      }
423      parser.addArgument(bindPasswordFile);
424
425      promptForBindPassword = new BooleanArgument(null, "promptForBindPassword",
426           1, INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_PROMPT.get());
427      promptForBindPassword.setArgumentGroupName(argumentGroup);
428      if (includeAlternateLongIdentifiers())
429      {
430        promptForBindPassword.addLongIdentifier("prompt-for-bind-password",
431             true);
432      }
433      parser.addArgument(promptForBindPassword);
434    }
435
436    useSSL = new BooleanArgument(getShortIdentifierIfNotSuppressed('Z'),
437         "useSSL", 1, INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get());
438    useSSL.setArgumentGroupName(argumentGroup);
439    if (includeAlternateLongIdentifiers())
440    {
441      useSSL.addLongIdentifier("use-ssl", true);
442    }
443    parser.addArgument(useSSL);
444
445    useStartTLS = new BooleanArgument(getShortIdentifierIfNotSuppressed('q'),
446         "useStartTLS", 1, INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get());
447    useStartTLS.setArgumentGroupName(argumentGroup);
448      if (includeAlternateLongIdentifiers())
449      {
450        useStartTLS.addLongIdentifier("use-starttls", true);
451        useStartTLS.addLongIdentifier("use-start-tls", true);
452      }
453    parser.addArgument(useStartTLS);
454
455    trustAll = new BooleanArgument(getShortIdentifierIfNotSuppressed('X'),
456         "trustAll", 1, INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get());
457    trustAll.setArgumentGroupName(argumentGroup);
458    if (includeAlternateLongIdentifiers())
459    {
460      trustAll.addLongIdentifier("trustAllCertificates", true);
461      trustAll.addLongIdentifier("trust-all", true);
462      trustAll.addLongIdentifier("trust-all-certificates", true);
463    }
464    parser.addArgument(trustAll);
465
466    keyStorePath = new StringArgument(getShortIdentifierIfNotSuppressed('K'),
467         "keyStorePath", false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
468         INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get());
469    keyStorePath.setArgumentGroupName(argumentGroup);
470    if (includeAlternateLongIdentifiers())
471    {
472      keyStorePath.addLongIdentifier("key-store-path", true);
473    }
474    parser.addArgument(keyStorePath);
475
476    keyStorePassword = new StringArgument(
477         getShortIdentifierIfNotSuppressed('W'), "keyStorePassword", false, 1,
478         INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
479         INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get());
480    keyStorePassword.setSensitive(true);
481    keyStorePassword.setArgumentGroupName(argumentGroup);
482    if (includeAlternateLongIdentifiers())
483    {
484      keyStorePassword.addLongIdentifier("keyStorePIN", true);
485      keyStorePassword.addLongIdentifier("key-store-password", true);
486      keyStorePassword.addLongIdentifier("key-store-pin", true);
487    }
488    parser.addArgument(keyStorePassword);
489
490    keyStorePasswordFile = new FileArgument(
491         getShortIdentifierIfNotSuppressed('u'), "keyStorePasswordFile", false,
492         1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
493         INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get());
494    keyStorePasswordFile.setArgumentGroupName(argumentGroup);
495    if (includeAlternateLongIdentifiers())
496    {
497      keyStorePasswordFile.addLongIdentifier("keyStorePINFile", true);
498      keyStorePasswordFile.addLongIdentifier("key-store-password-file", true);
499      keyStorePasswordFile.addLongIdentifier("key-store-pin-file", true);
500    }
501    parser.addArgument(keyStorePasswordFile);
502
503    promptForKeyStorePassword = new BooleanArgument(null,
504         "promptForKeyStorePassword", 1,
505         INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_PROMPT.get());
506    promptForKeyStorePassword.setArgumentGroupName(argumentGroup);
507    if (includeAlternateLongIdentifiers())
508    {
509      promptForKeyStorePassword.addLongIdentifier("promptForKeyStorePIN", true);
510      promptForKeyStorePassword.addLongIdentifier(
511           "prompt-for-key-store-password", true);
512      promptForKeyStorePassword.addLongIdentifier("prompt-for-key-store-pin",
513           true);
514    }
515    parser.addArgument(promptForKeyStorePassword);
516
517    keyStoreFormat = new StringArgument(null, "keyStoreFormat", false, 1,
518         INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
519         INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get());
520    keyStoreFormat.setArgumentGroupName(argumentGroup);
521    if (includeAlternateLongIdentifiers())
522    {
523      keyStoreFormat.addLongIdentifier("keyStoreType", true);
524      keyStoreFormat.addLongIdentifier("key-store-format", true);
525      keyStoreFormat.addLongIdentifier("key-store-type", true);
526    }
527    parser.addArgument(keyStoreFormat);
528
529    trustStorePath = new StringArgument(getShortIdentifierIfNotSuppressed('P'),
530         "trustStorePath", false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
531         INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get());
532    trustStorePath.setArgumentGroupName(argumentGroup);
533    if (includeAlternateLongIdentifiers())
534    {
535      trustStorePath.addLongIdentifier("trust-store-path", true);
536    }
537    parser.addArgument(trustStorePath);
538
539    trustStorePassword = new StringArgument(
540         getShortIdentifierIfNotSuppressed('T'), "trustStorePassword", false, 1,
541         INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
542         INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get());
543    trustStorePassword.setSensitive(true);
544    trustStorePassword.setArgumentGroupName(argumentGroup);
545    if (includeAlternateLongIdentifiers())
546    {
547      trustStorePassword.addLongIdentifier("trustStorePIN", true);
548      trustStorePassword.addLongIdentifier("trust-store-password", true);
549      trustStorePassword.addLongIdentifier("trust-store-pin", true);
550    }
551    parser.addArgument(trustStorePassword);
552
553    trustStorePasswordFile = new FileArgument(
554         getShortIdentifierIfNotSuppressed('U'), "trustStorePasswordFile",
555         false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
556         INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get());
557    trustStorePasswordFile.setArgumentGroupName(argumentGroup);
558    if (includeAlternateLongIdentifiers())
559    {
560      trustStorePasswordFile.addLongIdentifier("trustStorePINFile", true);
561      trustStorePasswordFile.addLongIdentifier("trust-store-password-file",
562           true);
563      trustStorePasswordFile.addLongIdentifier("trust-store-pin-file", true);
564    }
565    parser.addArgument(trustStorePasswordFile);
566
567    promptForTrustStorePassword = new BooleanArgument(null,
568         "promptForTrustStorePassword", 1,
569         INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_PROMPT.get());
570    promptForTrustStorePassword.setArgumentGroupName(argumentGroup);
571    if (includeAlternateLongIdentifiers())
572    {
573      promptForTrustStorePassword.addLongIdentifier("promptForTrustStorePIN",
574           true);
575      promptForTrustStorePassword.addLongIdentifier(
576           "prompt-for-trust-store-password", true);
577      promptForTrustStorePassword.addLongIdentifier(
578           "prompt-for-trust-store-pin", true);
579    }
580    parser.addArgument(promptForTrustStorePassword);
581
582    trustStoreFormat = new StringArgument(null, "trustStoreFormat", false, 1,
583         INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
584         INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get());
585    trustStoreFormat.setArgumentGroupName(argumentGroup);
586    if (includeAlternateLongIdentifiers())
587    {
588      trustStoreFormat.addLongIdentifier("trustStoreType", true);
589      trustStoreFormat.addLongIdentifier("trust-store-format", true);
590      trustStoreFormat.addLongIdentifier("trust-store-type", true);
591    }
592    parser.addArgument(trustStoreFormat);
593
594    certificateNickname = new StringArgument(
595         getShortIdentifierIfNotSuppressed('N'), "certNickname", false, 1,
596         INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(),
597         INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get());
598    certificateNickname.setArgumentGroupName(argumentGroup);
599    if (includeAlternateLongIdentifiers())
600    {
601      certificateNickname.addLongIdentifier("certificateNickname", true);
602      certificateNickname.addLongIdentifier("cert-nickname", true);
603      certificateNickname.addLongIdentifier("certificate-nickname", true);
604    }
605    parser.addArgument(certificateNickname);
606
607    if (supportsAuthentication)
608    {
609      saslOption = new StringArgument(getShortIdentifierIfNotSuppressed('o'),
610           "saslOption", false, 0, INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(),
611           INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get());
612      saslOption.setArgumentGroupName(argumentGroup);
613      if (includeAlternateLongIdentifiers())
614      {
615        saslOption.addLongIdentifier("sasl-option", true);
616      }
617      parser.addArgument(saslOption);
618
619      useSASLExternal = new BooleanArgument(null, "useSASLExternal", 1,
620           INFO_LDAP_TOOL_DESCRIPTION_USE_SASL_EXTERNAL.get());
621      useSASLExternal.setArgumentGroupName(argumentGroup);
622      if (includeAlternateLongIdentifiers())
623      {
624        useSASLExternal.addLongIdentifier("use-sasl-external", true);
625      }
626      parser.addArgument(useSASLExternal);
627
628      if (supportsSASLHelp())
629      {
630        helpSASL = new BooleanArgument(null, "helpSASL",
631             INFO_LDAP_TOOL_DESCRIPTION_HELP_SASL.get());
632        helpSASL.setArgumentGroupName(argumentGroup);
633        if (includeAlternateLongIdentifiers())
634        {
635          helpSASL.addLongIdentifier("help-sasl", true);
636        }
637        helpSASL.setUsageArgument(true);
638        parser.addArgument(helpSASL);
639        setHelpSASLArgument(helpSASL);
640      }
641    }
642
643
644    // Both useSSL and useStartTLS cannot be used together.
645    parser.addExclusiveArgumentSet(useSSL, useStartTLS);
646
647    // Only one option may be used for specifying the key store password.
648    parser.addExclusiveArgumentSet(keyStorePassword, keyStorePasswordFile,
649         promptForKeyStorePassword);
650
651    // Only one option may be used for specifying the trust store password.
652    parser.addExclusiveArgumentSet(trustStorePassword, trustStorePasswordFile,
653         promptForTrustStorePassword);
654
655    // It doesn't make sense to provide a trust store path if any server
656    // certificate should be trusted.
657    parser.addExclusiveArgumentSet(trustAll, trustStorePath);
658
659    // If a key store password is provided, then a key store path must have also
660    // been provided.
661    parser.addDependentArgumentSet(keyStorePassword, keyStorePath);
662    parser.addDependentArgumentSet(keyStorePasswordFile, keyStorePath);
663    parser.addDependentArgumentSet(promptForKeyStorePassword, keyStorePath);
664
665    // If a trust store password is provided, then a trust store path must have
666    // also been provided.
667    parser.addDependentArgumentSet(trustStorePassword, trustStorePath);
668    parser.addDependentArgumentSet(trustStorePasswordFile, trustStorePath);
669    parser.addDependentArgumentSet(promptForTrustStorePassword, trustStorePath);
670
671    // If a key or trust store path is provided, then the tool must either use
672    // SSL or StartTLS.
673    parser.addDependentArgumentSet(keyStorePath, useSSL, useStartTLS);
674    parser.addDependentArgumentSet(trustStorePath, useSSL, useStartTLS);
675
676    // If the tool should trust all server certificates, then the tool must
677    // either use SSL or StartTLS.
678    parser.addDependentArgumentSet(trustAll, useSSL, useStartTLS);
679
680    if (supportsAuthentication)
681    {
682      // If a bind DN was provided, then a bind password must have also been
683      // provided unless defaultToPromptForBindPassword returns true.
684      if (! defaultToPromptForBindPassword())
685      {
686        parser.addDependentArgumentSet(bindDN, bindPassword, bindPasswordFile,
687             promptForBindPassword);
688      }
689
690      // The bindDN, saslOption, and useSASLExternal arguments are all mutually
691      // exclusive.
692      parser.addExclusiveArgumentSet(bindDN, saslOption, useSASLExternal);
693
694      // Only one option may be used for specifying the bind password.
695      parser.addExclusiveArgumentSet(bindPassword, bindPasswordFile,
696           promptForBindPassword);
697
698      // If a bind password was provided, then the a bind DN or SASL option
699      // must have also been provided.
700      parser.addDependentArgumentSet(bindPassword, bindDN, saslOption);
701      parser.addDependentArgumentSet(bindPasswordFile, bindDN, saslOption);
702      parser.addDependentArgumentSet(promptForBindPassword, bindDN, saslOption);
703    }
704
705    addNonLDAPArguments(parser);
706  }
707
708
709
710  /**
711   * Adds the arguments needed by this command-line tool to the provided
712   * argument parser which are not related to connecting or authenticating to
713   * the directory server.
714   *
715   * @param  parser  The argument parser to which the arguments should be added.
716   *
717   * @throws  ArgumentException  If a problem occurs while adding the arguments.
718   */
719  public abstract void addNonLDAPArguments(ArgumentParser parser)
720         throws ArgumentException;
721
722
723
724  /**
725   * {@inheritDoc}
726   */
727  @Override()
728  public final void doExtendedArgumentValidation()
729         throws ArgumentException
730  {
731    // If more than one hostname or port number was provided, then make sure
732    // that the same number of values were provided for each.
733    if ((host.getValues().size() > 1) || (port.getValues().size() > 1))
734    {
735      if (host.getValues().size() != port.getValues().size())
736      {
737        throw new ArgumentException(
738             ERR_LDAP_TOOL_HOST_PORT_COUNT_MISMATCH.get(
739                  host.getLongIdentifier(), port.getLongIdentifier()));
740      }
741    }
742
743
744    doExtendedNonLDAPArgumentValidation();
745  }
746
747
748
749  /**
750   * Indicates whether this tool should provide the arguments that allow it to
751   * bind via simple or SASL authentication.
752   *
753   * @return  {@code true} if this tool should provide the arguments that allow
754   *          it to bind via simple or SASL authentication, or {@code false} if
755   *          not.
756   */
757  protected boolean supportsAuthentication()
758  {
759    return true;
760  }
761
762
763
764  /**
765   * Indicates whether this tool should default to interactively prompting for
766   * the bind password if a password is required but no argument was provided
767   * to indicate how to get the password.
768   *
769   * @return  {@code true} if this tool should default to interactively
770   *          prompting for the bind password, or {@code false} if not.
771   */
772  protected boolean defaultToPromptForBindPassword()
773  {
774    return false;
775  }
776
777
778
779  /**
780   * Indicates whether this tool should provide a "--help-sasl" argument that
781   * provides information about the supported SASL mechanisms and their
782   * associated properties.
783   *
784   * @return  {@code true} if this tool should provide a "--help-sasl" argument,
785   *          or {@code false} if not.
786   */
787  protected boolean supportsSASLHelp()
788  {
789    return true;
790  }
791
792
793
794  /**
795   * Indicates whether the LDAP-specific arguments should include alternate
796   * versions of all long identifiers that consist of multiple words so that
797   * they are available in both camelCase and dash-separated versions.
798   *
799   * @return  {@code true} if this tool should provide multiple versions of
800   *          long identifiers for LDAP-specific arguments, or {@code false} if
801   *          not.
802   */
803  protected boolean includeAlternateLongIdentifiers()
804  {
805    return false;
806  }
807
808
809
810  /**
811   * Retrieves a set of controls that should be included in any bind request
812   * generated by this tool.
813   *
814   * @return  A set of controls that should be included in any bind request
815   *          generated by this tool.  It may be {@code null} or empty if no
816   *          controls should be included in the bind request.
817   */
818  protected List<Control> getBindControls()
819  {
820    return null;
821  }
822
823
824
825  /**
826   * Indicates whether this tool supports creating connections to multiple
827   * servers.  If it is to support multiple servers, then the "--hostname" and
828   * "--port" arguments will be allowed to be provided multiple times, and
829   * will be required to be provided the same number of times.  The same type of
830   * communication security and bind credentials will be used for all servers.
831   *
832   * @return  {@code true} if this tool supports creating connections to
833   *          multiple servers, or {@code false} if not.
834   */
835  protected boolean supportsMultipleServers()
836  {
837    return false;
838  }
839
840
841
842  /**
843   * Performs any necessary processing that should be done to ensure that the
844   * provided set of command-line arguments were valid.  This method will be
845   * called after the basic argument parsing has been performed and after all
846   * LDAP-specific argument validation has been processed, and immediately
847   * before the {@link CommandLineTool#doToolProcessing} method is invoked.
848   *
849   * @throws  ArgumentException  If there was a problem with the command-line
850   *                             arguments provided to this program.
851   */
852  public void doExtendedNonLDAPArgumentValidation()
853         throws ArgumentException
854  {
855    // No processing will be performed by default.
856  }
857
858
859
860  /**
861   * Retrieves the connection options that should be used for connections that
862   * are created with this command line tool.  Subclasses may override this
863   * method to use a custom set of connection options.
864   *
865   * @return  The connection options that should be used for connections that
866   *          are created with this command line tool.
867   */
868  public LDAPConnectionOptions getConnectionOptions()
869  {
870    return new LDAPConnectionOptions();
871  }
872
873
874
875  /**
876   * Retrieves a connection that may be used to communicate with the target
877   * directory server.
878   * <BR><BR>
879   * Note that this method is threadsafe and may be invoked by multiple threads
880   * accessing the same instance only while that instance is in the process of
881   * invoking the {@link #doToolProcessing} method.
882   *
883   * @return  A connection that may be used to communicate with the target
884   *          directory server.
885   *
886   * @throws  LDAPException  If a problem occurs while creating the connection.
887   */
888  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
889  public final LDAPConnection getConnection()
890         throws LDAPException
891  {
892    final LDAPConnection connection = getUnauthenticatedConnection();
893
894    try
895    {
896      if (bindRequest != null)
897      {
898        connection.bind(bindRequest);
899      }
900    }
901    catch (final LDAPException le)
902    {
903      Debug.debugException(le);
904      connection.close();
905      throw le;
906    }
907
908    return connection;
909  }
910
911
912
913  /**
914   * Retrieves an unauthenticated connection that may be used to communicate
915   * with the target directory server.
916   * <BR><BR>
917   * Note that this method is threadsafe and may be invoked by multiple threads
918   * accessing the same instance only while that instance is in the process of
919   * invoking the {@link #doToolProcessing} method.
920   *
921   * @return  An unauthenticated connection that may be used to communicate with
922   *          the target directory server.
923   *
924   * @throws  LDAPException  If a problem occurs while creating the connection.
925   */
926  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
927  public final LDAPConnection getUnauthenticatedConnection()
928         throws LDAPException
929  {
930    if (serverSet == null)
931    {
932      serverSet   = createServerSet();
933      bindRequest = createBindRequest();
934    }
935
936    final LDAPConnection connection = serverSet.getConnection();
937
938    if (useStartTLS.isPresent())
939    {
940      try
941      {
942        final ExtendedResult extendedResult =
943             connection.processExtendedOperation(
944                  new StartTLSExtendedRequest(startTLSSocketFactory));
945        if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
946        {
947          throw new LDAPException(extendedResult.getResultCode(),
948               ERR_LDAP_TOOL_START_TLS_FAILED.get(
949                    extendedResult.getDiagnosticMessage()));
950        }
951      }
952      catch (final LDAPException le)
953      {
954        Debug.debugException(le);
955        connection.close();
956        throw le;
957      }
958    }
959
960    return connection;
961  }
962
963
964
965  /**
966   * Retrieves a connection pool that may be used to communicate with the target
967   * directory server.
968   * <BR><BR>
969   * Note that this method is threadsafe and may be invoked by multiple threads
970   * accessing the same instance only while that instance is in the process of
971   * invoking the {@link #doToolProcessing} method.
972   *
973   * @param  initialConnections  The number of connections that should be
974   *                             initially established in the pool.
975   * @param  maxConnections      The maximum number of connections to maintain
976   *                             in the pool.
977   *
978   * @return  A connection that may be used to communicate with the target
979   *          directory server.
980   *
981   * @throws  LDAPException  If a problem occurs while creating the connection
982   *                         pool.
983   */
984  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
985  public final LDAPConnectionPool getConnectionPool(
986                                       final int initialConnections,
987                                       final int maxConnections)
988            throws LDAPException
989  {
990    return getConnectionPool(initialConnections, maxConnections, 1, null, null,
991         true, null);
992  }
993
994
995
996  /**
997   * Retrieves a connection pool that may be used to communicate with the target
998   * directory server.
999   * <BR><BR>
1000   * Note that this method is threadsafe and may be invoked by multiple threads
1001   * accessing the same instance only while that instance is in the process of
1002   * invoking the {@link #doToolProcessing} method.
1003   *
1004   * @param  initialConnections       The number of connections that should be
1005   *                                  initially established in the pool.
1006   * @param  maxConnections           The maximum number of connections to
1007   *                                  maintain in the pool.
1008   * @param  initialConnectThreads    The number of concurrent threads to use to
1009   *                                  establish the initial set of connections.
1010   *                                  A value greater than one indicates that
1011   *                                  the attempt to establish connections
1012   *                                  should be parallelized.
1013   * @param  beforeStartTLSProcessor  An optional post-connect processor that
1014   *                                  should be used for the connection pool and
1015   *                                  should be invoked before any StartTLS
1016   *                                  post-connect processor that may be needed
1017   *                                  based on the selected arguments.  It may
1018   *                                  be {@code null} if no such post-connect
1019   *                                  processor is needed.
1020   * @param  afterStartTLSProcessor   An optional post-connect processor that
1021   *                                  should be used for the connection pool and
1022   *                                  should be invoked after any StartTLS
1023   *                                  post-connect processor that may be needed
1024   *                                  based on the selected arguments.  It may
1025   *                                  be {@code null} if no such post-connect
1026   *                                  processor is needed.
1027   * @param  throwOnConnectFailure    If an exception should be thrown if a
1028   *                                  problem is encountered while attempting to
1029   *                                  create the specified initial number of
1030   *                                  connections.  If {@code true}, then the
1031   *                                  attempt to create the pool will fail if
1032   *                                  any connection cannot be established.  If
1033   *                                  {@code false}, then the pool will be
1034   *                                  created but may have fewer than the
1035   *                                  initial number of connections (or possibly
1036   *                                  no connections).
1037   * @param  healthCheck              An optional health check that should be
1038   *                                  configured for the connection pool.  It
1039   *                                  may be {@code null} if the default health
1040   *                                  checking should be performed.
1041   *
1042   * @return  A connection that may be used to communicate with the target
1043   *          directory server.
1044   *
1045   * @throws  LDAPException  If a problem occurs while creating the connection
1046   *                         pool.
1047   */
1048  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
1049  public final LDAPConnectionPool getConnectionPool(
1050                    final int initialConnections, final int maxConnections,
1051                    final int initialConnectThreads,
1052                    final PostConnectProcessor beforeStartTLSProcessor,
1053                    final PostConnectProcessor afterStartTLSProcessor,
1054                    final boolean throwOnConnectFailure,
1055                    final LDAPConnectionPoolHealthCheck healthCheck)
1056            throws LDAPException
1057  {
1058    // Create the server set and bind request, if necessary.
1059    if (serverSet == null)
1060    {
1061      serverSet   = createServerSet();
1062      bindRequest = createBindRequest();
1063    }
1064
1065
1066    // Prepare the post-connect processor for the pool.
1067    final ArrayList<PostConnectProcessor> pcpList = new ArrayList<>(3);
1068    if (beforeStartTLSProcessor != null)
1069    {
1070      pcpList.add(beforeStartTLSProcessor);
1071    }
1072
1073    if (useStartTLS.isPresent())
1074    {
1075      pcpList.add(new StartTLSPostConnectProcessor(startTLSSocketFactory));
1076    }
1077
1078    if (afterStartTLSProcessor != null)
1079    {
1080      pcpList.add(afterStartTLSProcessor);
1081    }
1082
1083    final PostConnectProcessor postConnectProcessor;
1084    switch (pcpList.size())
1085    {
1086      case 0:
1087        postConnectProcessor = null;
1088        break;
1089      case 1:
1090        postConnectProcessor = pcpList.get(0);
1091        break;
1092      default:
1093        postConnectProcessor = new AggregatePostConnectProcessor(pcpList);
1094        break;
1095    }
1096
1097    return new LDAPConnectionPool(serverSet, bindRequest, initialConnections,
1098         maxConnections, initialConnectThreads, postConnectProcessor,
1099         throwOnConnectFailure, healthCheck);
1100  }
1101
1102
1103
1104  /**
1105   * Creates the server set to use when creating connections or connection
1106   * pools.
1107   *
1108   * @return  The server set to use when creating connections or connection
1109   *          pools.
1110   *
1111   * @throws  LDAPException  If a problem occurs while creating the server set.
1112   */
1113  public ServerSet createServerSet()
1114         throws LDAPException
1115  {
1116    final SSLUtil sslUtil = createSSLUtil();
1117
1118    SocketFactory socketFactory = null;
1119    if (useSSL.isPresent())
1120    {
1121      try
1122      {
1123        socketFactory = sslUtil.createSSLSocketFactory();
1124      }
1125      catch (final Exception e)
1126      {
1127        Debug.debugException(e);
1128        throw new LDAPException(ResultCode.LOCAL_ERROR,
1129             ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
1130                  StaticUtils.getExceptionMessage(e)),
1131             e);
1132      }
1133    }
1134    else if (useStartTLS.isPresent())
1135    {
1136      try
1137      {
1138        startTLSSocketFactory = sslUtil.createSSLSocketFactory();
1139      }
1140      catch (final Exception e)
1141      {
1142        Debug.debugException(e);
1143        throw new LDAPException(ResultCode.LOCAL_ERROR,
1144             ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
1145                  StaticUtils.getExceptionMessage(e)),
1146             e);
1147      }
1148    }
1149
1150    if (host.getValues().size() == 1)
1151    {
1152      return new SingleServerSet(host.getValue(), port.getValue(),
1153                                 socketFactory, getConnectionOptions());
1154    }
1155    else
1156    {
1157      final List<String>  hostList = host.getValues();
1158      final List<Integer> portList = port.getValues();
1159
1160      final String[] hosts = new String[hostList.size()];
1161      final int[]    ports = new int[hosts.length];
1162
1163      for (int i=0; i < hosts.length; i++)
1164      {
1165        hosts[i] = hostList.get(i);
1166        ports[i] = portList.get(i);
1167      }
1168
1169      return new RoundRobinServerSet(hosts, ports, socketFactory,
1170                                     getConnectionOptions());
1171    }
1172  }
1173
1174
1175
1176  /**
1177   * Creates the SSLUtil instance to use for secure communication.
1178   *
1179   * @return  The SSLUtil instance to use for secure communication, or
1180   *          {@code null} if secure communication is not needed.
1181   *
1182   * @throws  LDAPException  If a problem occurs while creating the SSLUtil
1183   *                         instance.
1184   */
1185  public SSLUtil createSSLUtil()
1186         throws LDAPException
1187  {
1188    return createSSLUtil(false);
1189  }
1190
1191
1192
1193  /**
1194   * Creates the SSLUtil instance to use for secure communication.
1195   *
1196   * @param  force  Indicates whether to create the SSLUtil object even if
1197   *                neither the "--useSSL" nor the "--useStartTLS" argument was
1198   *                provided.  The key store and/or trust store paths must still
1199   *                have been provided.  This may be useful for tools that
1200   *                accept SSL-based communication but do not themselves intend
1201   *                to perform SSL-based communication as an LDAP client.
1202   *
1203   * @return  The SSLUtil instance to use for secure communication, or
1204   *          {@code null} if secure communication is not needed.
1205   *
1206   * @throws  LDAPException  If a problem occurs while creating the SSLUtil
1207   *                         instance.
1208   */
1209  public SSLUtil createSSLUtil(final boolean force)
1210         throws LDAPException
1211  {
1212    if (force || useSSL.isPresent() || useStartTLS.isPresent())
1213    {
1214      KeyManager keyManager = null;
1215      if (keyStorePath.isPresent())
1216      {
1217        char[] pw = null;
1218        if (keyStorePassword.isPresent())
1219        {
1220          pw = keyStorePassword.getValue().toCharArray();
1221        }
1222        else if (keyStorePasswordFile.isPresent())
1223        {
1224          try
1225          {
1226            pw = getPasswordFileReader().readPassword(
1227                 keyStorePasswordFile.getValue());
1228          }
1229          catch (final Exception e)
1230          {
1231            Debug.debugException(e);
1232            throw new LDAPException(ResultCode.LOCAL_ERROR,
1233                 ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get(
1234                      StaticUtils.getExceptionMessage(e)),
1235                 e);
1236          }
1237        }
1238        else if (promptForKeyStorePassword.isPresent())
1239        {
1240          getOut().print(INFO_LDAP_TOOL_ENTER_KEY_STORE_PASSWORD.get());
1241          pw = StaticUtils.toUTF8String(
1242               PasswordReader.readPassword()).toCharArray();
1243          getOut().println();
1244        }
1245
1246        try
1247        {
1248          keyManager = new KeyStoreKeyManager(keyStorePath.getValue(), pw,
1249               keyStoreFormat.getValue(), certificateNickname.getValue());
1250        }
1251        catch (final Exception e)
1252        {
1253          Debug.debugException(e);
1254          throw new LDAPException(ResultCode.LOCAL_ERROR,
1255               ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
1256                    StaticUtils.getExceptionMessage(e)),
1257               e);
1258        }
1259      }
1260
1261      final TrustManager tm;
1262      if (trustAll.isPresent())
1263      {
1264        tm = new TrustAllTrustManager(false);
1265      }
1266      else if (trustStorePath.isPresent())
1267      {
1268        char[] pw = null;
1269        if (trustStorePassword.isPresent())
1270        {
1271          pw = trustStorePassword.getValue().toCharArray();
1272        }
1273        else if (trustStorePasswordFile.isPresent())
1274        {
1275          try
1276          {
1277            pw = getPasswordFileReader().readPassword(
1278                 trustStorePasswordFile.getValue());
1279          }
1280          catch (final Exception e)
1281          {
1282            Debug.debugException(e);
1283            throw new LDAPException(ResultCode.LOCAL_ERROR,
1284                 ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
1285                      StaticUtils.getExceptionMessage(e)), e);
1286          }
1287        }
1288        else if (promptForTrustStorePassword.isPresent())
1289        {
1290          getOut().print(INFO_LDAP_TOOL_ENTER_TRUST_STORE_PASSWORD.get());
1291          pw = StaticUtils.toUTF8String(
1292               PasswordReader.readPassword()).toCharArray();
1293          getOut().println();
1294        }
1295
1296        tm = new TrustStoreTrustManager(trustStorePath.getValue(), pw,
1297             trustStoreFormat.getValue(), true);
1298      }
1299      else if (promptTrustManager.get() != null)
1300      {
1301        tm = promptTrustManager.get();
1302      }
1303      else
1304      {
1305        final ArrayList<String> expectedAddresses = new ArrayList<>(5);
1306        if (useSSL.isPresent() || useStartTLS.isPresent())
1307        {
1308          expectedAddresses.addAll(host.getValues());
1309        }
1310
1311        final AggregateTrustManager atm = new AggregateTrustManager(false,
1312             JVMDefaultTrustManager.getInstance(),
1313             new PromptTrustManager(null, true, expectedAddresses, null,
1314                  null));
1315        if (promptTrustManager.compareAndSet(null, atm))
1316        {
1317          tm = atm;
1318        }
1319        else
1320        {
1321          tm = promptTrustManager.get();
1322        }
1323      }
1324
1325      return new SSLUtil(keyManager, tm);
1326    }
1327    else
1328    {
1329      return null;
1330    }
1331  }
1332
1333
1334
1335  /**
1336   * Creates the bind request to use to authenticate to the server.
1337   *
1338   * @return  The bind request to use to authenticate to the server, or
1339   *          {@code null} if no bind should be performed.
1340   *
1341   * @throws  LDAPException  If a problem occurs while creating the bind
1342   *                         request.
1343   */
1344  public BindRequest createBindRequest()
1345         throws LDAPException
1346  {
1347    if (! supportsAuthentication())
1348    {
1349      return null;
1350    }
1351
1352    final Control[] bindControls;
1353    final List<Control> bindControlList = getBindControls();
1354    if ((bindControlList == null) || bindControlList.isEmpty())
1355    {
1356      bindControls = StaticUtils.NO_CONTROLS;
1357    }
1358    else
1359    {
1360      bindControls = new Control[bindControlList.size()];
1361      bindControlList.toArray(bindControls);
1362    }
1363
1364    byte[] pw;
1365    if (bindPassword.isPresent())
1366    {
1367      pw = StaticUtils.getBytes(bindPassword.getValue());
1368    }
1369    else if (bindPasswordFile.isPresent())
1370    {
1371      try
1372      {
1373        final char[] pwChars = getPasswordFileReader().readPassword(
1374             bindPasswordFile.getValue());
1375        pw = StaticUtils.getBytes(new String(pwChars));
1376      }
1377      catch (final Exception e)
1378      {
1379        Debug.debugException(e);
1380        throw new LDAPException(ResultCode.LOCAL_ERROR,
1381             ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
1382                  StaticUtils.getExceptionMessage(e)), e);
1383      }
1384    }
1385    else if (promptForBindPassword.isPresent())
1386    {
1387      getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1388      pw = PasswordReader.readPassword();
1389      getOriginalOut().println();
1390    }
1391    else
1392    {
1393      pw = null;
1394    }
1395
1396    if (saslOption.isPresent())
1397    {
1398      final String dnStr;
1399      if (bindDN.isPresent())
1400      {
1401        dnStr = bindDN.getValue().toString();
1402      }
1403      else
1404      {
1405        dnStr = null;
1406      }
1407
1408      return SASLUtils.createBindRequest(dnStr, pw,
1409           defaultToPromptForBindPassword(), this, null,
1410           saslOption.getValues(), bindControls);
1411    }
1412    else if (useSASLExternal.isPresent())
1413    {
1414      return new EXTERNALBindRequest(bindControls);
1415    }
1416    else if (bindDN.isPresent())
1417    {
1418      if ((pw == null) && (! bindDN.getValue().isNullDN()) &&
1419          defaultToPromptForBindPassword())
1420      {
1421        getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1422        pw = PasswordReader.readPassword();
1423        getOriginalOut().println();
1424      }
1425
1426      return new SimpleBindRequest(bindDN.getValue(), pw, bindControls);
1427    }
1428    else
1429    {
1430      return null;
1431    }
1432  }
1433
1434
1435
1436  /**
1437   * Indicates whether any of the LDAP-related arguments maintained by the
1438   * {@code LDAPCommandLineTool} class were provided on the command line.
1439   *
1440   * @return  {@code true} if any of the LDAP-related arguments maintained by
1441   *          the {@code LDAPCommandLineTool} were provided on the command line,
1442   *          or {@code false} if not.
1443   */
1444  public final boolean anyLDAPArgumentsProvided()
1445  {
1446    return isAnyPresent(host, port, bindDN, bindPassword, bindPasswordFile,
1447         promptForBindPassword, useSSL, useStartTLS, trustAll, keyStorePath,
1448         keyStorePassword, keyStorePasswordFile, promptForKeyStorePassword,
1449         keyStoreFormat, trustStorePath, trustStorePassword,
1450         trustStorePasswordFile, trustStoreFormat, certificateNickname,
1451         saslOption, useSASLExternal);
1452  }
1453
1454
1455
1456  /**
1457   * Indicates whether at least one of the provided arguments was provided on
1458   * the command line.
1459   *
1460   * @param  args  The set of command-line arguments for which to make the
1461   *               determination.
1462   *
1463   * @return  {@code true} if at least one of the provided arguments was
1464   *          provided on the command line, or {@code false} if not.
1465   */
1466  private static boolean isAnyPresent(final Argument... args)
1467  {
1468    for (final Argument a : args)
1469    {
1470      if ((a != null) && (a.getNumOccurrences() > 0))
1471      {
1472        return true;
1473      }
1474    }
1475
1476    return false;
1477  }
1478}