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