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