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