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 */
021package com.unboundid.util;
022
023
024
025import java.io.OutputStream;
026import java.util.List;
027import java.util.concurrent.atomic.AtomicReference;
028import javax.net.SocketFactory;
029import javax.net.ssl.KeyManager;
030import javax.net.ssl.SSLContext;
031import javax.net.ssl.TrustManager;
032
033import com.unboundid.ldap.sdk.BindRequest;
034import com.unboundid.ldap.sdk.ExtendedResult;
035import com.unboundid.ldap.sdk.LDAPConnection;
036import com.unboundid.ldap.sdk.LDAPConnectionOptions;
037import com.unboundid.ldap.sdk.LDAPConnectionPool;
038import com.unboundid.ldap.sdk.LDAPException;
039import com.unboundid.ldap.sdk.PostConnectProcessor;
040import com.unboundid.ldap.sdk.ResultCode;
041import com.unboundid.ldap.sdk.RoundRobinServerSet;
042import com.unboundid.ldap.sdk.ServerSet;
043import com.unboundid.ldap.sdk.SimpleBindRequest;
044import com.unboundid.ldap.sdk.SingleServerSet;
045import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor;
046import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
047import com.unboundid.util.args.ArgumentException;
048import com.unboundid.util.args.ArgumentParser;
049import com.unboundid.util.args.BooleanArgument;
050import com.unboundid.util.args.DNArgument;
051import com.unboundid.util.args.FileArgument;
052import com.unboundid.util.args.IntegerArgument;
053import com.unboundid.util.args.StringArgument;
054import com.unboundid.util.ssl.KeyStoreKeyManager;
055import com.unboundid.util.ssl.PromptTrustManager;
056import com.unboundid.util.ssl.SSLUtil;
057import com.unboundid.util.ssl.TrustAllTrustManager;
058import com.unboundid.util.ssl.TrustStoreTrustManager;
059
060import static com.unboundid.util.Debug.*;
061import static com.unboundid.util.StaticUtils.*;
062import 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)
187public 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}