001    /*
002     * Copyright 2008-2011 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2011 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.List;
027    import java.util.concurrent.atomic.AtomicReference;
028    import javax.net.SocketFactory;
029    import javax.net.ssl.KeyManager;
030    import javax.net.ssl.SSLContext;
031    import javax.net.ssl.TrustManager;
032    
033    import com.unboundid.ldap.sdk.BindRequest;
034    import com.unboundid.ldap.sdk.ExtendedResult;
035    import com.unboundid.ldap.sdk.LDAPConnection;
036    import com.unboundid.ldap.sdk.LDAPConnectionOptions;
037    import com.unboundid.ldap.sdk.LDAPConnectionPool;
038    import com.unboundid.ldap.sdk.LDAPException;
039    import com.unboundid.ldap.sdk.PostConnectProcessor;
040    import com.unboundid.ldap.sdk.ResultCode;
041    import com.unboundid.ldap.sdk.RoundRobinServerSet;
042    import com.unboundid.ldap.sdk.ServerSet;
043    import com.unboundid.ldap.sdk.SimpleBindRequest;
044    import com.unboundid.ldap.sdk.SingleServerSet;
045    import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor;
046    import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
047    import com.unboundid.util.args.ArgumentException;
048    import com.unboundid.util.args.ArgumentParser;
049    import com.unboundid.util.args.BooleanArgument;
050    import com.unboundid.util.args.DNArgument;
051    import com.unboundid.util.args.FileArgument;
052    import com.unboundid.util.args.IntegerArgument;
053    import com.unboundid.util.args.StringArgument;
054    import com.unboundid.util.ssl.KeyStoreKeyManager;
055    import com.unboundid.util.ssl.PromptTrustManager;
056    import com.unboundid.util.ssl.SSLUtil;
057    import com.unboundid.util.ssl.TrustAllTrustManager;
058    import com.unboundid.util.ssl.TrustStoreTrustManager;
059    
060    import static com.unboundid.util.Debug.*;
061    import static com.unboundid.util.StaticUtils.*;
062    import static com.unboundid.util.UtilityMessages.*;
063    
064    
065    
066    /**
067     * This class provides a basis for developing command-line tools that
068     * communicate with an LDAP directory server.  It provides a common set of
069     * options for connecting and authenticating to a directory server, and then
070     * provides a mechanism for obtaining connections and connection pools to use
071     * when communicating with that server.
072     * <BR><BR>
073     * The arguments that this class supports include:
074     * <UL>
075     *   <LI>"-h {address}" or "--hostname {address}" -- Specifies the address of
076     *       the directory server.  If this isn't specified, then a default of
077     *       "localhost" will be used.</LI>
078     *   <LI>"-p {port}" or "--port {port}" -- Specifies the port number of the
079     *       directory server.  If this isn't specified, then a default port of 389
080     *       will be used.</LI>
081     *   <LI>"-D {bindDN}" or "--bindDN {bindDN}" -- Specifies the DN to use to bind
082     *       to the directory server using simple authentication.  If this isn't
083     *       specified, then simple authentication will not be performed.</LI>
084     *   <LI>"-w {password}" or "--bindPassword {password}" -- Specifies the
085     *       password to use when binding with simple authentication or a
086     *       password-based SASL mechanism.</LI>
087     *   <LI>"-j {path}" or "--bindPasswordFile {path}" -- Specifies the path to the
088     *       file containing the password to use when binding with simple
089     *       authentication or a password-based SASL mechanism.</LI>
090     *   <LI>"-Z" or "--useSSL" -- Indicates that the communication with the server
091     *       should be secured using SSL.</LI>
092     *   <LI>"-q" or "--useStartTLS" -- Indicates that the communication with the
093     *       server should be secured using StartTLS.</LI>
094     *   <LI>"-X" or "--trustAll" -- Indicates that the client should trust any
095     *       certificate that the server presents to it.</LI>
096     *   <LI>"-K {path}" or "--keyStorePath {path}" -- Specifies the path to the
097     *       key store to use to obtain client certificates.</LI>
098     *   <LI>"-W {password}" or "--keyStorePassword {password}" -- Specifies the
099     *       password to use to access the contents of the key store.</LI>
100     *   <LI>"-u {path}" or "--keyStorePasswordFile {path}" -- Specifies the path to
101     *       the file containing the password to use to access the contents of the
102     *       key store.</LI>
103     *   <LI>"--keyStoreFormat {format}" -- Specifies the format to use for the key
104     *       store file.</LI>
105     *   <LI>"-P {path}" or "--trustStorePath {path}" -- Specifies the path to the
106     *       trust store to use when determining whether to trust server
107     *       certificates.</LI>
108     *   <LI>"-T {password}" or "--trustStorePassword {password}" -- Specifies the
109     *       password to use to access the contents of the trust store.</LI>
110     *   <LI>"-U {path}" or "--trustStorePasswordFile {path}" -- Specifies the path
111     *       to the file containing the password to use to access the contents of
112     *       the trust store.</LI>
113     *   <LI>"--trustStoreFormat {format}" -- Specifies the format to use for the
114     *       trust store file.</LI>
115     *   <LI>"-N {nickname}" or "--certNickname {nickname}" -- Specifies the
116     *       nickname of the client certificate to use when performing SSL client
117     *       authentication.</LI>
118     *   <LI>"-o {name=value}" or "--saslOption {name=value}" -- Specifies a SASL
119     *       option to use when performing SASL authentication.</LI>
120     * </UL>
121     * If SASL authentication is to be used, then a "mech" SASL option must be
122     * provided to specify the name of the SASL mechanism to use (e.g.,
123     * "--saslOption mech=EXTERNAL" indicates that the EXTERNAL mechanism should be
124     * used).  Depending on the SASL mechanism, additional SASL options may be
125     * required or optional.  They include:
126     * <UL>
127     *   <LI>
128     *     mech=ANONYMOUS
129     *     <UL>
130     *       <LI>Required SASL options:  </LI>
131     *       <LI>Optional SASL options:  trace</LI>
132     *     </UL>
133     *   </LI>
134     *   <LI>
135     *     mech=CRAM-MD5
136     *     <UL>
137     *       <LI>Required SASL options:  authID</LI>
138     *       <LI>Optional SASL options:  </LI>
139     *     </UL>
140     *   </LI>
141     *   <LI>
142     *     mech=DIGEST-MD5
143     *     <UL>
144     *       <LI>Required SASL options:  authID</LI>
145     *       <LI>Optional SASL options:  authzID, realm</LI>
146     *     </UL>
147     *   </LI>
148     *   <LI>
149     *     mech=EXTERNAL
150     *     <UL>
151     *       <LI>Required SASL options:  </LI>
152     *       <LI>Optional SASL options:  </LI>
153     *     </UL>
154     *   </LI>
155     *   <LI>
156     *     mech=GSSAPI
157     *     <UL>
158     *       <LI>Required SASL options:  authID</LI>
159     *       <LI>Optional SASL options:  authzID, configFile, debug, protocol,
160     *                realm, kdcAddress, useTicketCache, requireCache,
161     *                renewTGT, ticketCachePath</LI>
162     *     </UL>
163     *   </LI>
164     *   <LI>
165     *     mech=PLAIN
166     *     <UL>
167     *       <LI>Required SASL options:  authID</LI>
168     *       <LI>Optional SASL options:  authzID</LI>
169     *     </UL>
170     *   </LI>
171     * </UL>
172     * <BR><BR>
173     * Note that in general, methods in this class are not threadsafe.  However, the
174     * {@link #getConnection()} and {@link #getConnectionPool(int,int)} methods may
175     * be invoked concurrently by multiple threads accessing the same instance only
176     * while that instance is in the process of invoking the
177     * {@link #doToolProcessing()} method.
178     */
179    @Extensible()
180    @ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
181    public abstract class LDAPCommandLineTool
182           extends CommandLineTool
183    {
184    
185    
186    
187      // Arguments used to communicate with an LDAP directory server.
188      private BooleanArgument trustAll               = null;
189      private BooleanArgument useSSL                 = null;
190      private BooleanArgument useStartTLS            = null;
191      private DNArgument      bindDN                 = null;
192      private FileArgument    bindPasswordFile       = null;
193      private FileArgument    keyStorePasswordFile   = null;
194      private FileArgument    trustStorePasswordFile = null;
195      private IntegerArgument port                   = null;
196      private StringArgument  bindPassword           = null;
197      private StringArgument  certificateNickname    = null;
198      private StringArgument  host                   = null;
199      private StringArgument  keyStoreFormat         = null;
200      private StringArgument  keyStorePath           = null;
201      private StringArgument  keyStorePassword       = null;
202      private StringArgument  saslOption             = null;
203      private StringArgument  trustStoreFormat       = null;
204      private StringArgument  trustStorePath         = null;
205      private StringArgument  trustStorePassword     = null;
206    
207      // Variables used when creating and authenticating connections.
208      private BindRequest bindRequest     = null;
209      private ServerSet   serverSet       = null;
210      private SSLContext  startTLSContext = null;
211    
212      // The prompt trust manager that will be shared by all connections created
213      // for which it is appropriate.  This will allow them to benefit from the
214      // common cache.
215      private final AtomicReference<PromptTrustManager> promptTrustManager;
216    
217    
218    
219      /**
220       * Creates a new instance of this LDAP-enabled command-line tool with the
221       * provided information.
222       *
223       * @param  outStream  The output stream to use for standard output.  It may be
224       *                    {@code System.out} for the JVM's default standard output
225       *                    stream, {@code null} if no output should be generated,
226       *                    or a custom output stream if the output should be sent
227       *                    to an alternate location.
228       * @param  errStream  The output stream to use for standard error.  It may be
229       *                    {@code System.err} for the JVM's default standard error
230       *                    stream, {@code null} if no output should be generated,
231       *                    or a custom output stream if the output should be sent
232       *                    to an alternate location.
233       */
234      public LDAPCommandLineTool(final OutputStream outStream,
235                                 final OutputStream errStream)
236      {
237        super(outStream, errStream);
238    
239        promptTrustManager = new AtomicReference<PromptTrustManager>();
240      }
241    
242    
243    
244      /**
245       * {@inheritDoc}
246       */
247      @Override()
248      public final void addToolArguments(final ArgumentParser parser)
249             throws ArgumentException
250      {
251        host = new StringArgument('h', "hostname", true,
252             (supportsMultipleServers() ? 0 : 1),
253             INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(),
254             INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost");
255        parser.addArgument(host);
256    
257        port = new IntegerArgument('p', "port", true,
258             (supportsMultipleServers() ? 0 : 1),
259             INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(),
260             INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65535, 389);
261        parser.addArgument(port);
262    
263        bindDN = new DNArgument('D', "bindDN", false, 1,
264             INFO_LDAP_TOOL_PLACEHOLDER_DN.get(),
265             INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get());
266        parser.addArgument(bindDN);
267    
268        bindPassword = new StringArgument('w', "bindPassword", false, 1,
269             INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
270             INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get());
271        parser.addArgument(bindPassword);
272    
273        bindPasswordFile = new FileArgument('j', "bindPasswordFile", false, 1,
274             INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
275             INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true,
276             false);
277        parser.addArgument(bindPasswordFile);
278    
279        useSSL = new BooleanArgument('Z', "useSSL", 1,
280             INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get());
281        parser.addArgument(useSSL);
282    
283        useStartTLS = new BooleanArgument('q', "useStartTLS", 1,
284             INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get());
285        parser.addArgument(useStartTLS);
286    
287        trustAll = new BooleanArgument('X', "trustAll", 1,
288             INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get());
289        parser.addArgument(trustAll);
290    
291        keyStorePath = new StringArgument('K', "keyStorePath", false, 1,
292             INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
293             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get());
294        parser.addArgument(keyStorePath);
295    
296        keyStorePassword = new StringArgument('W', "keyStorePassword", false, 1,
297             INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
298             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get());
299        parser.addArgument(keyStorePassword);
300    
301        keyStorePasswordFile = new FileArgument('u', "keyStorePasswordFile", false,
302             1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
303             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get());
304        parser.addArgument(keyStorePasswordFile);
305    
306        keyStoreFormat = new StringArgument(null, "keyStoreFormat", false, 1,
307             INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
308             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get());
309        parser.addArgument(keyStoreFormat);
310    
311        trustStorePath = new StringArgument('P', "trustStorePath", false, 1,
312             INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
313             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get());
314        parser.addArgument(trustStorePath);
315    
316        trustStorePassword = new StringArgument('T', "trustStorePassword", false, 1,
317             INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
318             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get());
319        parser.addArgument(trustStorePassword);
320    
321        trustStorePasswordFile = new FileArgument('U', "trustStorePasswordFile",
322             false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
323             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get());
324        parser.addArgument(trustStorePasswordFile);
325    
326        trustStoreFormat = new StringArgument(null, "trustStoreFormat", false, 1,
327             INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
328             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get());
329        parser.addArgument(trustStoreFormat);
330    
331        certificateNickname = new StringArgument('N', "certNickname", false, 1,
332             INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(),
333             INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get());
334        parser.addArgument(certificateNickname);
335    
336        saslOption = new StringArgument('o', "saslOption", false, 0,
337             INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(),
338             INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get());
339        parser.addArgument(saslOption);
340    
341    
342        parser.addDependentArgumentSet(bindDN, bindPassword, bindPasswordFile);
343    
344        parser.addExclusiveArgumentSet(useSSL, useStartTLS);
345        parser.addExclusiveArgumentSet(bindPassword, bindPasswordFile);
346        parser.addExclusiveArgumentSet(keyStorePassword, keyStorePasswordFile);
347        parser.addExclusiveArgumentSet(trustStorePassword, trustStorePasswordFile);
348        parser.addExclusiveArgumentSet(trustAll, trustStorePath);
349    
350        addNonLDAPArguments(parser);
351      }
352    
353    
354    
355      /**
356       * Adds the arguments needed by this command-line tool to the provided
357       * argument parser which are not related to connecting or authenticating to
358       * the directory server.
359       *
360       * @param  parser  The argument parser to which the arguments should be added.
361       *
362       * @throws  ArgumentException  If a problem occurs while adding the arguments.
363       */
364      public abstract void addNonLDAPArguments(final ArgumentParser parser)
365             throws ArgumentException;
366    
367    
368    
369      /**
370       * {@inheritDoc}
371       */
372      @Override()
373      public final void doExtendedArgumentValidation()
374             throws ArgumentException
375      {
376        // If more than one hostname or port number was provided, then make sure
377        // that the same number of values were provided for each.
378        if ((host.getValues().size() > 1) || (port.getValues().size() > 1))
379        {
380          if (host.getValues().size() != port.getValues().size())
381          {
382            throw new ArgumentException(
383                 ERR_LDAP_TOOL_HOST_PORT_COUNT_MISMATCH.get(
384                      host.getLongIdentifier(), port.getLongIdentifier()));
385          }
386        }
387    
388    
389        doExtendedNonLDAPArgumentValidation();
390      }
391    
392    
393    
394      /**
395       * Indicates whether this tool supports creating connections to multiple
396       * servers.  If it is to support multiple servers, then the "--hostname" and
397       * "--port" arguments will be allowed to be provided multiple times, and
398       * will be required to be provided the same number of times.  The same type of
399       * communication security and bind credentials will be used for all servers.
400       *
401       * @return  {@code true} if this tool supports creating connections to
402       *          multiple servers, or {@code false} if not.
403       */
404      protected boolean supportsMultipleServers()
405      {
406        return false;
407      }
408    
409    
410    
411      /**
412       * Performs any necessary processing that should be done to ensure that the
413       * provided set of command-line arguments were valid.  This method will be
414       * called after the basic argument parsing has been performed and after all
415       * LDAP-specific argument validation has been processed, and immediately
416       * before the {@link CommandLineTool#doToolProcessing} method is invoked.
417       *
418       * @throws  ArgumentException  If there was a problem with the command-line
419       *                             arguments provided to this program.
420       */
421      public void doExtendedNonLDAPArgumentValidation()
422             throws ArgumentException
423      {
424        // No processing will be performed by default.
425      }
426    
427    
428    
429      /**
430       * Retrieves the connection options that should be used for connections that
431       * are created with this command line tool.  Subclasses may override this
432       * method to use a custom set of connection options.
433       *
434       * @return  The connection options that should be used for connections that
435       *          are created with this command line tool.
436       */
437      public LDAPConnectionOptions getConnectionOptions()
438      {
439        return new LDAPConnectionOptions();
440      }
441    
442    
443    
444      /**
445       * Retrieves a connection that may be used to communicate with the target
446       * directory server.
447       * <BR><BR>
448       * Note that this method is threadsafe and may be invoked by multiple threads
449       * accessing the same instance only while that instance is in the process of
450       * invoking the {@link #doToolProcessing} method.
451       *
452       * @return  A connection that may be used to communicate with the target
453       *          directory server.
454       *
455       * @throws  LDAPException  If a problem occurs while creating the connection.
456       */
457      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
458      public final LDAPConnection getConnection()
459             throws LDAPException
460      {
461        if (serverSet == null)
462        {
463          serverSet   = createServerSet();
464          bindRequest = createBindRequest();
465        }
466    
467        final LDAPConnection connection = serverSet.getConnection();
468    
469        if (useStartTLS.isPresent())
470        {
471          try
472          {
473            final ExtendedResult extendedResult =
474                 connection.processExtendedOperation(
475                      new StartTLSExtendedRequest(startTLSContext));
476            if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
477            {
478              throw new LDAPException(extendedResult.getResultCode(),
479                   ERR_LDAP_TOOL_START_TLS_FAILED.get(
480                        extendedResult.getDiagnosticMessage()));
481            }
482          }
483          catch (LDAPException le)
484          {
485            debugException(le);
486            connection.close();
487            throw le;
488          }
489        }
490    
491        try
492        {
493          if (bindRequest != null)
494          {
495            connection.bind(bindRequest);
496          }
497        }
498        catch (LDAPException le)
499        {
500          debugException(le);
501          connection.close();
502          throw le;
503        }
504    
505        return connection;
506      }
507    
508    
509    
510      /**
511       * Retrieves a connection pool that may be used to communicate with the target
512       * directory server.
513       * <BR><BR>
514       * Note that this method is threadsafe and may be invoked by multiple threads
515       * accessing the same instance only while that instance is in the process of
516       * invoking the {@link #doToolProcessing} method.
517       *
518       * @param  initialConnections  The number of connections that should be
519       *                             initially established in the pool.
520       * @param  maxConnections      The maximum number of connections to maintain
521       *                             in the pool.
522       *
523       * @return  A connection that may be used to communicate with the target
524       *          directory server.
525       *
526       * @throws  LDAPException  If a problem occurs while creating the connection
527       *                         pool.
528       */
529      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
530      public final LDAPConnectionPool getConnectionPool(
531                                           final int initialConnections,
532                                           final int maxConnections)
533                throws LDAPException
534      {
535        if (serverSet == null)
536        {
537          serverSet   = createServerSet();
538          bindRequest = createBindRequest();
539        }
540    
541        PostConnectProcessor postConnectProcessor = null;
542        if (useStartTLS.isPresent())
543        {
544          postConnectProcessor = new StartTLSPostConnectProcessor(startTLSContext);
545        }
546    
547        return new LDAPConnectionPool(serverSet, bindRequest, initialConnections,
548                                      maxConnections, postConnectProcessor);
549      }
550    
551    
552    
553      /**
554       * Creates the server set to use when creating connections or connection
555       * pools.
556       *
557       * @return  The server set to use when creating connections or connection
558       *          pools.
559       *
560       * @throws  LDAPException  If a problem occurs while creating the server set.
561       */
562      public ServerSet createServerSet()
563             throws LDAPException
564      {
565        final SSLUtil sslUtil = createSSLUtil();
566    
567        SocketFactory socketFactory = null;
568        if (useSSL.isPresent())
569        {
570          try
571          {
572            socketFactory = sslUtil.createSSLSocketFactory();
573          }
574          catch (Exception e)
575          {
576            debugException(e);
577            throw new LDAPException(ResultCode.LOCAL_ERROR,
578                 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
579                      getExceptionMessage(e)), e);
580          }
581        }
582        else if (useStartTLS.isPresent())
583        {
584          try
585          {
586            startTLSContext = sslUtil.createSSLContext();
587          }
588          catch (Exception e)
589          {
590            debugException(e);
591            throw new LDAPException(ResultCode.LOCAL_ERROR,
592                 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_CONTEXT.get(
593                      getExceptionMessage(e)), e);
594          }
595        }
596    
597        if (host.getValues().size() == 1)
598        {
599          return new SingleServerSet(host.getValue(), port.getValue(),
600                                     socketFactory, getConnectionOptions());
601        }
602        else
603        {
604          final List<String>  hostList = host.getValues();
605          final List<Integer> portList = port.getValues();
606    
607          final String[] hosts = new String[hostList.size()];
608          final int[]    ports = new int[hosts.length];
609    
610          for (int i=0; i < hosts.length; i++)
611          {
612            hosts[i] = hostList.get(i);
613            ports[i] = portList.get(i);
614          }
615    
616          return new RoundRobinServerSet(hosts, ports, socketFactory,
617                                         getConnectionOptions());
618        }
619      }
620    
621    
622    
623      /**
624       * Creates the SSLUtil instance to use for secure communication.
625       *
626       * @return  The SSLUtil instance to use for secure communication, or
627       *          {@code null} if secure communication is not needed.
628       *
629       * @throws  LDAPException  If a problem occurs while creating the SSLUtil
630       *                         instance.
631       */
632      private SSLUtil createSSLUtil()
633              throws LDAPException
634      {
635        if (useSSL.isPresent() || useStartTLS.isPresent())
636        {
637          KeyManager keyManager = null;
638          if (keyStorePath.isPresent())
639          {
640            char[] pw = null;
641            if (keyStorePassword.isPresent())
642            {
643              pw = keyStorePassword.getValue().toCharArray();
644            }
645            else if (keyStorePasswordFile.isPresent())
646            {
647              try
648              {
649                pw = keyStorePasswordFile.getNonBlankFileLines().get(0).
650                          toCharArray();
651              }
652              catch (Exception e)
653              {
654                debugException(e);
655                throw new LDAPException(ResultCode.LOCAL_ERROR,
656                     ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get(
657                          getExceptionMessage(e)), e);
658              }
659            }
660    
661            try
662            {
663              keyManager = new KeyStoreKeyManager(keyStorePath.getValue(), pw,
664                   keyStoreFormat.getValue(), certificateNickname.getValue());
665            }
666            catch (Exception e)
667            {
668              debugException(e);
669              throw new LDAPException(ResultCode.LOCAL_ERROR,
670                   ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
671                        getExceptionMessage(e)), e);
672            }
673          }
674    
675          TrustManager trustManager;
676          if (trustAll.isPresent())
677          {
678            trustManager = new TrustAllTrustManager(false);
679          }
680          else if (trustStorePath.isPresent())
681          {
682            char[] pw = null;
683            if (trustStorePassword.isPresent())
684            {
685              pw = trustStorePassword.getValue().toCharArray();
686            }
687            else if (trustStorePasswordFile.isPresent())
688            {
689              try
690              {
691                pw = trustStorePasswordFile.getNonBlankFileLines().get(0).
692                          toCharArray();
693              }
694              catch (Exception e)
695              {
696                debugException(e);
697                throw new LDAPException(ResultCode.LOCAL_ERROR,
698                     ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
699                          getExceptionMessage(e)), e);
700              }
701            }
702    
703            trustManager = new TrustStoreTrustManager(trustStorePath.getValue(), pw,
704                 trustStoreFormat.getValue(), true);
705          }
706          else
707          {
708            trustManager = promptTrustManager.get();
709            if (trustManager == null)
710            {
711              final PromptTrustManager m = new PromptTrustManager();
712              promptTrustManager.compareAndSet(null, m);
713              trustManager = promptTrustManager.get();
714            }
715          }
716    
717          return new SSLUtil(keyManager, trustManager);
718        }
719        else
720        {
721          return null;
722        }
723      }
724    
725    
726    
727      /**
728       * Creates the bind request to use to authenticate to the server.
729       *
730       * @return  The bind request to use to authenticate to the server, or
731       *          {@code null} if no bind should be performed.
732       *
733       * @throws  LDAPException  If a problem occurs while creating the bind
734       *                         request.
735       */
736      private BindRequest createBindRequest()
737              throws LDAPException
738      {
739        final String pw;
740        if (bindPassword.isPresent())
741        {
742          pw = bindPassword.getValue();
743        }
744        else if (bindPasswordFile.isPresent())
745        {
746          try
747          {
748            pw = bindPasswordFile.getNonBlankFileLines().get(0);
749          }
750          catch (Exception e)
751          {
752            debugException(e);
753            throw new LDAPException(ResultCode.LOCAL_ERROR,
754                 ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
755                      getExceptionMessage(e)), e);
756          }
757        }
758        else
759        {
760          pw = null;
761        }
762    
763        if (saslOption.isPresent())
764        {
765          final String dnStr;
766          if (bindDN.isPresent())
767          {
768            dnStr = bindDN.getValue().toString();
769          }
770          else
771          {
772            dnStr = null;
773          }
774    
775          return SASLUtils.createBindRequest(dnStr, pw, null,
776               saslOption.getValues());
777        }
778        else if (bindDN.isPresent())
779        {
780          return new SimpleBindRequest(bindDN.getValue(), pw);
781        }
782        else
783        {
784          return null;
785        }
786      }
787    }