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