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