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