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