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  includeAuthenticationArgs  Indicates whether to include
263       *                                    authentication-related arguments.
264       *
265       * @return  A set containing the long identifiers used for LDAP-related
266       *          arguments injected by this class.
267       */
268      static Set<String> getLongLDAPArgumentIdentifiers(
269                              final boolean includeAuthenticationArgs)
270      {
271        final LinkedHashSet<String> ids = new LinkedHashSet<String>(21);
272    
273        ids.add("hostname");
274        ids.add("port");
275    
276        if (includeAuthenticationArgs)
277        {
278          ids.add("bindDN");
279          ids.add("bindPassword");
280          ids.add("bindPasswordFile");
281          ids.add("promptForBindPassword");
282        }
283    
284        ids.add("useSSL");
285        ids.add("useStartTLS");
286        ids.add("trustAll");
287        ids.add("keyStorePath");
288        ids.add("keyStorePassword");
289        ids.add("keyStorePasswordFile");
290        ids.add("promptForKeyStorePassword");
291        ids.add("keyStoreFormat");
292        ids.add("trustStorePath");
293        ids.add("trustStorePassword");
294        ids.add("trustStorePasswordFile");
295        ids.add("promptForTrustStorePassword");
296        ids.add("trustStoreFormat");
297        ids.add("certNickname");
298    
299        if (includeAuthenticationArgs)
300        {
301          ids.add("saslOption");
302          ids.add("helpSASL");
303        }
304    
305        return Collections.unmodifiableSet(ids);
306      }
307    
308    
309    
310      /**
311       * {@inheritDoc}
312       */
313      @Override()
314      public final void addToolArguments(final ArgumentParser parser)
315             throws ArgumentException
316      {
317        final String argumentGroup;
318        final boolean supportsAuthentication = supportsAuthentication();
319        if (supportsAuthentication)
320        {
321          argumentGroup = INFO_LDAP_TOOL_ARG_GROUP_CONNECT_AND_AUTH.get();
322        }
323        else
324        {
325          argumentGroup = INFO_LDAP_TOOL_ARG_GROUP_CONNECT.get();
326        }
327    
328    
329        host = new StringArgument('h', "hostname", true,
330             (supportsMultipleServers() ? 0 : 1),
331             INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(),
332             INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost");
333        host.setArgumentGroupName(argumentGroup);
334        parser.addArgument(host);
335    
336        port = new IntegerArgument('p', "port", true,
337             (supportsMultipleServers() ? 0 : 1),
338             INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(),
339             INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65535, 389);
340        port.setArgumentGroupName(argumentGroup);
341        parser.addArgument(port);
342    
343        if (supportsAuthentication)
344        {
345          bindDN = new DNArgument('D', "bindDN", false, 1,
346               INFO_LDAP_TOOL_PLACEHOLDER_DN.get(),
347               INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get());
348          bindDN.setArgumentGroupName(argumentGroup);
349          if (includeAlternateLongIdentifiers())
350          {
351            bindDN.addLongIdentifier("bind-dn");
352          }
353          parser.addArgument(bindDN);
354    
355          bindPassword = new StringArgument('w', "bindPassword", false, 1,
356               INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
357               INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get());
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.setArgumentGroupName(argumentGroup);
430        if (includeAlternateLongIdentifiers())
431        {
432          keyStorePassword.addLongIdentifier("keyStorePIN");
433          keyStorePassword.addLongIdentifier("key-store-password");
434          keyStorePassword.addLongIdentifier("key-store-pin");
435        }
436        parser.addArgument(keyStorePassword);
437    
438        keyStorePasswordFile = new FileArgument('u', "keyStorePasswordFile", false,
439             1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
440             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get());
441        keyStorePasswordFile.setArgumentGroupName(argumentGroup);
442        if (includeAlternateLongIdentifiers())
443        {
444          keyStorePasswordFile.addLongIdentifier("keyStorePINFile");
445          keyStorePasswordFile.addLongIdentifier("key-store-password-file");
446          keyStorePasswordFile.addLongIdentifier("key-store-pin-file");
447        }
448        parser.addArgument(keyStorePasswordFile);
449    
450        promptForKeyStorePassword = new BooleanArgument(null,
451             "promptForKeyStorePassword", 1,
452             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_PROMPT.get());
453        promptForKeyStorePassword.setArgumentGroupName(argumentGroup);
454        if (includeAlternateLongIdentifiers())
455        {
456          promptForKeyStorePassword.addLongIdentifier("promptForKeyStorePIN");
457          promptForKeyStorePassword.addLongIdentifier(
458               "prompt-for-key-store-password");
459          promptForKeyStorePassword.addLongIdentifier("prompt-for-key-store-pin");
460        }
461        parser.addArgument(promptForKeyStorePassword);
462    
463        keyStoreFormat = new StringArgument(null, "keyStoreFormat", false, 1,
464             INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
465             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get());
466        keyStoreFormat.setArgumentGroupName(argumentGroup);
467        if (includeAlternateLongIdentifiers())
468        {
469          keyStoreFormat.addLongIdentifier("keyStoreType");
470          keyStoreFormat.addLongIdentifier("key-store-format");
471          keyStoreFormat.addLongIdentifier("key-store-type");
472        }
473        parser.addArgument(keyStoreFormat);
474    
475        trustStorePath = new StringArgument('P', "trustStorePath", false, 1,
476             INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
477             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get());
478        trustStorePath.setArgumentGroupName(argumentGroup);
479        if (includeAlternateLongIdentifiers())
480        {
481          trustStorePath.addLongIdentifier("trust-store-path");
482        }
483        parser.addArgument(trustStorePath);
484    
485        trustStorePassword = new StringArgument('T', "trustStorePassword", false, 1,
486             INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
487             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get());
488        trustStorePassword.setArgumentGroupName(argumentGroup);
489        if (includeAlternateLongIdentifiers())
490        {
491          trustStorePassword.addLongIdentifier("trustStorePIN");
492          trustStorePassword.addLongIdentifier("trust-store-password");
493          trustStorePassword.addLongIdentifier("trust-store-pin");
494        }
495        parser.addArgument(trustStorePassword);
496    
497        trustStorePasswordFile = new FileArgument('U', "trustStorePasswordFile",
498             false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
499             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get());
500        trustStorePasswordFile.setArgumentGroupName(argumentGroup);
501        if (includeAlternateLongIdentifiers())
502        {
503          trustStorePasswordFile.addLongIdentifier("trustStorePINFile");
504          trustStorePasswordFile.addLongIdentifier("trust-store-password-file");
505          trustStorePasswordFile.addLongIdentifier("trust-store-pin-file");
506        }
507        parser.addArgument(trustStorePasswordFile);
508    
509        promptForTrustStorePassword = new BooleanArgument(null,
510             "promptForTrustStorePassword", 1,
511             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_PROMPT.get());
512        promptForTrustStorePassword.setArgumentGroupName(argumentGroup);
513        if (includeAlternateLongIdentifiers())
514        {
515          promptForTrustStorePassword.addLongIdentifier("promptForTrustStorePIN");
516          promptForTrustStorePassword.addLongIdentifier(
517               "prompt-for-trust-store-password");
518          promptForTrustStorePassword.addLongIdentifier(
519               "prompt-for-trust-store-pin");
520        }
521        parser.addArgument(promptForTrustStorePassword);
522    
523        trustStoreFormat = new StringArgument(null, "trustStoreFormat", false, 1,
524             INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
525             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get());
526        trustStoreFormat.setArgumentGroupName(argumentGroup);
527        if (includeAlternateLongIdentifiers())
528        {
529          trustStoreFormat.addLongIdentifier("trustStoreType");
530          trustStoreFormat.addLongIdentifier("trust-store-format");
531          trustStoreFormat.addLongIdentifier("trust-store-type");
532        }
533        parser.addArgument(trustStoreFormat);
534    
535        certificateNickname = new StringArgument('N', "certNickname", false, 1,
536             INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(),
537             INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get());
538        certificateNickname.setArgumentGroupName(argumentGroup);
539        if (includeAlternateLongIdentifiers())
540        {
541          certificateNickname.addLongIdentifier("certificate-nickname");
542        }
543        parser.addArgument(certificateNickname);
544    
545        if (supportsAuthentication)
546        {
547          saslOption = new StringArgument('o', "saslOption", false, 0,
548               INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(),
549               INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get());
550          saslOption.setArgumentGroupName(argumentGroup);
551          if (includeAlternateLongIdentifiers())
552          {
553            saslOption.addLongIdentifier("sasl-option");
554          }
555          parser.addArgument(saslOption);
556    
557          if (supportsSASLHelp())
558          {
559            helpSASL = new BooleanArgument(null, "helpSASL",
560                 INFO_LDAP_TOOL_DESCRIPTION_HELP_SASL.get());
561            helpSASL.setArgumentGroupName(argumentGroup);
562            if (includeAlternateLongIdentifiers())
563            {
564              helpSASL.addLongIdentifier("help-sasl");
565            }
566            helpSASL.setUsageArgument(true);
567            parser.addArgument(helpSASL);
568            setHelpSASLArgument(helpSASL);
569          }
570        }
571    
572    
573        // Both useSSL and useStartTLS cannot be used together.
574        parser.addExclusiveArgumentSet(useSSL, useStartTLS);
575    
576        // Only one option may be used for specifying the key store password.
577        parser.addExclusiveArgumentSet(keyStorePassword, keyStorePasswordFile,
578             promptForKeyStorePassword);
579    
580        // Only one option may be used for specifying the trust store password.
581        parser.addExclusiveArgumentSet(trustStorePassword, trustStorePasswordFile,
582             promptForTrustStorePassword);
583    
584        // It doesn't make sense to provide a trust store path if any server
585        // certificate should be trusted.
586        parser.addExclusiveArgumentSet(trustAll, trustStorePath);
587    
588        // If a key store password is provided, then a key store path must have also
589        // been provided.
590        parser.addDependentArgumentSet(keyStorePassword, keyStorePath);
591        parser.addDependentArgumentSet(keyStorePasswordFile, keyStorePath);
592        parser.addDependentArgumentSet(promptForKeyStorePassword, keyStorePath);
593    
594        // If a trust store password is provided, then a trust store path must have
595        // also been provided.
596        parser.addDependentArgumentSet(trustStorePassword, trustStorePath);
597        parser.addDependentArgumentSet(trustStorePasswordFile, trustStorePath);
598        parser.addDependentArgumentSet(promptForTrustStorePassword, trustStorePath);
599    
600        // If a key or trust store path is provided, then the tool must either use
601        // SSL or StartTLS.
602        parser.addDependentArgumentSet(keyStorePath, useSSL, useStartTLS);
603        parser.addDependentArgumentSet(trustStorePath, useSSL, useStartTLS);
604    
605        // If the tool should trust all server certificates, then the tool must
606        // either use SSL or StartTLS.
607        parser.addDependentArgumentSet(trustAll, useSSL, useStartTLS);
608    
609        if (supportsAuthentication)
610        {
611          // If a bind DN was provided, then a bind password must have also been
612          // provided.
613          parser.addDependentArgumentSet(bindDN, bindPassword, bindPasswordFile,
614               promptForBindPassword);
615    
616          // Only one option may be used for specifying the bind password.
617          parser.addExclusiveArgumentSet(bindPassword, bindPasswordFile,
618               promptForBindPassword);
619    
620          // If a bind password was provided, then the a bind DN or SASL option
621          // must have also been provided.
622          parser.addDependentArgumentSet(bindPassword, bindDN, saslOption);
623          parser.addDependentArgumentSet(bindPasswordFile, bindDN, saslOption);
624          parser.addDependentArgumentSet(promptForBindPassword, bindDN, saslOption);
625        }
626    
627        addNonLDAPArguments(parser);
628      }
629    
630    
631    
632      /**
633       * Adds the arguments needed by this command-line tool to the provided
634       * argument parser which are not related to connecting or authenticating to
635       * the directory server.
636       *
637       * @param  parser  The argument parser to which the arguments should be added.
638       *
639       * @throws  ArgumentException  If a problem occurs while adding the arguments.
640       */
641      public abstract void addNonLDAPArguments(final ArgumentParser parser)
642             throws ArgumentException;
643    
644    
645    
646      /**
647       * {@inheritDoc}
648       */
649      @Override()
650      public final void doExtendedArgumentValidation()
651             throws ArgumentException
652      {
653        // If more than one hostname or port number was provided, then make sure
654        // that the same number of values were provided for each.
655        if ((host.getValues().size() > 1) || (port.getValues().size() > 1))
656        {
657          if (host.getValues().size() != port.getValues().size())
658          {
659            throw new ArgumentException(
660                 ERR_LDAP_TOOL_HOST_PORT_COUNT_MISMATCH.get(
661                      host.getLongIdentifier(), port.getLongIdentifier()));
662          }
663        }
664    
665    
666        doExtendedNonLDAPArgumentValidation();
667      }
668    
669    
670    
671      /**
672       * Indicates whether this tool should provide the arguments that allow it to
673       * bind via simple or SASL authentication.
674       *
675       * @return  {@code true} if this tool should provide the arguments that allow
676       *          it to bind via simple or SASL authentication, or {@code false} if
677       *          not.
678       */
679      protected boolean supportsAuthentication()
680      {
681        return true;
682      }
683    
684    
685    
686      /**
687       * Indicates whether this tool should provide a "--help-sasl" argument that
688       * provides information about the supported SASL mechanisms and their
689       * associated properties.
690       *
691       * @return  {@code true} if this tool should provide a "--help-sasl" argument,
692       *          or {@code false} if not.
693       */
694      protected boolean supportsSASLHelp()
695      {
696        return true;
697      }
698    
699    
700    
701      /**
702       * Indicates whether the LDAP-specific arguments should include alternate
703       * versions of all long identifiers that consist of multiple words so that
704       * they are available in both camelCase and dash-separated versions.
705       *
706       * @return  {@code true} if this tool should provide multiple versions of
707       *          long identifiers for LDAP-specific arguments, or {@code false} if
708       *          not.
709       */
710      protected boolean includeAlternateLongIdentifiers()
711      {
712        return false;
713      }
714    
715    
716    
717      /**
718       * Retrieves a set of controls that should be included in any bind request
719       * generated by this tool.
720       *
721       * @return  A set of controls that should be included in any bind request
722       *          generated by this tool.  It may be {@code null} or empty if no
723       *          controls should be included in the bind request.
724       */
725      protected List<Control> getBindControls()
726      {
727        return null;
728      }
729    
730    
731    
732      /**
733       * Indicates whether this tool supports creating connections to multiple
734       * servers.  If it is to support multiple servers, then the "--hostname" and
735       * "--port" arguments will be allowed to be provided multiple times, and
736       * will be required to be provided the same number of times.  The same type of
737       * communication security and bind credentials will be used for all servers.
738       *
739       * @return  {@code true} if this tool supports creating connections to
740       *          multiple servers, or {@code false} if not.
741       */
742      protected boolean supportsMultipleServers()
743      {
744        return false;
745      }
746    
747    
748    
749      /**
750       * Performs any necessary processing that should be done to ensure that the
751       * provided set of command-line arguments were valid.  This method will be
752       * called after the basic argument parsing has been performed and after all
753       * LDAP-specific argument validation has been processed, and immediately
754       * before the {@link CommandLineTool#doToolProcessing} method is invoked.
755       *
756       * @throws  ArgumentException  If there was a problem with the command-line
757       *                             arguments provided to this program.
758       */
759      public void doExtendedNonLDAPArgumentValidation()
760             throws ArgumentException
761      {
762        // No processing will be performed by default.
763      }
764    
765    
766    
767      /**
768       * Retrieves the connection options that should be used for connections that
769       * are created with this command line tool.  Subclasses may override this
770       * method to use a custom set of connection options.
771       *
772       * @return  The connection options that should be used for connections that
773       *          are created with this command line tool.
774       */
775      public LDAPConnectionOptions getConnectionOptions()
776      {
777        return new LDAPConnectionOptions();
778      }
779    
780    
781    
782      /**
783       * Retrieves a connection that may be used to communicate with the target
784       * directory server.
785       * <BR><BR>
786       * Note that this method is threadsafe and may be invoked by multiple threads
787       * accessing the same instance only while that instance is in the process of
788       * invoking the {@link #doToolProcessing} method.
789       *
790       * @return  A connection that may be used to communicate with the target
791       *          directory server.
792       *
793       * @throws  LDAPException  If a problem occurs while creating the connection.
794       */
795      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
796      public final LDAPConnection getConnection()
797             throws LDAPException
798      {
799        final LDAPConnection connection = getUnauthenticatedConnection();
800    
801        try
802        {
803          if (bindRequest != null)
804          {
805            connection.bind(bindRequest);
806          }
807        }
808        catch (LDAPException le)
809        {
810          debugException(le);
811          connection.close();
812          throw le;
813        }
814    
815        return connection;
816      }
817    
818    
819    
820      /**
821       * Retrieves an unauthenticated connection that may be used to communicate
822       * with the target directory server.
823       * <BR><BR>
824       * Note that this method is threadsafe and may be invoked by multiple threads
825       * accessing the same instance only while that instance is in the process of
826       * invoking the {@link #doToolProcessing} method.
827       *
828       * @return  An unauthenticated connection that may be used to communicate with
829       *          the target directory server.
830       *
831       * @throws  LDAPException  If a problem occurs while creating the connection.
832       */
833      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
834      public final LDAPConnection getUnauthenticatedConnection()
835             throws LDAPException
836      {
837        if (serverSet == null)
838        {
839          serverSet   = createServerSet();
840          bindRequest = createBindRequest();
841        }
842    
843        final LDAPConnection connection = serverSet.getConnection();
844    
845        if (useStartTLS.isPresent())
846        {
847          try
848          {
849            final ExtendedResult extendedResult =
850                 connection.processExtendedOperation(
851                      new StartTLSExtendedRequest(startTLSSocketFactory));
852            if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
853            {
854              throw new LDAPException(extendedResult.getResultCode(),
855                   ERR_LDAP_TOOL_START_TLS_FAILED.get(
856                        extendedResult.getDiagnosticMessage()));
857            }
858          }
859          catch (LDAPException le)
860          {
861            debugException(le);
862            connection.close();
863            throw le;
864          }
865        }
866    
867        return connection;
868      }
869    
870    
871    
872      /**
873       * Retrieves a connection pool that may be used to communicate with the target
874       * directory server.
875       * <BR><BR>
876       * Note that this method is threadsafe and may be invoked by multiple threads
877       * accessing the same instance only while that instance is in the process of
878       * invoking the {@link #doToolProcessing} method.
879       *
880       * @param  initialConnections  The number of connections that should be
881       *                             initially established in the pool.
882       * @param  maxConnections      The maximum number of connections to maintain
883       *                             in the pool.
884       *
885       * @return  A connection that may be used to communicate with the target
886       *          directory server.
887       *
888       * @throws  LDAPException  If a problem occurs while creating the connection
889       *                         pool.
890       */
891      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
892      public final LDAPConnectionPool getConnectionPool(
893                                           final int initialConnections,
894                                           final int maxConnections)
895                throws LDAPException
896      {
897        return getConnectionPool(initialConnections, maxConnections, 1, null, null,
898             true, null);
899      }
900    
901    
902    
903      /**
904       * Retrieves a connection pool that may be used to communicate with the target
905       * directory server.
906       * <BR><BR>
907       * Note that this method is threadsafe and may be invoked by multiple threads
908       * accessing the same instance only while that instance is in the process of
909       * invoking the {@link #doToolProcessing} method.
910       *
911       * @param  initialConnections       The number of connections that should be
912       *                                  initially established in the pool.
913       * @param  maxConnections           The maximum number of connections to
914       *                                  maintain in the pool.
915       * @param  initialConnectThreads    The number of concurrent threads to use to
916       *                                  establish the initial set of connections.
917       *                                  A value greater than one indicates that
918       *                                  the attempt to establish connections
919       *                                  should be parallelized.
920       * @param  beforeStartTLSProcessor  An optional post-connect processor that
921       *                                  should be used for the connection pool and
922       *                                  should be invoked before any StartTLS
923       *                                  post-connect processor that may be needed
924       *                                  based on the selected arguments.  It may
925       *                                  be {@code null} if no such post-connect
926       *                                  processor is needed.
927       * @param  afterStartTLSProcessor   An optional post-connect processor that
928       *                                  should be used for the connection pool and
929       *                                  should be invoked after any StartTLS
930       *                                  post-connect processor that may be needed
931       *                                  based on the selected arguments.  It may
932       *                                  be {@code null} if no such post-connect
933       *                                  processor is needed.
934       * @param  throwOnConnectFailure    If an exception should be thrown if a
935       *                                  problem is encountered while attempting to
936       *                                  create the specified initial number of
937       *                                  connections.  If {@code true}, then the
938       *                                  attempt to create the pool will fail if
939       *                                  any connection cannot be established.  If
940       *                                  {@code false}, then the pool will be
941       *                                  created but may have fewer than the
942       *                                  initial number of connections (or possibly
943       *                                  no connections).
944       * @param  healthCheck              An optional health check that should be
945       *                                  configured for the connection pool.  It
946       *                                  may be {@code null} if the default health
947       *                                  checking should be performed.
948       *
949       * @return  A connection that may be used to communicate with the target
950       *          directory server.
951       *
952       * @throws  LDAPException  If a problem occurs while creating the connection
953       *                         pool.
954       */
955      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
956      public final LDAPConnectionPool getConnectionPool(
957                        final int initialConnections, final int maxConnections,
958                        final int initialConnectThreads,
959                        final PostConnectProcessor beforeStartTLSProcessor,
960                        final PostConnectProcessor afterStartTLSProcessor,
961                        final boolean throwOnConnectFailure,
962                        final LDAPConnectionPoolHealthCheck healthCheck)
963                throws LDAPException
964      {
965        // Create the server set and bind request, if necessary.
966        if (serverSet == null)
967        {
968          serverSet   = createServerSet();
969          bindRequest = createBindRequest();
970        }
971    
972    
973        // Prepare the post-connect processor for the pool.
974        final ArrayList<PostConnectProcessor> pcpList =
975             new ArrayList<PostConnectProcessor>(3);
976        if (beforeStartTLSProcessor != null)
977        {
978          pcpList.add(beforeStartTLSProcessor);
979        }
980    
981        if (useStartTLS.isPresent())
982        {
983          pcpList.add(new StartTLSPostConnectProcessor(startTLSSocketFactory));
984        }
985    
986        if (afterStartTLSProcessor != null)
987        {
988          pcpList.add(afterStartTLSProcessor);
989        }
990    
991        final PostConnectProcessor postConnectProcessor;
992        switch (pcpList.size())
993        {
994          case 0:
995            postConnectProcessor = null;
996            break;
997          case 1:
998            postConnectProcessor = pcpList.get(0);
999            break;
1000          default:
1001            postConnectProcessor = new AggregatePostConnectProcessor(pcpList);
1002            break;
1003        }
1004    
1005        return new LDAPConnectionPool(serverSet, bindRequest, initialConnections,
1006             maxConnections, initialConnectThreads, postConnectProcessor,
1007             throwOnConnectFailure, healthCheck);
1008      }
1009    
1010    
1011    
1012      /**
1013       * Creates the server set to use when creating connections or connection
1014       * pools.
1015       *
1016       * @return  The server set to use when creating connections or connection
1017       *          pools.
1018       *
1019       * @throws  LDAPException  If a problem occurs while creating the server set.
1020       */
1021      public ServerSet createServerSet()
1022             throws LDAPException
1023      {
1024        final SSLUtil sslUtil = createSSLUtil();
1025    
1026        SocketFactory socketFactory = null;
1027        if (useSSL.isPresent())
1028        {
1029          try
1030          {
1031            socketFactory = sslUtil.createSSLSocketFactory();
1032          }
1033          catch (Exception e)
1034          {
1035            debugException(e);
1036            throw new LDAPException(ResultCode.LOCAL_ERROR,
1037                 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
1038                      getExceptionMessage(e)), e);
1039          }
1040        }
1041        else if (useStartTLS.isPresent())
1042        {
1043          try
1044          {
1045            startTLSSocketFactory = sslUtil.createSSLSocketFactory();
1046          }
1047          catch (Exception e)
1048          {
1049            debugException(e);
1050            throw new LDAPException(ResultCode.LOCAL_ERROR,
1051                 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
1052                      getExceptionMessage(e)), e);
1053          }
1054        }
1055    
1056        if (host.getValues().size() == 1)
1057        {
1058          return new SingleServerSet(host.getValue(), port.getValue(),
1059                                     socketFactory, getConnectionOptions());
1060        }
1061        else
1062        {
1063          final List<String>  hostList = host.getValues();
1064          final List<Integer> portList = port.getValues();
1065    
1066          final String[] hosts = new String[hostList.size()];
1067          final int[]    ports = new int[hosts.length];
1068    
1069          for (int i=0; i < hosts.length; i++)
1070          {
1071            hosts[i] = hostList.get(i);
1072            ports[i] = portList.get(i);
1073          }
1074    
1075          return new RoundRobinServerSet(hosts, ports, socketFactory,
1076                                         getConnectionOptions());
1077        }
1078      }
1079    
1080    
1081    
1082      /**
1083       * Creates the SSLUtil instance to use for secure communication.
1084       *
1085       * @return  The SSLUtil instance to use for secure communication, or
1086       *          {@code null} if secure communication is not needed.
1087       *
1088       * @throws  LDAPException  If a problem occurs while creating the SSLUtil
1089       *                         instance.
1090       */
1091      public SSLUtil createSSLUtil()
1092             throws LDAPException
1093      {
1094        return createSSLUtil(false);
1095      }
1096    
1097    
1098    
1099      /**
1100       * Creates the SSLUtil instance to use for secure communication.
1101       *
1102       * @param  force  Indicates whether to create the SSLUtil object even if
1103       *                neither the "--useSSL" nor the "--useStartTLS" argument was
1104       *                provided.  The key store and/or trust store paths must still
1105       *                have been provided.  This may be useful for tools that
1106       *                accept SSL-based communication but do not themselves intend
1107       *                to perform SSL-based communication as an LDAP client.
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(final boolean force)
1116             throws LDAPException
1117      {
1118        if (force || useSSL.isPresent() || useStartTLS.isPresent())
1119        {
1120          KeyManager keyManager = null;
1121          if (keyStorePath.isPresent())
1122          {
1123            char[] pw = null;
1124            if (keyStorePassword.isPresent())
1125            {
1126              pw = keyStorePassword.getValue().toCharArray();
1127            }
1128            else if (keyStorePasswordFile.isPresent())
1129            {
1130              try
1131              {
1132                pw = keyStorePasswordFile.getNonBlankFileLines().get(0).
1133                          toCharArray();
1134              }
1135              catch (Exception e)
1136              {
1137                debugException(e);
1138                throw new LDAPException(ResultCode.LOCAL_ERROR,
1139                     ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get(
1140                          getExceptionMessage(e)), e);
1141              }
1142            }
1143            else if (promptForKeyStorePassword.isPresent())
1144            {
1145              getOut().print(INFO_LDAP_TOOL_ENTER_KEY_STORE_PASSWORD.get());
1146              pw = StaticUtils.toUTF8String(
1147                   PasswordReader.readPassword()).toCharArray();
1148              getOut().println();
1149            }
1150    
1151            try
1152            {
1153              keyManager = new KeyStoreKeyManager(keyStorePath.getValue(), pw,
1154                   keyStoreFormat.getValue(), certificateNickname.getValue());
1155            }
1156            catch (Exception e)
1157            {
1158              debugException(e);
1159              throw new LDAPException(ResultCode.LOCAL_ERROR,
1160                   ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
1161                        getExceptionMessage(e)), e);
1162            }
1163          }
1164    
1165          TrustManager trustManager;
1166          if (trustAll.isPresent())
1167          {
1168            trustManager = new TrustAllTrustManager(false);
1169          }
1170          else if (trustStorePath.isPresent())
1171          {
1172            char[] pw = null;
1173            if (trustStorePassword.isPresent())
1174            {
1175              pw = trustStorePassword.getValue().toCharArray();
1176            }
1177            else if (trustStorePasswordFile.isPresent())
1178            {
1179              try
1180              {
1181                pw = trustStorePasswordFile.getNonBlankFileLines().get(0).
1182                          toCharArray();
1183              }
1184              catch (Exception e)
1185              {
1186                debugException(e);
1187                throw new LDAPException(ResultCode.LOCAL_ERROR,
1188                     ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
1189                          getExceptionMessage(e)), e);
1190              }
1191            }
1192            else if (promptForTrustStorePassword.isPresent())
1193            {
1194              getOut().print(INFO_LDAP_TOOL_ENTER_TRUST_STORE_PASSWORD.get());
1195              pw = StaticUtils.toUTF8String(
1196                   PasswordReader.readPassword()).toCharArray();
1197              getOut().println();
1198            }
1199    
1200            trustManager = new TrustStoreTrustManager(trustStorePath.getValue(), pw,
1201                 trustStoreFormat.getValue(), true);
1202          }
1203          else
1204          {
1205            trustManager = promptTrustManager.get();
1206            if (trustManager == null)
1207            {
1208              final PromptTrustManager m = new PromptTrustManager();
1209              promptTrustManager.compareAndSet(null, m);
1210              trustManager = promptTrustManager.get();
1211            }
1212          }
1213    
1214          return new SSLUtil(keyManager, trustManager);
1215        }
1216        else
1217        {
1218          return null;
1219        }
1220      }
1221    
1222    
1223    
1224      /**
1225       * Creates the bind request to use to authenticate to the server.
1226       *
1227       * @return  The bind request to use to authenticate to the server, or
1228       *          {@code null} if no bind should be performed.
1229       *
1230       * @throws  LDAPException  If a problem occurs while creating the bind
1231       *                         request.
1232       */
1233      public BindRequest createBindRequest()
1234             throws LDAPException
1235      {
1236        if (! supportsAuthentication())
1237        {
1238          return null;
1239        }
1240    
1241        final Control[] bindControls;
1242        final List<Control> bindControlList = getBindControls();
1243        if ((bindControlList == null) || bindControlList.isEmpty())
1244        {
1245          bindControls = NO_CONTROLS;
1246        }
1247        else
1248        {
1249          bindControls = new Control[bindControlList.size()];
1250          bindControlList.toArray(bindControls);
1251        }
1252    
1253        final String pw;
1254        if (bindPassword.isPresent())
1255        {
1256          pw = bindPassword.getValue();
1257        }
1258        else if (bindPasswordFile.isPresent())
1259        {
1260          try
1261          {
1262            pw = bindPasswordFile.getNonBlankFileLines().get(0);
1263          }
1264          catch (Exception e)
1265          {
1266            debugException(e);
1267            throw new LDAPException(ResultCode.LOCAL_ERROR,
1268                 ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
1269                      getExceptionMessage(e)), e);
1270          }
1271        }
1272        else if (promptForBindPassword.isPresent())
1273        {
1274          getOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1275          pw = StaticUtils.toUTF8String(PasswordReader.readPassword());
1276          getOut().println();
1277        }
1278        else
1279        {
1280          pw = null;
1281        }
1282    
1283        if (saslOption.isPresent())
1284        {
1285          final String dnStr;
1286          if (bindDN.isPresent())
1287          {
1288            dnStr = bindDN.getValue().toString();
1289          }
1290          else
1291          {
1292            dnStr = null;
1293          }
1294    
1295          return SASLUtils.createBindRequest(dnStr, pw, null,
1296               saslOption.getValues(), bindControls);
1297        }
1298        else if (bindDN.isPresent())
1299        {
1300          return new SimpleBindRequest(bindDN.getValue(), pw, bindControls);
1301        }
1302        else
1303        {
1304          return null;
1305        }
1306      }
1307    }