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