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