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