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