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 */
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.Control;
035import com.unboundid.ldap.sdk.ExtendedResult;
036import com.unboundid.ldap.sdk.LDAPConnection;
037import com.unboundid.ldap.sdk.LDAPConnectionOptions;
038import com.unboundid.ldap.sdk.LDAPConnectionPool;
039import com.unboundid.ldap.sdk.LDAPException;
040import com.unboundid.ldap.sdk.PostConnectProcessor;
041import com.unboundid.ldap.sdk.ResultCode;
042import com.unboundid.ldap.sdk.RoundRobinServerSet;
043import com.unboundid.ldap.sdk.ServerSet;
044import com.unboundid.ldap.sdk.SimpleBindRequest;
045import com.unboundid.ldap.sdk.SingleServerSet;
046import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor;
047import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
048import com.unboundid.util.args.ArgumentException;
049import com.unboundid.util.args.ArgumentParser;
050import com.unboundid.util.args.BooleanArgument;
051import com.unboundid.util.args.DNArgument;
052import com.unboundid.util.args.FileArgument;
053import com.unboundid.util.args.IntegerArgument;
054import com.unboundid.util.args.StringArgument;
055import com.unboundid.util.ssl.KeyStoreKeyManager;
056import com.unboundid.util.ssl.PromptTrustManager;
057import com.unboundid.util.ssl.SSLUtil;
058import com.unboundid.util.ssl.TrustAllTrustManager;
059import com.unboundid.util.ssl.TrustStoreTrustManager;
060
061import static com.unboundid.util.Debug.*;
062import static com.unboundid.util.StaticUtils.*;
063import 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)
188public 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}