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