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     */
021    package com.unboundid.util;
022    
023    
024    
025    import java.io.OutputStream;
026    import java.util.ArrayList;
027    import java.util.Collections;
028    import java.util.LinkedHashSet;
029    import java.util.List;
030    import java.util.Set;
031    import java.util.concurrent.atomic.AtomicReference;
032    import javax.net.SocketFactory;
033    import javax.net.ssl.KeyManager;
034    import javax.net.ssl.SSLSocketFactory;
035    import javax.net.ssl.TrustManager;
036    
037    import com.unboundid.ldap.sdk.AggregatePostConnectProcessor;
038    import com.unboundid.ldap.sdk.BindRequest;
039    import com.unboundid.ldap.sdk.Control;
040    import com.unboundid.ldap.sdk.ExtendedResult;
041    import com.unboundid.ldap.sdk.LDAPConnection;
042    import com.unboundid.ldap.sdk.LDAPConnectionOptions;
043    import com.unboundid.ldap.sdk.LDAPConnectionPool;
044    import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck;
045    import com.unboundid.ldap.sdk.LDAPException;
046    import com.unboundid.ldap.sdk.PostConnectProcessor;
047    import com.unboundid.ldap.sdk.ResultCode;
048    import com.unboundid.ldap.sdk.RoundRobinServerSet;
049    import com.unboundid.ldap.sdk.ServerSet;
050    import com.unboundid.ldap.sdk.SimpleBindRequest;
051    import com.unboundid.ldap.sdk.SingleServerSet;
052    import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor;
053    import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
054    import com.unboundid.util.args.ArgumentException;
055    import com.unboundid.util.args.ArgumentParser;
056    import com.unboundid.util.args.BooleanArgument;
057    import com.unboundid.util.args.DNArgument;
058    import com.unboundid.util.args.FileArgument;
059    import com.unboundid.util.args.IntegerArgument;
060    import com.unboundid.util.args.StringArgument;
061    import com.unboundid.util.ssl.KeyStoreKeyManager;
062    import com.unboundid.util.ssl.PromptTrustManager;
063    import com.unboundid.util.ssl.SSLUtil;
064    import com.unboundid.util.ssl.TrustAllTrustManager;
065    import com.unboundid.util.ssl.TrustStoreTrustManager;
066    
067    import static com.unboundid.util.Debug.*;
068    import static com.unboundid.util.StaticUtils.*;
069    import 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)
194    public 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    }