001    /*
002     * Copyright 2008-2014 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2014 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>"--promptForBindPassword" -- Indicates that the tool should
091     *       interactively prompt the user for the bind password.</LI>
092     *   <LI>"-Z" or "--useSSL" -- Indicates that the communication with the server
093     *       should be secured using SSL.</LI>
094     *   <LI>"-q" or "--useStartTLS" -- Indicates that the communication with the
095     *       server should be secured using StartTLS.</LI>
096     *   <LI>"-X" or "--trustAll" -- Indicates that the client should trust any
097     *       certificate that the server presents to it.</LI>
098     *   <LI>"-K {path}" or "--keyStorePath {path}" -- Specifies the path to the
099     *       key store to use to obtain client certificates.</LI>
100     *   <LI>"-W {password}" or "--keyStorePassword {password}" -- Specifies the
101     *       password to use to access the contents of the key store.</LI>
102     *   <LI>"-u {path}" or "--keyStorePasswordFile {path}" -- Specifies the path to
103     *       the file containing the password to use to access the contents of the
104     *       key store.</LI>
105     *   <LI>"--promptForKeyStorePassword" -- Indicates that the tool should
106     *       interactively prompt the user for the key store password.</LI>
107     *   <LI>"--keyStoreFormat {format}" -- Specifies the format to use for the key
108     *       store file.</LI>
109     *   <LI>"-P {path}" or "--trustStorePath {path}" -- Specifies the path to the
110     *       trust store to use when determining whether to trust server
111     *       certificates.</LI>
112     *   <LI>"-T {password}" or "--trustStorePassword {password}" -- Specifies the
113     *       password to use to access the contents of the trust store.</LI>
114     *   <LI>"-U {path}" or "--trustStorePasswordFile {path}" -- Specifies the path
115     *       to the file containing the password to use to access the contents of
116     *       the trust store.</LI>
117     *   <LI>"--promptForTrustStorePassword" -- Indicates that the tool should
118     *       interactively prompt the user for the trust store password.</LI>
119     *   <LI>"--trustStoreFormat {format}" -- Specifies the format to use for the
120     *       trust store file.</LI>
121     *   <LI>"-N {nickname}" or "--certNickname {nickname}" -- Specifies the
122     *       nickname of the client certificate to use when performing SSL client
123     *       authentication.</LI>
124     *   <LI>"-o {name=value}" or "--saslOption {name=value}" -- Specifies a SASL
125     *       option to use when performing SASL authentication.</LI>
126     * </UL>
127     * If SASL authentication is to be used, then a "mech" SASL option must be
128     * provided to specify the name of the SASL mechanism to use (e.g.,
129     * "--saslOption mech=EXTERNAL" indicates that the EXTERNAL mechanism should be
130     * used).  Depending on the SASL mechanism, additional SASL options may be
131     * required or optional.  They include:
132     * <UL>
133     *   <LI>
134     *     mech=ANONYMOUS
135     *     <UL>
136     *       <LI>Required SASL options:  </LI>
137     *       <LI>Optional SASL options:  trace</LI>
138     *     </UL>
139     *   </LI>
140     *   <LI>
141     *     mech=CRAM-MD5
142     *     <UL>
143     *       <LI>Required SASL options:  authID</LI>
144     *       <LI>Optional SASL options:  </LI>
145     *     </UL>
146     *   </LI>
147     *   <LI>
148     *     mech=DIGEST-MD5
149     *     <UL>
150     *       <LI>Required SASL options:  authID</LI>
151     *       <LI>Optional SASL options:  authzID, realm</LI>
152     *     </UL>
153     *   </LI>
154     *   <LI>
155     *     mech=EXTERNAL
156     *     <UL>
157     *       <LI>Required SASL options:  </LI>
158     *       <LI>Optional SASL options:  </LI>
159     *     </UL>
160     *   </LI>
161     *   <LI>
162     *     mech=GSSAPI
163     *     <UL>
164     *       <LI>Required SASL options:  authID</LI>
165     *       <LI>Optional SASL options:  authzID, configFile, debug, protocol,
166     *                realm, kdcAddress, useTicketCache, requireCache,
167     *                renewTGT, ticketCachePath</LI>
168     *     </UL>
169     *   </LI>
170     *   <LI>
171     *     mech=PLAIN
172     *     <UL>
173     *       <LI>Required SASL options:  authID</LI>
174     *       <LI>Optional SASL options:  authzID</LI>
175     *     </UL>
176     *   </LI>
177     * </UL>
178     * <BR><BR>
179     * Note that in general, methods in this class are not threadsafe.  However, the
180     * {@link #getConnection()} and {@link #getConnectionPool(int,int)} methods may
181     * be invoked concurrently by multiple threads accessing the same instance only
182     * while that instance is in the process of invoking the
183     * {@link #doToolProcessing()} method.
184     */
185    @Extensible()
186    @ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
187    public abstract class LDAPCommandLineTool
188           extends CommandLineTool
189    {
190    
191    
192    
193      // Arguments used to communicate with an LDAP directory server.
194      private BooleanArgument promptForBindPassword       = null;
195      private BooleanArgument promptForKeyStorePassword   = null;
196      private BooleanArgument promptForTrustStorePassword = null;
197      private BooleanArgument trustAll                    = null;
198      private BooleanArgument useSSL                      = null;
199      private BooleanArgument useStartTLS                 = null;
200      private DNArgument      bindDN                      = null;
201      private FileArgument    bindPasswordFile            = null;
202      private FileArgument    keyStorePasswordFile        = null;
203      private FileArgument    trustStorePasswordFile      = null;
204      private IntegerArgument port                        = null;
205      private StringArgument  bindPassword                = null;
206      private StringArgument  certificateNickname         = null;
207      private StringArgument  host                        = null;
208      private StringArgument  keyStoreFormat              = null;
209      private StringArgument  keyStorePath                = null;
210      private StringArgument  keyStorePassword            = null;
211      private StringArgument  saslOption                  = null;
212      private StringArgument  trustStoreFormat            = null;
213      private StringArgument  trustStorePath              = null;
214      private StringArgument  trustStorePassword          = null;
215    
216      // Variables used when creating and authenticating connections.
217      private BindRequest bindRequest     = null;
218      private ServerSet   serverSet       = null;
219      private SSLContext  startTLSContext = null;
220    
221      // The prompt trust manager that will be shared by all connections created
222      // for which it is appropriate.  This will allow them to benefit from the
223      // common cache.
224      private final AtomicReference<PromptTrustManager> promptTrustManager;
225    
226    
227    
228      /**
229       * Creates a new instance of this LDAP-enabled command-line tool with the
230       * provided information.
231       *
232       * @param  outStream  The output stream to use for standard output.  It may be
233       *                    {@code System.out} for the JVM's default standard output
234       *                    stream, {@code null} if no output should be generated,
235       *                    or a custom output stream if the output should be sent
236       *                    to an alternate location.
237       * @param  errStream  The output stream to use for standard error.  It may be
238       *                    {@code System.err} for the JVM's default standard error
239       *                    stream, {@code null} if no output should be generated,
240       *                    or a custom output stream if the output should be sent
241       *                    to an alternate location.
242       */
243      public LDAPCommandLineTool(final OutputStream outStream,
244                                 final OutputStream errStream)
245      {
246        super(outStream, errStream);
247    
248        promptTrustManager = new AtomicReference<PromptTrustManager>();
249      }
250    
251    
252    
253      /**
254       * {@inheritDoc}
255       */
256      @Override()
257      public final void addToolArguments(final ArgumentParser parser)
258             throws ArgumentException
259      {
260        host = new StringArgument('h', "hostname", true,
261             (supportsMultipleServers() ? 0 : 1),
262             INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(),
263             INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost");
264        parser.addArgument(host);
265    
266        port = new IntegerArgument('p', "port", true,
267             (supportsMultipleServers() ? 0 : 1),
268             INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(),
269             INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65535, 389);
270        parser.addArgument(port);
271    
272        final boolean supportsAuthentication = supportsAuthentication();
273        if (supportsAuthentication)
274        {
275          bindDN = new DNArgument('D', "bindDN", false, 1,
276               INFO_LDAP_TOOL_PLACEHOLDER_DN.get(),
277               INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get());
278          parser.addArgument(bindDN);
279    
280          bindPassword = new StringArgument('w', "bindPassword", false, 1,
281               INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
282               INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get());
283          parser.addArgument(bindPassword);
284    
285          bindPasswordFile = new FileArgument('j', "bindPasswordFile", false, 1,
286               INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
287               INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true,
288               false);
289          parser.addArgument(bindPasswordFile);
290    
291          promptForBindPassword = new BooleanArgument(null, "promptForBindPassword",
292               1, INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_PROMPT.get());
293          parser.addArgument(promptForBindPassword);
294        }
295    
296        useSSL = new BooleanArgument('Z', "useSSL", 1,
297             INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get());
298        parser.addArgument(useSSL);
299    
300        useStartTLS = new BooleanArgument('q', "useStartTLS", 1,
301             INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get());
302        parser.addArgument(useStartTLS);
303    
304        trustAll = new BooleanArgument('X', "trustAll", 1,
305             INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get());
306        parser.addArgument(trustAll);
307    
308        keyStorePath = new StringArgument('K', "keyStorePath", false, 1,
309             INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
310             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get());
311        parser.addArgument(keyStorePath);
312    
313        keyStorePassword = new StringArgument('W', "keyStorePassword", false, 1,
314             INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
315             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get());
316        parser.addArgument(keyStorePassword);
317    
318        keyStorePasswordFile = new FileArgument('u', "keyStorePasswordFile", false,
319             1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
320             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get());
321        parser.addArgument(keyStorePasswordFile);
322    
323        promptForKeyStorePassword = new BooleanArgument(null,
324             "promptForKeyStorePassword", 1,
325             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_PROMPT.get());
326        parser.addArgument(promptForKeyStorePassword);
327    
328        keyStoreFormat = new StringArgument(null, "keyStoreFormat", false, 1,
329             INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
330             INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get());
331        parser.addArgument(keyStoreFormat);
332    
333        trustStorePath = new StringArgument('P', "trustStorePath", false, 1,
334             INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
335             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get());
336        parser.addArgument(trustStorePath);
337    
338        trustStorePassword = new StringArgument('T', "trustStorePassword", false, 1,
339             INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
340             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get());
341        parser.addArgument(trustStorePassword);
342    
343        trustStorePasswordFile = new FileArgument('U', "trustStorePasswordFile",
344             false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
345             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get());
346        parser.addArgument(trustStorePasswordFile);
347    
348        promptForTrustStorePassword = new BooleanArgument(null,
349             "promptForTrustStorePassword", 1,
350             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_PROMPT.get());
351        parser.addArgument(promptForTrustStorePassword);
352    
353        trustStoreFormat = new StringArgument(null, "trustStoreFormat", false, 1,
354             INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
355             INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get());
356        parser.addArgument(trustStoreFormat);
357    
358        certificateNickname = new StringArgument('N', "certNickname", false, 1,
359             INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(),
360             INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get());
361        parser.addArgument(certificateNickname);
362    
363        if (supportsAuthentication)
364        {
365          saslOption = new StringArgument('o', "saslOption", false, 0,
366               INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(),
367               INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get());
368          parser.addArgument(saslOption);
369        }
370    
371    
372        // Both useSSL and useStartTLS cannot be used together.
373        parser.addExclusiveArgumentSet(useSSL, useStartTLS);
374    
375        // Only one option may be used for specifying the key store password.
376        parser.addExclusiveArgumentSet(keyStorePassword, keyStorePasswordFile,
377             promptForKeyStorePassword);
378    
379        // Only one option may be used for specifying the trust store password.
380        parser.addExclusiveArgumentSet(trustStorePassword, trustStorePasswordFile,
381             promptForTrustStorePassword);
382    
383        // It doesn't make sense to provide a trust store path if any server
384        // certificate should be trusted.
385        parser.addExclusiveArgumentSet(trustAll, trustStorePath);
386    
387        // If a key store password is provided, then a key store path must have also
388        // been provided.
389        parser.addDependentArgumentSet(keyStorePassword, keyStorePath);
390        parser.addDependentArgumentSet(keyStorePasswordFile, keyStorePath);
391        parser.addDependentArgumentSet(promptForKeyStorePassword, keyStorePath);
392    
393        // If a trust store password is provided, then a trust store path must have
394        // also been provided.
395        parser.addDependentArgumentSet(trustStorePassword, trustStorePath);
396        parser.addDependentArgumentSet(trustStorePasswordFile, trustStorePath);
397        parser.addDependentArgumentSet(promptForTrustStorePassword, trustStorePath);
398    
399        // If a key or trust store path is provided, then the tool must either use
400        // SSL or StartTLS.
401        parser.addDependentArgumentSet(keyStorePath, useSSL, useStartTLS);
402        parser.addDependentArgumentSet(trustStorePath, useSSL, useStartTLS);
403    
404        // If the tool should trust all server certificates, then the tool must
405        // either use SSL or StartTLS.
406        parser.addDependentArgumentSet(trustAll, useSSL, useStartTLS);
407    
408        if (supportsAuthentication)
409        {
410          // If a bind DN was provided, then a bind password must have also been
411          // provided.
412          parser.addDependentArgumentSet(bindDN, bindPassword, bindPasswordFile,
413               promptForBindPassword);
414    
415          // Only one option may be used for specifying the bind password.
416          parser.addExclusiveArgumentSet(bindPassword, bindPasswordFile,
417               promptForBindPassword);
418    
419          // If a bind password was provided, then the a bind DN or SASL option
420          // must have also been provided.
421          parser.addDependentArgumentSet(bindPassword, bindDN, saslOption);
422          parser.addDependentArgumentSet(bindPasswordFile, bindDN, saslOption);
423          parser.addDependentArgumentSet(promptForBindPassword, bindDN, saslOption);
424        }
425    
426        addNonLDAPArguments(parser);
427      }
428    
429    
430    
431      /**
432       * Adds the arguments needed by this command-line tool to the provided
433       * argument parser which are not related to connecting or authenticating to
434       * the directory server.
435       *
436       * @param  parser  The argument parser to which the arguments should be added.
437       *
438       * @throws  ArgumentException  If a problem occurs while adding the arguments.
439       */
440      public abstract void addNonLDAPArguments(final ArgumentParser parser)
441             throws ArgumentException;
442    
443    
444    
445      /**
446       * {@inheritDoc}
447       */
448      @Override()
449      public final void doExtendedArgumentValidation()
450             throws ArgumentException
451      {
452        // If more than one hostname or port number was provided, then make sure
453        // that the same number of values were provided for each.
454        if ((host.getValues().size() > 1) || (port.getValues().size() > 1))
455        {
456          if (host.getValues().size() != port.getValues().size())
457          {
458            throw new ArgumentException(
459                 ERR_LDAP_TOOL_HOST_PORT_COUNT_MISMATCH.get(
460                      host.getLongIdentifier(), port.getLongIdentifier()));
461          }
462        }
463    
464    
465        doExtendedNonLDAPArgumentValidation();
466      }
467    
468    
469    
470      /**
471       * Indicates whether this tool should provide the arguments that allow it to
472       * bind via simple or SASL authentication.
473       *
474       * @return  {@code true} if this tool should provide the arguments that allow
475       *          it to bind via simple or SASL authentication, or {@code false} if
476       *          not.
477       */
478      protected boolean supportsAuthentication()
479      {
480        return true;
481      }
482    
483    
484    
485      /**
486       * Indicates whether this tool supports creating connections to multiple
487       * servers.  If it is to support multiple servers, then the "--hostname" and
488       * "--port" arguments will be allowed to be provided multiple times, and
489       * will be required to be provided the same number of times.  The same type of
490       * communication security and bind credentials will be used for all servers.
491       *
492       * @return  {@code true} if this tool supports creating connections to
493       *          multiple servers, or {@code false} if not.
494       */
495      protected boolean supportsMultipleServers()
496      {
497        return false;
498      }
499    
500    
501    
502      /**
503       * Performs any necessary processing that should be done to ensure that the
504       * provided set of command-line arguments were valid.  This method will be
505       * called after the basic argument parsing has been performed and after all
506       * LDAP-specific argument validation has been processed, and immediately
507       * before the {@link CommandLineTool#doToolProcessing} method is invoked.
508       *
509       * @throws  ArgumentException  If there was a problem with the command-line
510       *                             arguments provided to this program.
511       */
512      public void doExtendedNonLDAPArgumentValidation()
513             throws ArgumentException
514      {
515        // No processing will be performed by default.
516      }
517    
518    
519    
520      /**
521       * Retrieves the connection options that should be used for connections that
522       * are created with this command line tool.  Subclasses may override this
523       * method to use a custom set of connection options.
524       *
525       * @return  The connection options that should be used for connections that
526       *          are created with this command line tool.
527       */
528      public LDAPConnectionOptions getConnectionOptions()
529      {
530        return new LDAPConnectionOptions();
531      }
532    
533    
534    
535      /**
536       * Retrieves a connection that may be used to communicate with the target
537       * directory server.
538       * <BR><BR>
539       * Note that this method is threadsafe and may be invoked by multiple threads
540       * accessing the same instance only while that instance is in the process of
541       * invoking the {@link #doToolProcessing} method.
542       *
543       * @return  A connection that may be used to communicate with the target
544       *          directory server.
545       *
546       * @throws  LDAPException  If a problem occurs while creating the connection.
547       */
548      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
549      public final LDAPConnection getConnection()
550             throws LDAPException
551      {
552        final LDAPConnection connection = getUnauthenticatedConnection();
553    
554        try
555        {
556          if (bindRequest != null)
557          {
558            connection.bind(bindRequest);
559          }
560        }
561        catch (LDAPException le)
562        {
563          debugException(le);
564          connection.close();
565          throw le;
566        }
567    
568        return connection;
569      }
570    
571    
572    
573      /**
574       * Retrieves an unauthenticated connection that may be used to communicate
575       * with the target directory server.
576       * <BR><BR>
577       * Note that this method is threadsafe and may be invoked by multiple threads
578       * accessing the same instance only while that instance is in the process of
579       * invoking the {@link #doToolProcessing} method.
580       *
581       * @return  An unauthenticated connection that may be used to communicate with
582       *          the target directory server.
583       *
584       * @throws  LDAPException  If a problem occurs while creating the connection.
585       */
586      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
587      public final LDAPConnection getUnauthenticatedConnection()
588             throws LDAPException
589      {
590        if (serverSet == null)
591        {
592          serverSet   = createServerSet();
593          bindRequest = createBindRequest();
594        }
595    
596        final LDAPConnection connection = serverSet.getConnection();
597    
598        if (useStartTLS.isPresent())
599        {
600          try
601          {
602            final ExtendedResult extendedResult =
603                 connection.processExtendedOperation(
604                      new StartTLSExtendedRequest(startTLSContext));
605            if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
606            {
607              throw new LDAPException(extendedResult.getResultCode(),
608                   ERR_LDAP_TOOL_START_TLS_FAILED.get(
609                        extendedResult.getDiagnosticMessage()));
610            }
611          }
612          catch (LDAPException le)
613          {
614            debugException(le);
615            connection.close();
616            throw le;
617          }
618        }
619    
620        return connection;
621      }
622    
623    
624    
625      /**
626       * Retrieves a connection pool that may be used to communicate with the target
627       * directory server.
628       * <BR><BR>
629       * Note that this method is threadsafe and may be invoked by multiple threads
630       * accessing the same instance only while that instance is in the process of
631       * invoking the {@link #doToolProcessing} method.
632       *
633       * @param  initialConnections  The number of connections that should be
634       *                             initially established in the pool.
635       * @param  maxConnections      The maximum number of connections to maintain
636       *                             in the pool.
637       *
638       * @return  A connection that may be used to communicate with the target
639       *          directory server.
640       *
641       * @throws  LDAPException  If a problem occurs while creating the connection
642       *                         pool.
643       */
644      @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
645      public final LDAPConnectionPool getConnectionPool(
646                                           final int initialConnections,
647                                           final int maxConnections)
648                throws LDAPException
649      {
650        if (serverSet == null)
651        {
652          serverSet   = createServerSet();
653          bindRequest = createBindRequest();
654        }
655    
656        PostConnectProcessor postConnectProcessor = null;
657        if (useStartTLS.isPresent())
658        {
659          postConnectProcessor = new StartTLSPostConnectProcessor(startTLSContext);
660        }
661    
662        return new LDAPConnectionPool(serverSet, bindRequest, initialConnections,
663                                      maxConnections, postConnectProcessor);
664      }
665    
666    
667    
668      /**
669       * Creates the server set to use when creating connections or connection
670       * pools.
671       *
672       * @return  The server set to use when creating connections or connection
673       *          pools.
674       *
675       * @throws  LDAPException  If a problem occurs while creating the server set.
676       */
677      public ServerSet createServerSet()
678             throws LDAPException
679      {
680        final SSLUtil sslUtil = createSSLUtil();
681    
682        SocketFactory socketFactory = null;
683        if (useSSL.isPresent())
684        {
685          try
686          {
687            socketFactory = sslUtil.createSSLSocketFactory();
688          }
689          catch (Exception e)
690          {
691            debugException(e);
692            throw new LDAPException(ResultCode.LOCAL_ERROR,
693                 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
694                      getExceptionMessage(e)), e);
695          }
696        }
697        else if (useStartTLS.isPresent())
698        {
699          try
700          {
701            startTLSContext = sslUtil.createSSLContext();
702          }
703          catch (Exception e)
704          {
705            debugException(e);
706            throw new LDAPException(ResultCode.LOCAL_ERROR,
707                 ERR_LDAP_TOOL_CANNOT_CREATE_SSL_CONTEXT.get(
708                      getExceptionMessage(e)), e);
709          }
710        }
711    
712        if (host.getValues().size() == 1)
713        {
714          return new SingleServerSet(host.getValue(), port.getValue(),
715                                     socketFactory, getConnectionOptions());
716        }
717        else
718        {
719          final List<String>  hostList = host.getValues();
720          final List<Integer> portList = port.getValues();
721    
722          final String[] hosts = new String[hostList.size()];
723          final int[]    ports = new int[hosts.length];
724    
725          for (int i=0; i < hosts.length; i++)
726          {
727            hosts[i] = hostList.get(i);
728            ports[i] = portList.get(i);
729          }
730    
731          return new RoundRobinServerSet(hosts, ports, socketFactory,
732                                         getConnectionOptions());
733        }
734      }
735    
736    
737    
738      /**
739       * Creates the SSLUtil instance to use for secure communication.
740       *
741       * @return  The SSLUtil instance to use for secure communication, or
742       *          {@code null} if secure communication is not needed.
743       *
744       * @throws  LDAPException  If a problem occurs while creating the SSLUtil
745       *                         instance.
746       */
747      public SSLUtil createSSLUtil()
748             throws LDAPException
749      {
750        return createSSLUtil(false);
751      }
752    
753    
754    
755      /**
756       * Creates the SSLUtil instance to use for secure communication.
757       *
758       * @param  force  Indicates whether to create the SSLUtil object even if
759       *                neither the "--useSSL" nor the "--useStartTLS" argument was
760       *                provided.  The key store and/or trust store paths must still
761       *                have been provided.  This may be useful for tools that
762       *                accept SSL-based communication but do not themselves intend
763       *                to perform SSL-based communication as an LDAP client.
764       *
765       * @return  The SSLUtil instance to use for secure communication, or
766       *          {@code null} if secure communication is not needed.
767       *
768       * @throws  LDAPException  If a problem occurs while creating the SSLUtil
769       *                         instance.
770       */
771      public SSLUtil createSSLUtil(final boolean force)
772             throws LDAPException
773      {
774        if (force || useSSL.isPresent() || useStartTLS.isPresent())
775        {
776          KeyManager keyManager = null;
777          if (keyStorePath.isPresent())
778          {
779            char[] pw = null;
780            if (keyStorePassword.isPresent())
781            {
782              pw = keyStorePassword.getValue().toCharArray();
783            }
784            else if (keyStorePasswordFile.isPresent())
785            {
786              try
787              {
788                pw = keyStorePasswordFile.getNonBlankFileLines().get(0).
789                          toCharArray();
790              }
791              catch (Exception e)
792              {
793                debugException(e);
794                throw new LDAPException(ResultCode.LOCAL_ERROR,
795                     ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get(
796                          getExceptionMessage(e)), e);
797              }
798            }
799            else if (promptForKeyStorePassword.isPresent())
800            {
801              getOut().print(INFO_LDAP_TOOL_ENTER_KEY_STORE_PASSWORD.get());
802              pw = StaticUtils.toUTF8String(
803                   PasswordReader.readPassword()).toCharArray();
804              getOut().println();
805            }
806    
807            try
808            {
809              keyManager = new KeyStoreKeyManager(keyStorePath.getValue(), pw,
810                   keyStoreFormat.getValue(), certificateNickname.getValue());
811            }
812            catch (Exception e)
813            {
814              debugException(e);
815              throw new LDAPException(ResultCode.LOCAL_ERROR,
816                   ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
817                        getExceptionMessage(e)), e);
818            }
819          }
820    
821          TrustManager trustManager;
822          if (trustAll.isPresent())
823          {
824            trustManager = new TrustAllTrustManager(false);
825          }
826          else if (trustStorePath.isPresent())
827          {
828            char[] pw = null;
829            if (trustStorePassword.isPresent())
830            {
831              pw = trustStorePassword.getValue().toCharArray();
832            }
833            else if (trustStorePasswordFile.isPresent())
834            {
835              try
836              {
837                pw = trustStorePasswordFile.getNonBlankFileLines().get(0).
838                          toCharArray();
839              }
840              catch (Exception e)
841              {
842                debugException(e);
843                throw new LDAPException(ResultCode.LOCAL_ERROR,
844                     ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
845                          getExceptionMessage(e)), e);
846              }
847            }
848            else if (promptForTrustStorePassword.isPresent())
849            {
850              getOut().print(INFO_LDAP_TOOL_ENTER_TRUST_STORE_PASSWORD.get());
851              pw = StaticUtils.toUTF8String(
852                   PasswordReader.readPassword()).toCharArray();
853              getOut().println();
854            }
855    
856            trustManager = new TrustStoreTrustManager(trustStorePath.getValue(), pw,
857                 trustStoreFormat.getValue(), true);
858          }
859          else
860          {
861            trustManager = promptTrustManager.get();
862            if (trustManager == null)
863            {
864              final PromptTrustManager m = new PromptTrustManager();
865              promptTrustManager.compareAndSet(null, m);
866              trustManager = promptTrustManager.get();
867            }
868          }
869    
870          return new SSLUtil(keyManager, trustManager);
871        }
872        else
873        {
874          return null;
875        }
876      }
877    
878    
879    
880      /**
881       * Creates the bind request to use to authenticate to the server.
882       *
883       * @return  The bind request to use to authenticate to the server, or
884       *          {@code null} if no bind should be performed.
885       *
886       * @throws  LDAPException  If a problem occurs while creating the bind
887       *                         request.
888       */
889      public BindRequest createBindRequest()
890             throws LDAPException
891      {
892        if (! supportsAuthentication())
893        {
894          return null;
895        }
896    
897        final String pw;
898        if (bindPassword.isPresent())
899        {
900          pw = bindPassword.getValue();
901        }
902        else if (bindPasswordFile.isPresent())
903        {
904          try
905          {
906            pw = bindPasswordFile.getNonBlankFileLines().get(0);
907          }
908          catch (Exception e)
909          {
910            debugException(e);
911            throw new LDAPException(ResultCode.LOCAL_ERROR,
912                 ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
913                      getExceptionMessage(e)), e);
914          }
915        }
916        else if (promptForBindPassword.isPresent())
917        {
918          getOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
919          pw = StaticUtils.toUTF8String(PasswordReader.readPassword());
920          getOut().println();
921        }
922        else
923        {
924          pw = null;
925        }
926    
927        if (saslOption.isPresent())
928        {
929          final String dnStr;
930          if (bindDN.isPresent())
931          {
932            dnStr = bindDN.getValue().toString();
933          }
934          else
935          {
936            dnStr = null;
937          }
938    
939          return SASLUtils.createBindRequest(dnStr, pw, null,
940               saslOption.getValues());
941        }
942        else if (bindDN.isPresent())
943        {
944          return new SimpleBindRequest(bindDN.getValue(), pw);
945        }
946        else
947        {
948          return null;
949        }
950      }
951    }