001    /*
002     * Copyright 2008-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2013 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        final LDAPConnection connection = getUnauthenticatedConnection();
462    
463        try
464        {
465          if (bindRequest != null)
466          {
467            connection.bind(bindRequest);
468          }
469        }
470        catch (LDAPException le)
471        {
472          debugException(le);
473          connection.close();
474          throw le;
475        }
476    
477        return connection;
478      }
479    
480    
481    
482      /**
483       * Retrieves an unauthenticated connection that may be used to communicate
484       * with the target directory server.
485       * <BR><BR>
486       * Note that this method is threadsafe and may be invoked by multiple threads
487       * accessing the same instance only while that instance is in the process of
488       * invoking the {@link #doToolProcessing} method.
489       *
490       * @return  An unauthenticated connection that may be used to communicate with
491       *          the target directory server.
492       *
493       * @throws  LDAPException  If a problem occurs while creating the connection.
494       */
495      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
496      public final LDAPConnection getUnauthenticatedConnection()
497             throws LDAPException
498      {
499        if (serverSet == null)
500        {
501          serverSet   = createServerSet();
502          bindRequest = createBindRequest();
503        }
504    
505        final LDAPConnection connection = serverSet.getConnection();
506    
507        if (useStartTLS.isPresent())
508        {
509          try
510          {
511            final ExtendedResult extendedResult =
512                 connection.processExtendedOperation(
513                      new StartTLSExtendedRequest(startTLSContext));
514            if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
515            {
516              throw new LDAPException(extendedResult.getResultCode(),
517                   ERR_LDAP_TOOL_START_TLS_FAILED.get(
518                        extendedResult.getDiagnosticMessage()));
519            }
520          }
521          catch (LDAPException le)
522          {
523            debugException(le);
524            connection.close();
525            throw le;
526          }
527        }
528    
529        return connection;
530      }
531    
532    
533    
534      /**
535       * Retrieves a connection pool that may be used to communicate with the target
536       * directory server.
537       * <BR><BR>
538       * Note that this method is threadsafe and may be invoked by multiple threads
539       * accessing the same instance only while that instance is in the process of
540       * invoking the {@link #doToolProcessing} method.
541       *
542       * @param  initialConnections  The number of connections that should be
543       *                             initially established in the pool.
544       * @param  maxConnections      The maximum number of connections to maintain
545       *                             in the pool.
546       *
547       * @return  A connection that may be used to communicate with the target
548       *          directory server.
549       *
550       * @throws  LDAPException  If a problem occurs while creating the connection
551       *                         pool.
552       */
553      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
554      public final LDAPConnectionPool getConnectionPool(
555                                           final int initialConnections,
556                                           final int maxConnections)
557                throws LDAPException
558      {
559        if (serverSet == null)
560        {
561          serverSet   = createServerSet();
562          bindRequest = createBindRequest();
563        }
564    
565        PostConnectProcessor postConnectProcessor = null;
566        if (useStartTLS.isPresent())
567        {
568          postConnectProcessor = new StartTLSPostConnectProcessor(startTLSContext);
569        }
570    
571        return new LDAPConnectionPool(serverSet, bindRequest, initialConnections,
572                                      maxConnections, postConnectProcessor);
573      }
574    
575    
576    
577      /**
578       * Creates the server set to use when creating connections or connection
579       * pools.
580       *
581       * @return  The server set to use when creating connections or connection
582       *          pools.
583       *
584       * @throws  LDAPException  If a problem occurs while creating the server set.
585       */
586      public ServerSet createServerSet()
587             throws LDAPException
588      {
589        final SSLUtil sslUtil = createSSLUtil();
590    
591        SocketFactory socketFactory = null;
592        if (useSSL.isPresent())
593        {
594          try
595          {
596            socketFactory = sslUtil.createSSLSocketFactory();
597          }
598          catch (Exception e)
599          {
600            debugException(e);
601            throw new LDAPException(ResultCode.LOCAL_ERROR,
602                 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
603                      getExceptionMessage(e)), e);
604          }
605        }
606        else if (useStartTLS.isPresent())
607        {
608          try
609          {
610            startTLSContext = sslUtil.createSSLContext();
611          }
612          catch (Exception e)
613          {
614            debugException(e);
615            throw new LDAPException(ResultCode.LOCAL_ERROR,
616                 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_CONTEXT.get(
617                      getExceptionMessage(e)), e);
618          }
619        }
620    
621        if (host.getValues().size() == 1)
622        {
623          return new SingleServerSet(host.getValue(), port.getValue(),
624                                     socketFactory, getConnectionOptions());
625        }
626        else
627        {
628          final List<String>  hostList = host.getValues();
629          final List<Integer> portList = port.getValues();
630    
631          final String[] hosts = new String[hostList.size()];
632          final int[]    ports = new int[hosts.length];
633    
634          for (int i=0; i < hosts.length; i++)
635          {
636            hosts[i] = hostList.get(i);
637            ports[i] = portList.get(i);
638          }
639    
640          return new RoundRobinServerSet(hosts, ports, socketFactory,
641                                         getConnectionOptions());
642        }
643      }
644    
645    
646    
647      /**
648       * Creates the SSLUtil instance to use for secure communication.
649       *
650       * @return  The SSLUtil instance to use for secure communication, or
651       *          {@code null} if secure communication is not needed.
652       *
653       * @throws  LDAPException  If a problem occurs while creating the SSLUtil
654       *                         instance.
655       */
656      public SSLUtil createSSLUtil()
657             throws LDAPException
658      {
659        return createSSLUtil(false);
660      }
661    
662    
663    
664      /**
665       * Creates the SSLUtil instance to use for secure communication.
666       *
667       * @param  force  Indicates whether to create the SSLUtil object even if
668       *                neither the "--useSSL" nor the "--useStartTLS" argument was
669       *                provided.  The key store and/or trust store paths must still
670       *                have been provided.  This may be useful for tools that
671       *                accept SSL-based communication but do not themselves intend
672       *                to perform SSL-based communication as an LDAP client.
673       *
674       * @return  The SSLUtil instance to use for secure communication, or
675       *          {@code null} if secure communication is not needed.
676       *
677       * @throws  LDAPException  If a problem occurs while creating the SSLUtil
678       *                         instance.
679       */
680      public SSLUtil createSSLUtil(final boolean force)
681             throws LDAPException
682      {
683        if (force || useSSL.isPresent() || useStartTLS.isPresent())
684        {
685          KeyManager keyManager = null;
686          if (keyStorePath.isPresent())
687          {
688            char[] pw = null;
689            if (keyStorePassword.isPresent())
690            {
691              pw = keyStorePassword.getValue().toCharArray();
692            }
693            else if (keyStorePasswordFile.isPresent())
694            {
695              try
696              {
697                pw = keyStorePasswordFile.getNonBlankFileLines().get(0).
698                          toCharArray();
699              }
700              catch (Exception e)
701              {
702                debugException(e);
703                throw new LDAPException(ResultCode.LOCAL_ERROR,
704                     ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get(
705                          getExceptionMessage(e)), e);
706              }
707            }
708    
709            try
710            {
711              keyManager = new KeyStoreKeyManager(keyStorePath.getValue(), pw,
712                   keyStoreFormat.getValue(), certificateNickname.getValue());
713            }
714            catch (Exception e)
715            {
716              debugException(e);
717              throw new LDAPException(ResultCode.LOCAL_ERROR,
718                   ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
719                        getExceptionMessage(e)), e);
720            }
721          }
722    
723          TrustManager trustManager;
724          if (trustAll.isPresent())
725          {
726            trustManager = new TrustAllTrustManager(false);
727          }
728          else if (trustStorePath.isPresent())
729          {
730            char[] pw = null;
731            if (trustStorePassword.isPresent())
732            {
733              pw = trustStorePassword.getValue().toCharArray();
734            }
735            else if (trustStorePasswordFile.isPresent())
736            {
737              try
738              {
739                pw = trustStorePasswordFile.getNonBlankFileLines().get(0).
740                          toCharArray();
741              }
742              catch (Exception e)
743              {
744                debugException(e);
745                throw new LDAPException(ResultCode.LOCAL_ERROR,
746                     ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
747                          getExceptionMessage(e)), e);
748              }
749            }
750    
751            trustManager = new TrustStoreTrustManager(trustStorePath.getValue(), pw,
752                 trustStoreFormat.getValue(), true);
753          }
754          else
755          {
756            trustManager = promptTrustManager.get();
757            if (trustManager == null)
758            {
759              final PromptTrustManager m = new PromptTrustManager();
760              promptTrustManager.compareAndSet(null, m);
761              trustManager = promptTrustManager.get();
762            }
763          }
764    
765          return new SSLUtil(keyManager, trustManager);
766        }
767        else
768        {
769          return null;
770        }
771      }
772    
773    
774    
775      /**
776       * Creates the bind request to use to authenticate to the server.
777       *
778       * @return  The bind request to use to authenticate to the server, or
779       *          {@code null} if no bind should be performed.
780       *
781       * @throws  LDAPException  If a problem occurs while creating the bind
782       *                         request.
783       */
784      public BindRequest createBindRequest()
785             throws LDAPException
786      {
787        final String pw;
788        if (bindPassword.isPresent())
789        {
790          pw = bindPassword.getValue();
791        }
792        else if (bindPasswordFile.isPresent())
793        {
794          try
795          {
796            pw = bindPasswordFile.getNonBlankFileLines().get(0);
797          }
798          catch (Exception e)
799          {
800            debugException(e);
801            throw new LDAPException(ResultCode.LOCAL_ERROR,
802                 ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
803                      getExceptionMessage(e)), e);
804          }
805        }
806        else
807        {
808          pw = null;
809        }
810    
811        if (saslOption.isPresent())
812        {
813          final String dnStr;
814          if (bindDN.isPresent())
815          {
816            dnStr = bindDN.getValue().toString();
817          }
818          else
819          {
820            dnStr = null;
821          }
822    
823          return SASLUtils.createBindRequest(dnStr, pw, null,
824               saslOption.getValues());
825        }
826        else if (bindDN.isPresent())
827        {
828          return new SimpleBindRequest(bindDN.getValue(), pw);
829        }
830        else
831        {
832          return null;
833        }
834      }
835    }