001/*
002 * Copyright 2008-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2018 Ping Identity Corporation
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.ArrayList;
027import java.util.Collections;
028import java.util.LinkedHashSet;
029import java.util.List;
030import java.util.Set;
031import java.util.concurrent.atomic.AtomicReference;
032import javax.net.SocketFactory;
033import javax.net.ssl.KeyManager;
034import javax.net.ssl.SSLSocketFactory;
035import javax.net.ssl.TrustManager;
036
037import com.unboundid.ldap.sdk.AggregatePostConnectProcessor;
038import com.unboundid.ldap.sdk.BindRequest;
039import com.unboundid.ldap.sdk.Control;
040import com.unboundid.ldap.sdk.EXTERNALBindRequest;
041import com.unboundid.ldap.sdk.ExtendedResult;
042import com.unboundid.ldap.sdk.LDAPConnection;
043import com.unboundid.ldap.sdk.LDAPConnectionOptions;
044import com.unboundid.ldap.sdk.LDAPConnectionPool;
045import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck;
046import com.unboundid.ldap.sdk.LDAPException;
047import com.unboundid.ldap.sdk.PostConnectProcessor;
048import com.unboundid.ldap.sdk.ResultCode;
049import com.unboundid.ldap.sdk.RoundRobinServerSet;
050import com.unboundid.ldap.sdk.ServerSet;
051import com.unboundid.ldap.sdk.SimpleBindRequest;
052import com.unboundid.ldap.sdk.SingleServerSet;
053import com.unboundid.ldap.sdk.StartTLSPostConnectProcessor;
054import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
055import com.unboundid.util.args.Argument;
056import com.unboundid.util.args.ArgumentException;
057import com.unboundid.util.args.ArgumentParser;
058import com.unboundid.util.args.BooleanArgument;
059import com.unboundid.util.args.DNArgument;
060import com.unboundid.util.args.FileArgument;
061import com.unboundid.util.args.IntegerArgument;
062import com.unboundid.util.args.StringArgument;
063import com.unboundid.util.ssl.AggregateTrustManager;
064import com.unboundid.util.ssl.JVMDefaultTrustManager;
065import com.unboundid.util.ssl.KeyStoreKeyManager;
066import com.unboundid.util.ssl.PromptTrustManager;
067import com.unboundid.util.ssl.SSLUtil;
068import com.unboundid.util.ssl.TrustAllTrustManager;
069import com.unboundid.util.ssl.TrustStoreTrustManager;
070
071import static com.unboundid.util.Debug.*;
072import static com.unboundid.util.StaticUtils.*;
073import static com.unboundid.util.UtilityMessages.*;
074
075
076
077/**
078 * This class provides a basis for developing command-line tools that
079 * communicate with an LDAP directory server.  It provides a common set of
080 * options for connecting and authenticating to a directory server, and then
081 * provides a mechanism for obtaining connections and connection pools to use
082 * when communicating with that server.
083 * <BR><BR>
084 * The arguments that this class supports include:
085 * <UL>
086 *   <LI>"-h {address}" or "--hostname {address}" -- Specifies the address of
087 *       the directory server.  If this isn't specified, then a default of
088 *       "localhost" will be used.</LI>
089 *   <LI>"-p {port}" or "--port {port}" -- Specifies the port number of the
090 *       directory server.  If this isn't specified, then a default port of 389
091 *       will be used.</LI>
092 *   <LI>"-D {bindDN}" or "--bindDN {bindDN}" -- Specifies the DN to use to bind
093 *       to the directory server using simple authentication.  If this isn't
094 *       specified, then simple authentication will not be performed.</LI>
095 *   <LI>"-w {password}" or "--bindPassword {password}" -- Specifies the
096 *       password to use when binding with simple authentication or a
097 *       password-based SASL mechanism.</LI>
098 *   <LI>"-j {path}" or "--bindPasswordFile {path}" -- Specifies the path to the
099 *       file containing the password to use when binding with simple
100 *       authentication or a password-based SASL mechanism.</LI>
101 *   <LI>"--promptForBindPassword" -- Indicates that the tool should
102 *       interactively prompt the user for the bind password.</LI>
103 *   <LI>"-Z" or "--useSSL" -- Indicates that the communication with the server
104 *       should be secured using SSL.</LI>
105 *   <LI>"-q" or "--useStartTLS" -- Indicates that the communication with the
106 *       server should be secured using StartTLS.</LI>
107 *   <LI>"-X" or "--trustAll" -- Indicates that the client should trust any
108 *       certificate that the server presents to it.</LI>
109 *   <LI>"-K {path}" or "--keyStorePath {path}" -- Specifies the path to the
110 *       key store to use to obtain client certificates.</LI>
111 *   <LI>"-W {password}" or "--keyStorePassword {password}" -- Specifies the
112 *       password to use to access the contents of the key store.</LI>
113 *   <LI>"-u {path}" or "--keyStorePasswordFile {path}" -- Specifies the path to
114 *       the file containing the password to use to access the contents of the
115 *       key store.</LI>
116 *   <LI>"--promptForKeyStorePassword" -- Indicates that the tool should
117 *       interactively prompt the user for the key store password.</LI>
118 *   <LI>"--keyStoreFormat {format}" -- Specifies the format to use for the key
119 *       store file.</LI>
120 *   <LI>"-P {path}" or "--trustStorePath {path}" -- Specifies the path to the
121 *       trust store to use when determining whether to trust server
122 *       certificates.</LI>
123 *   <LI>"-T {password}" or "--trustStorePassword {password}" -- Specifies the
124 *       password to use to access the contents of the trust store.</LI>
125 *   <LI>"-U {path}" or "--trustStorePasswordFile {path}" -- Specifies the path
126 *       to the file containing the password to use to access the contents of
127 *       the trust store.</LI>
128 *   <LI>"--promptForTrustStorePassword" -- Indicates that the tool should
129 *       interactively prompt the user for the trust store password.</LI>
130 *   <LI>"--trustStoreFormat {format}" -- Specifies the format to use for the
131 *       trust store file.</LI>
132 *   <LI>"-N {nickname}" or "--certNickname {nickname}" -- Specifies the
133 *       nickname of the client certificate to use when performing SSL client
134 *       authentication.</LI>
135 *   <LI>"-o {name=value}" or "--saslOption {name=value}" -- Specifies a SASL
136 *       option to use when performing SASL authentication.</LI>
137 * </UL>
138 * If SASL authentication is to be used, then a "mech" SASL option must be
139 * provided to specify the name of the SASL mechanism to use (e.g.,
140 * "--saslOption mech=EXTERNAL" indicates that the EXTERNAL mechanism should be
141 * used).  Depending on the SASL mechanism, additional SASL options may be
142 * required or optional.  They include:
143 * <UL>
144 *   <LI>
145 *     mech=ANONYMOUS
146 *     <UL>
147 *       <LI>Required SASL options:  </LI>
148 *       <LI>Optional SASL options:  trace</LI>
149 *     </UL>
150 *   </LI>
151 *   <LI>
152 *     mech=CRAM-MD5
153 *     <UL>
154 *       <LI>Required SASL options:  authID</LI>
155 *       <LI>Optional SASL options:  </LI>
156 *     </UL>
157 *   </LI>
158 *   <LI>
159 *     mech=DIGEST-MD5
160 *     <UL>
161 *       <LI>Required SASL options:  authID</LI>
162 *       <LI>Optional SASL options:  authzID, realm</LI>
163 *     </UL>
164 *   </LI>
165 *   <LI>
166 *     mech=EXTERNAL
167 *     <UL>
168 *       <LI>Required SASL options:  </LI>
169 *       <LI>Optional SASL options:  </LI>
170 *     </UL>
171 *   </LI>
172 *   <LI>
173 *     mech=GSSAPI
174 *     <UL>
175 *       <LI>Required SASL options:  authID</LI>
176 *       <LI>Optional SASL options:  authzID, configFile, debug, protocol,
177 *                realm, kdcAddress, useTicketCache, requireCache,
178 *                renewTGT, ticketCachePath</LI>
179 *     </UL>
180 *   </LI>
181 *   <LI>
182 *     mech=PLAIN
183 *     <UL>
184 *       <LI>Required SASL options:  authID</LI>
185 *       <LI>Optional SASL options:  authzID</LI>
186 *     </UL>
187 *   </LI>
188 * </UL>
189 * <BR><BR>
190 * Note that in general, methods in this class are not threadsafe.  However, the
191 * {@link #getConnection()} and {@link #getConnectionPool(int,int)} methods may
192 * be invoked concurrently by multiple threads accessing the same instance only
193 * while that instance is in the process of invoking the
194 * {@link #doToolProcessing()} method.
195 */
196@Extensible()
197@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
198public abstract class LDAPCommandLineTool
199       extends CommandLineTool
200{
201  // Arguments used to communicate with an LDAP directory server.
202  private BooleanArgument helpSASL                    = null;
203  private BooleanArgument promptForBindPassword       = null;
204  private BooleanArgument promptForKeyStorePassword   = null;
205  private BooleanArgument promptForTrustStorePassword = null;
206  private BooleanArgument trustAll                    = null;
207  private BooleanArgument useSASLExternal             = null;
208  private BooleanArgument useSSL                      = null;
209  private BooleanArgument useStartTLS                 = null;
210  private DNArgument      bindDN                      = null;
211  private FileArgument    bindPasswordFile            = null;
212  private FileArgument    keyStorePasswordFile        = null;
213  private FileArgument    trustStorePasswordFile      = null;
214  private IntegerArgument port                        = null;
215  private StringArgument  bindPassword                = null;
216  private StringArgument  certificateNickname         = null;
217  private StringArgument  host                        = null;
218  private StringArgument  keyStoreFormat              = null;
219  private StringArgument  keyStorePath                = null;
220  private StringArgument  keyStorePassword            = null;
221  private StringArgument  saslOption                  = null;
222  private StringArgument  trustStoreFormat            = null;
223  private StringArgument  trustStorePath              = null;
224  private StringArgument  trustStorePassword          = null;
225
226  // Variables used when creating and authenticating connections.
227  private BindRequest      bindRequest           = null;
228  private ServerSet        serverSet             = null;
229  private SSLSocketFactory startTLSSocketFactory = null;
230
231  // An atomic reference to an aggregate trust manager that will check a
232  // JVM-default set of trusted issuers, and then its own cache, before
233  // prompting the user about whether to trust the presented certificate chain.
234  // Re-using this trust manager will allow the tool to benefit from a common
235  // cache if multiple connections are needed.
236  private final AtomicReference<AggregateTrustManager> promptTrustManager;
237
238
239
240  /**
241   * Creates a new instance of this LDAP-enabled command-line tool with the
242   * provided information.
243   *
244   * @param  outStream  The output stream to use for standard output.  It may be
245   *                    {@code System.out} for the JVM's default standard output
246   *                    stream, {@code null} if no output should be generated,
247   *                    or a custom output stream if the output should be sent
248   *                    to an alternate location.
249   * @param  errStream  The output stream to use for standard error.  It may be
250   *                    {@code System.err} for the JVM's default standard error
251   *                    stream, {@code null} if no output should be generated,
252   *                    or a custom output stream if the output should be sent
253   *                    to an alternate location.
254   */
255  public LDAPCommandLineTool(final OutputStream outStream,
256                             final OutputStream errStream)
257  {
258    super(outStream, errStream);
259
260    promptTrustManager = new AtomicReference<>();
261  }
262
263
264
265  /**
266   * Retrieves a set containing the long identifiers used for LDAP-related
267   * arguments injected by this class.
268   *
269   * @param  tool  The tool to use to help make the determination.
270   *
271   * @return  A set containing the long identifiers used for LDAP-related
272   *          arguments injected by this class.
273   */
274  static Set<String> getLongLDAPArgumentIdentifiers(
275                          final LDAPCommandLineTool tool)
276  {
277    final LinkedHashSet<String> ids = new LinkedHashSet<String>(21);
278
279    ids.add("hostname");
280    ids.add("port");
281
282    if (tool.supportsAuthentication())
283    {
284      ids.add("bindDN");
285      ids.add("bindPassword");
286      ids.add("bindPasswordFile");
287      ids.add("promptForBindPassword");
288    }
289
290    ids.add("useSSL");
291    ids.add("useStartTLS");
292    ids.add("trustAll");
293    ids.add("keyStorePath");
294    ids.add("keyStorePassword");
295    ids.add("keyStorePasswordFile");
296    ids.add("promptForKeyStorePassword");
297    ids.add("keyStoreFormat");
298    ids.add("trustStorePath");
299    ids.add("trustStorePassword");
300    ids.add("trustStorePasswordFile");
301    ids.add("promptForTrustStorePassword");
302    ids.add("trustStoreFormat");
303    ids.add("certNickname");
304
305    if (tool.supportsAuthentication())
306    {
307      ids.add("saslOption");
308      ids.add("useSASLExternal");
309      ids.add("helpSASL");
310    }
311
312    return Collections.unmodifiableSet(ids);
313  }
314
315
316
317  /**
318   * Retrieves a set containing any short identifiers that should be suppressed
319   * in the set of generic tool arguments so that they can be used by a
320   * tool-specific argument instead.
321   *
322   * @return  A set containing any short identifiers that should be suppressed
323   *          in the set of generic tool arguments so that they can be used by a
324   *          tool-specific argument instead.  It may be empty but must not be
325   *          {@code null}.
326   */
327  protected Set<Character> getSuppressedShortIdentifiers()
328  {
329    return Collections.emptySet();
330  }
331
332
333
334  /**
335   * Retrieves the provided character if it is not included in the set of
336   * suppressed short identifiers.
337   *
338   * @param  id  The character to return if it is not in the set of suppressed
339   *             short identifiers.  It must not be {@code null}.
340   *
341   * @return  The provided character, or {@code null} if it is in the set of
342   *          suppressed short identifiers.
343   */
344  private Character getShortIdentifierIfNotSuppressed(final Character id)
345  {
346    if (getSuppressedShortIdentifiers().contains(id))
347    {
348      return null;
349    }
350    else
351    {
352      return id;
353    }
354  }
355
356
357
358  /**
359   * {@inheritDoc}
360   */
361  @Override()
362  public final void addToolArguments(final ArgumentParser parser)
363         throws ArgumentException
364  {
365    final String argumentGroup;
366    final boolean supportsAuthentication = supportsAuthentication();
367    if (supportsAuthentication)
368    {
369      argumentGroup = INFO_LDAP_TOOL_ARG_GROUP_CONNECT_AND_AUTH.get();
370    }
371    else
372    {
373      argumentGroup = INFO_LDAP_TOOL_ARG_GROUP_CONNECT.get();
374    }
375
376
377    host = new StringArgument(getShortIdentifierIfNotSuppressed('h'),
378         "hostname", true, (supportsMultipleServers() ? 0 : 1),
379         INFO_LDAP_TOOL_PLACEHOLDER_HOST.get(),
380         INFO_LDAP_TOOL_DESCRIPTION_HOST.get(), "localhost");
381    host.setArgumentGroupName(argumentGroup);
382    parser.addArgument(host);
383
384    port = new IntegerArgument(getShortIdentifierIfNotSuppressed('p'), "port",
385         true, (supportsMultipleServers() ? 0 : 1),
386         INFO_LDAP_TOOL_PLACEHOLDER_PORT.get(),
387         INFO_LDAP_TOOL_DESCRIPTION_PORT.get(), 1, 65535, 389);
388    port.setArgumentGroupName(argumentGroup);
389    parser.addArgument(port);
390
391    if (supportsAuthentication)
392    {
393      bindDN = new DNArgument(getShortIdentifierIfNotSuppressed('D'), "bindDN",
394           false, 1, INFO_LDAP_TOOL_PLACEHOLDER_DN.get(),
395           INFO_LDAP_TOOL_DESCRIPTION_BIND_DN.get());
396      bindDN.setArgumentGroupName(argumentGroup);
397      if (includeAlternateLongIdentifiers())
398      {
399        bindDN.addLongIdentifier("bind-dn", true);
400      }
401      parser.addArgument(bindDN);
402
403      bindPassword = new StringArgument(getShortIdentifierIfNotSuppressed('w'),
404           "bindPassword", false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
405           INFO_LDAP_TOOL_DESCRIPTION_BIND_PW.get());
406      bindPassword.setSensitive(true);
407      bindPassword.setArgumentGroupName(argumentGroup);
408      if (includeAlternateLongIdentifiers())
409      {
410        bindPassword.addLongIdentifier("bind-password", true);
411      }
412      parser.addArgument(bindPassword);
413
414      bindPasswordFile = new FileArgument(
415           getShortIdentifierIfNotSuppressed('j'), "bindPasswordFile", false, 1,
416           INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
417           INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_FILE.get(), true, true, true,
418           false);
419      bindPasswordFile.setArgumentGroupName(argumentGroup);
420      if (includeAlternateLongIdentifiers())
421      {
422        bindPasswordFile.addLongIdentifier("bind-password-file", true);
423      }
424      parser.addArgument(bindPasswordFile);
425
426      promptForBindPassword = new BooleanArgument(null, "promptForBindPassword",
427           1, INFO_LDAP_TOOL_DESCRIPTION_BIND_PW_PROMPT.get());
428      promptForBindPassword.setArgumentGroupName(argumentGroup);
429      if (includeAlternateLongIdentifiers())
430      {
431        promptForBindPassword.addLongIdentifier("prompt-for-bind-password",
432             true);
433      }
434      parser.addArgument(promptForBindPassword);
435    }
436
437    useSSL = new BooleanArgument(getShortIdentifierIfNotSuppressed('Z'),
438         "useSSL", 1, INFO_LDAP_TOOL_DESCRIPTION_USE_SSL.get());
439    useSSL.setArgumentGroupName(argumentGroup);
440    if (includeAlternateLongIdentifiers())
441    {
442      useSSL.addLongIdentifier("use-ssl", true);
443    }
444    parser.addArgument(useSSL);
445
446    useStartTLS = new BooleanArgument(getShortIdentifierIfNotSuppressed('q'),
447         "useStartTLS", 1, INFO_LDAP_TOOL_DESCRIPTION_USE_START_TLS.get());
448    useStartTLS.setArgumentGroupName(argumentGroup);
449      if (includeAlternateLongIdentifiers())
450      {
451        useStartTLS.addLongIdentifier("use-starttls", true);
452        useStartTLS.addLongIdentifier("use-start-tls", true);
453      }
454    parser.addArgument(useStartTLS);
455
456    trustAll = new BooleanArgument(getShortIdentifierIfNotSuppressed('X'),
457         "trustAll", 1, INFO_LDAP_TOOL_DESCRIPTION_TRUST_ALL.get());
458    trustAll.setArgumentGroupName(argumentGroup);
459    if (includeAlternateLongIdentifiers())
460    {
461      trustAll.addLongIdentifier("trustAllCertificates", true);
462      trustAll.addLongIdentifier("trust-all", true);
463      trustAll.addLongIdentifier("trust-all-certificates", true);
464    }
465    parser.addArgument(trustAll);
466
467    keyStorePath = new StringArgument(getShortIdentifierIfNotSuppressed('K'),
468         "keyStorePath", false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
469         INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PATH.get());
470    keyStorePath.setArgumentGroupName(argumentGroup);
471    if (includeAlternateLongIdentifiers())
472    {
473      keyStorePath.addLongIdentifier("key-store-path", true);
474    }
475    parser.addArgument(keyStorePath);
476
477    keyStorePassword = new StringArgument(
478         getShortIdentifierIfNotSuppressed('W'), "keyStorePassword", false, 1,
479         INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
480         INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get());
481    keyStorePassword.setSensitive(true);
482    keyStorePassword.setArgumentGroupName(argumentGroup);
483    if (includeAlternateLongIdentifiers())
484    {
485      keyStorePassword.addLongIdentifier("keyStorePIN", true);
486      keyStorePassword.addLongIdentifier("key-store-password", true);
487      keyStorePassword.addLongIdentifier("key-store-pin", true);
488    }
489    parser.addArgument(keyStorePassword);
490
491    keyStorePasswordFile = new FileArgument(
492         getShortIdentifierIfNotSuppressed('u'), "keyStorePasswordFile", false,
493         1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
494         INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get());
495    keyStorePasswordFile.setArgumentGroupName(argumentGroup);
496    if (includeAlternateLongIdentifiers())
497    {
498      keyStorePasswordFile.addLongIdentifier("keyStorePINFile", true);
499      keyStorePasswordFile.addLongIdentifier("key-store-password-file", true);
500      keyStorePasswordFile.addLongIdentifier("key-store-pin-file", true);
501    }
502    parser.addArgument(keyStorePasswordFile);
503
504    promptForKeyStorePassword = new BooleanArgument(null,
505         "promptForKeyStorePassword", 1,
506         INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_PROMPT.get());
507    promptForKeyStorePassword.setArgumentGroupName(argumentGroup);
508    if (includeAlternateLongIdentifiers())
509    {
510      promptForKeyStorePassword.addLongIdentifier("promptForKeyStorePIN", true);
511      promptForKeyStorePassword.addLongIdentifier(
512           "prompt-for-key-store-password", true);
513      promptForKeyStorePassword.addLongIdentifier("prompt-for-key-store-pin",
514           true);
515    }
516    parser.addArgument(promptForKeyStorePassword);
517
518    keyStoreFormat = new StringArgument(null, "keyStoreFormat", false, 1,
519         INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
520         INFO_LDAP_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get());
521    keyStoreFormat.setArgumentGroupName(argumentGroup);
522    if (includeAlternateLongIdentifiers())
523    {
524      keyStoreFormat.addLongIdentifier("keyStoreType", true);
525      keyStoreFormat.addLongIdentifier("key-store-format", true);
526      keyStoreFormat.addLongIdentifier("key-store-type", true);
527    }
528    parser.addArgument(keyStoreFormat);
529
530    trustStorePath = new StringArgument(getShortIdentifierIfNotSuppressed('P'),
531         "trustStorePath", false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
532         INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PATH.get());
533    trustStorePath.setArgumentGroupName(argumentGroup);
534    if (includeAlternateLongIdentifiers())
535    {
536      trustStorePath.addLongIdentifier("trust-store-path", true);
537    }
538    parser.addArgument(trustStorePath);
539
540    trustStorePassword = new StringArgument(
541         getShortIdentifierIfNotSuppressed('T'), "trustStorePassword", false, 1,
542         INFO_LDAP_TOOL_PLACEHOLDER_PASSWORD.get(),
543         INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get());
544    trustStorePassword.setSensitive(true);
545    trustStorePassword.setArgumentGroupName(argumentGroup);
546    if (includeAlternateLongIdentifiers())
547    {
548      trustStorePassword.addLongIdentifier("trustStorePIN", true);
549      trustStorePassword.addLongIdentifier("trust-store-password", true);
550      trustStorePassword.addLongIdentifier("trust-store-pin", true);
551    }
552    parser.addArgument(trustStorePassword);
553
554    trustStorePasswordFile = new FileArgument(
555         getShortIdentifierIfNotSuppressed('U'), "trustStorePasswordFile",
556         false, 1, INFO_LDAP_TOOL_PLACEHOLDER_PATH.get(),
557         INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get());
558    trustStorePasswordFile.setArgumentGroupName(argumentGroup);
559    if (includeAlternateLongIdentifiers())
560    {
561      trustStorePasswordFile.addLongIdentifier("trustStorePINFile", true);
562      trustStorePasswordFile.addLongIdentifier("trust-store-password-file",
563           true);
564      trustStorePasswordFile.addLongIdentifier("trust-store-pin-file", true);
565    }
566    parser.addArgument(trustStorePasswordFile);
567
568    promptForTrustStorePassword = new BooleanArgument(null,
569         "promptForTrustStorePassword", 1,
570         INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_PROMPT.get());
571    promptForTrustStorePassword.setArgumentGroupName(argumentGroup);
572    if (includeAlternateLongIdentifiers())
573    {
574      promptForTrustStorePassword.addLongIdentifier("promptForTrustStorePIN",
575           true);
576      promptForTrustStorePassword.addLongIdentifier(
577           "prompt-for-trust-store-password", true);
578      promptForTrustStorePassword.addLongIdentifier(
579           "prompt-for-trust-store-pin", true);
580    }
581    parser.addArgument(promptForTrustStorePassword);
582
583    trustStoreFormat = new StringArgument(null, "trustStoreFormat", false, 1,
584         INFO_LDAP_TOOL_PLACEHOLDER_FORMAT.get(),
585         INFO_LDAP_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get());
586    trustStoreFormat.setArgumentGroupName(argumentGroup);
587    if (includeAlternateLongIdentifiers())
588    {
589      trustStoreFormat.addLongIdentifier("trustStoreType", true);
590      trustStoreFormat.addLongIdentifier("trust-store-format", true);
591      trustStoreFormat.addLongIdentifier("trust-store-type", true);
592    }
593    parser.addArgument(trustStoreFormat);
594
595    certificateNickname = new StringArgument(
596         getShortIdentifierIfNotSuppressed('N'), "certNickname", false, 1,
597         INFO_LDAP_TOOL_PLACEHOLDER_CERT_NICKNAME.get(),
598         INFO_LDAP_TOOL_DESCRIPTION_CERT_NICKNAME.get());
599    certificateNickname.setArgumentGroupName(argumentGroup);
600    if (includeAlternateLongIdentifiers())
601    {
602      certificateNickname.addLongIdentifier("certificateNickname", true);
603      certificateNickname.addLongIdentifier("cert-nickname", true);
604      certificateNickname.addLongIdentifier("certificate-nickname", true);
605    }
606    parser.addArgument(certificateNickname);
607
608    if (supportsAuthentication)
609    {
610      saslOption = new StringArgument(getShortIdentifierIfNotSuppressed('o'),
611           "saslOption", false, 0, INFO_LDAP_TOOL_PLACEHOLDER_SASL_OPTION.get(),
612           INFO_LDAP_TOOL_DESCRIPTION_SASL_OPTION.get());
613      saslOption.setArgumentGroupName(argumentGroup);
614      if (includeAlternateLongIdentifiers())
615      {
616        saslOption.addLongIdentifier("sasl-option", true);
617      }
618      parser.addArgument(saslOption);
619
620      useSASLExternal = new BooleanArgument(null, "useSASLExternal", 1,
621           INFO_LDAP_TOOL_DESCRIPTION_USE_SASL_EXTERNAL.get());
622      useSASLExternal.setArgumentGroupName(argumentGroup);
623      if (includeAlternateLongIdentifiers())
624      {
625        useSASLExternal.addLongIdentifier("use-sasl-external", true);
626      }
627      parser.addArgument(useSASLExternal);
628
629      if (supportsSASLHelp())
630      {
631        helpSASL = new BooleanArgument(null, "helpSASL",
632             INFO_LDAP_TOOL_DESCRIPTION_HELP_SASL.get());
633        helpSASL.setArgumentGroupName(argumentGroup);
634        if (includeAlternateLongIdentifiers())
635        {
636          helpSASL.addLongIdentifier("help-sasl", true);
637        }
638        helpSASL.setUsageArgument(true);
639        parser.addArgument(helpSASL);
640        setHelpSASLArgument(helpSASL);
641      }
642    }
643
644
645    // Both useSSL and useStartTLS cannot be used together.
646    parser.addExclusiveArgumentSet(useSSL, useStartTLS);
647
648    // Only one option may be used for specifying the key store password.
649    parser.addExclusiveArgumentSet(keyStorePassword, keyStorePasswordFile,
650         promptForKeyStorePassword);
651
652    // Only one option may be used for specifying the trust store password.
653    parser.addExclusiveArgumentSet(trustStorePassword, trustStorePasswordFile,
654         promptForTrustStorePassword);
655
656    // It doesn't make sense to provide a trust store path if any server
657    // certificate should be trusted.
658    parser.addExclusiveArgumentSet(trustAll, trustStorePath);
659
660    // If a key store password is provided, then a key store path must have also
661    // been provided.
662    parser.addDependentArgumentSet(keyStorePassword, keyStorePath);
663    parser.addDependentArgumentSet(keyStorePasswordFile, keyStorePath);
664    parser.addDependentArgumentSet(promptForKeyStorePassword, keyStorePath);
665
666    // If a trust store password is provided, then a trust store path must have
667    // also been provided.
668    parser.addDependentArgumentSet(trustStorePassword, trustStorePath);
669    parser.addDependentArgumentSet(trustStorePasswordFile, trustStorePath);
670    parser.addDependentArgumentSet(promptForTrustStorePassword, trustStorePath);
671
672    // If a key or trust store path is provided, then the tool must either use
673    // SSL or StartTLS.
674    parser.addDependentArgumentSet(keyStorePath, useSSL, useStartTLS);
675    parser.addDependentArgumentSet(trustStorePath, useSSL, useStartTLS);
676
677    // If the tool should trust all server certificates, then the tool must
678    // either use SSL or StartTLS.
679    parser.addDependentArgumentSet(trustAll, useSSL, useStartTLS);
680
681    if (supportsAuthentication)
682    {
683      // If a bind DN was provided, then a bind password must have also been
684      // provided unless defaultToPromptForBindPassword returns true.
685      if (! defaultToPromptForBindPassword())
686      {
687        parser.addDependentArgumentSet(bindDN, bindPassword, bindPasswordFile,
688             promptForBindPassword);
689      }
690
691      // The bindDN, saslOption, and useSASLExternal arguments are all mutually
692      // exclusive.
693      parser.addExclusiveArgumentSet(bindDN, saslOption, useSASLExternal);
694
695      // Only one option may be used for specifying the bind password.
696      parser.addExclusiveArgumentSet(bindPassword, bindPasswordFile,
697           promptForBindPassword);
698
699      // If a bind password was provided, then the a bind DN or SASL option
700      // must have also been provided.
701      parser.addDependentArgumentSet(bindPassword, bindDN, saslOption);
702      parser.addDependentArgumentSet(bindPasswordFile, bindDN, saslOption);
703      parser.addDependentArgumentSet(promptForBindPassword, bindDN, saslOption);
704    }
705
706    addNonLDAPArguments(parser);
707  }
708
709
710
711  /**
712   * Adds the arguments needed by this command-line tool to the provided
713   * argument parser which are not related to connecting or authenticating to
714   * the directory server.
715   *
716   * @param  parser  The argument parser to which the arguments should be added.
717   *
718   * @throws  ArgumentException  If a problem occurs while adding the arguments.
719   */
720  public abstract void addNonLDAPArguments(ArgumentParser parser)
721         throws ArgumentException;
722
723
724
725  /**
726   * {@inheritDoc}
727   */
728  @Override()
729  public final void doExtendedArgumentValidation()
730         throws ArgumentException
731  {
732    // If more than one hostname or port number was provided, then make sure
733    // that the same number of values were provided for each.
734    if ((host.getValues().size() > 1) || (port.getValues().size() > 1))
735    {
736      if (host.getValues().size() != port.getValues().size())
737      {
738        throw new ArgumentException(
739             ERR_LDAP_TOOL_HOST_PORT_COUNT_MISMATCH.get(
740                  host.getLongIdentifier(), port.getLongIdentifier()));
741      }
742    }
743
744
745    doExtendedNonLDAPArgumentValidation();
746  }
747
748
749
750  /**
751   * Indicates whether this tool should provide the arguments that allow it to
752   * bind via simple or SASL authentication.
753   *
754   * @return  {@code true} if this tool should provide the arguments that allow
755   *          it to bind via simple or SASL authentication, or {@code false} if
756   *          not.
757   */
758  protected boolean supportsAuthentication()
759  {
760    return true;
761  }
762
763
764
765  /**
766   * Indicates whether this tool should default to interactively prompting for
767   * the bind password if a password is required but no argument was provided
768   * to indicate how to get the password.
769   *
770   * @return  {@code true} if this tool should default to interactively
771   *          prompting for the bind password, or {@code false} if not.
772   */
773  protected boolean defaultToPromptForBindPassword()
774  {
775    return false;
776  }
777
778
779
780  /**
781   * Indicates whether this tool should provide a "--help-sasl" argument that
782   * provides information about the supported SASL mechanisms and their
783   * associated properties.
784   *
785   * @return  {@code true} if this tool should provide a "--help-sasl" argument,
786   *          or {@code false} if not.
787   */
788  protected boolean supportsSASLHelp()
789  {
790    return true;
791  }
792
793
794
795  /**
796   * Indicates whether the LDAP-specific arguments should include alternate
797   * versions of all long identifiers that consist of multiple words so that
798   * they are available in both camelCase and dash-separated versions.
799   *
800   * @return  {@code true} if this tool should provide multiple versions of
801   *          long identifiers for LDAP-specific arguments, or {@code false} if
802   *          not.
803   */
804  protected boolean includeAlternateLongIdentifiers()
805  {
806    return false;
807  }
808
809
810
811  /**
812   * Retrieves a set of controls that should be included in any bind request
813   * generated by this tool.
814   *
815   * @return  A set of controls that should be included in any bind request
816   *          generated by this tool.  It may be {@code null} or empty if no
817   *          controls should be included in the bind request.
818   */
819  protected List<Control> getBindControls()
820  {
821    return null;
822  }
823
824
825
826  /**
827   * Indicates whether this tool supports creating connections to multiple
828   * servers.  If it is to support multiple servers, then the "--hostname" and
829   * "--port" arguments will be allowed to be provided multiple times, and
830   * will be required to be provided the same number of times.  The same type of
831   * communication security and bind credentials will be used for all servers.
832   *
833   * @return  {@code true} if this tool supports creating connections to
834   *          multiple servers, or {@code false} if not.
835   */
836  protected boolean supportsMultipleServers()
837  {
838    return false;
839  }
840
841
842
843  /**
844   * Performs any necessary processing that should be done to ensure that the
845   * provided set of command-line arguments were valid.  This method will be
846   * called after the basic argument parsing has been performed and after all
847   * LDAP-specific argument validation has been processed, and immediately
848   * before the {@link CommandLineTool#doToolProcessing} method is invoked.
849   *
850   * @throws  ArgumentException  If there was a problem with the command-line
851   *                             arguments provided to this program.
852   */
853  public void doExtendedNonLDAPArgumentValidation()
854         throws ArgumentException
855  {
856    // No processing will be performed by default.
857  }
858
859
860
861  /**
862   * Retrieves the connection options that should be used for connections that
863   * are created with this command line tool.  Subclasses may override this
864   * method to use a custom set of connection options.
865   *
866   * @return  The connection options that should be used for connections that
867   *          are created with this command line tool.
868   */
869  public LDAPConnectionOptions getConnectionOptions()
870  {
871    return new LDAPConnectionOptions();
872  }
873
874
875
876  /**
877   * Retrieves a connection that may be used to communicate with the target
878   * directory server.
879   * <BR><BR>
880   * Note that this method is threadsafe and may be invoked by multiple threads
881   * accessing the same instance only while that instance is in the process of
882   * invoking the {@link #doToolProcessing} method.
883   *
884   * @return  A connection that may be used to communicate with the target
885   *          directory server.
886   *
887   * @throws  LDAPException  If a problem occurs while creating the connection.
888   */
889  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
890  public final LDAPConnection getConnection()
891         throws LDAPException
892  {
893    final LDAPConnection connection = getUnauthenticatedConnection();
894
895    try
896    {
897      if (bindRequest != null)
898      {
899        connection.bind(bindRequest);
900      }
901    }
902    catch (final LDAPException le)
903    {
904      debugException(le);
905      connection.close();
906      throw le;
907    }
908
909    return connection;
910  }
911
912
913
914  /**
915   * Retrieves an unauthenticated connection that may be used to communicate
916   * with the target directory server.
917   * <BR><BR>
918   * Note that this method is threadsafe and may be invoked by multiple threads
919   * accessing the same instance only while that instance is in the process of
920   * invoking the {@link #doToolProcessing} method.
921   *
922   * @return  An unauthenticated connection that may be used to communicate with
923   *          the target directory server.
924   *
925   * @throws  LDAPException  If a problem occurs while creating the connection.
926   */
927  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
928  public final LDAPConnection getUnauthenticatedConnection()
929         throws LDAPException
930  {
931    if (serverSet == null)
932    {
933      serverSet   = createServerSet();
934      bindRequest = createBindRequest();
935    }
936
937    final LDAPConnection connection = serverSet.getConnection();
938
939    if (useStartTLS.isPresent())
940    {
941      try
942      {
943        final ExtendedResult extendedResult =
944             connection.processExtendedOperation(
945                  new StartTLSExtendedRequest(startTLSSocketFactory));
946        if (! extendedResult.getResultCode().equals(ResultCode.SUCCESS))
947        {
948          throw new LDAPException(extendedResult.getResultCode(),
949               ERR_LDAP_TOOL_START_TLS_FAILED.get(
950                    extendedResult.getDiagnosticMessage()));
951        }
952      }
953      catch (final LDAPException le)
954      {
955        debugException(le);
956        connection.close();
957        throw le;
958      }
959    }
960
961    return connection;
962  }
963
964
965
966  /**
967   * Retrieves a connection pool that may be used to communicate with the target
968   * directory server.
969   * <BR><BR>
970   * Note that this method is threadsafe and may be invoked by multiple threads
971   * accessing the same instance only while that instance is in the process of
972   * invoking the {@link #doToolProcessing} method.
973   *
974   * @param  initialConnections  The number of connections that should be
975   *                             initially established in the pool.
976   * @param  maxConnections      The maximum number of connections to maintain
977   *                             in the pool.
978   *
979   * @return  A connection that may be used to communicate with the target
980   *          directory server.
981   *
982   * @throws  LDAPException  If a problem occurs while creating the connection
983   *                         pool.
984   */
985  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
986  public final LDAPConnectionPool getConnectionPool(
987                                       final int initialConnections,
988                                       final int maxConnections)
989            throws LDAPException
990  {
991    return getConnectionPool(initialConnections, maxConnections, 1, null, null,
992         true, null);
993  }
994
995
996
997  /**
998   * Retrieves a connection pool that may be used to communicate with the target
999   * directory server.
1000   * <BR><BR>
1001   * Note that this method is threadsafe and may be invoked by multiple threads
1002   * accessing the same instance only while that instance is in the process of
1003   * invoking the {@link #doToolProcessing} method.
1004   *
1005   * @param  initialConnections       The number of connections that should be
1006   *                                  initially established in the pool.
1007   * @param  maxConnections           The maximum number of connections to
1008   *                                  maintain in the pool.
1009   * @param  initialConnectThreads    The number of concurrent threads to use to
1010   *                                  establish the initial set of connections.
1011   *                                  A value greater than one indicates that
1012   *                                  the attempt to establish connections
1013   *                                  should be parallelized.
1014   * @param  beforeStartTLSProcessor  An optional post-connect processor that
1015   *                                  should be used for the connection pool and
1016   *                                  should be invoked before any StartTLS
1017   *                                  post-connect processor that may be needed
1018   *                                  based on the selected arguments.  It may
1019   *                                  be {@code null} if no such post-connect
1020   *                                  processor is needed.
1021   * @param  afterStartTLSProcessor   An optional post-connect processor that
1022   *                                  should be used for the connection pool and
1023   *                                  should be invoked after any StartTLS
1024   *                                  post-connect processor that may be needed
1025   *                                  based on the selected arguments.  It may
1026   *                                  be {@code null} if no such post-connect
1027   *                                  processor is needed.
1028   * @param  throwOnConnectFailure    If an exception should be thrown if a
1029   *                                  problem is encountered while attempting to
1030   *                                  create the specified initial number of
1031   *                                  connections.  If {@code true}, then the
1032   *                                  attempt to create the pool will fail if
1033   *                                  any connection cannot be established.  If
1034   *                                  {@code false}, then the pool will be
1035   *                                  created but may have fewer than the
1036   *                                  initial number of connections (or possibly
1037   *                                  no connections).
1038   * @param  healthCheck              An optional health check that should be
1039   *                                  configured for the connection pool.  It
1040   *                                  may be {@code null} if the default health
1041   *                                  checking should be performed.
1042   *
1043   * @return  A connection that may be used to communicate with the target
1044   *          directory server.
1045   *
1046   * @throws  LDAPException  If a problem occurs while creating the connection
1047   *                         pool.
1048   */
1049  @ThreadSafety(level=ThreadSafetyLevel.METHOD_THREADSAFE)
1050  public final LDAPConnectionPool getConnectionPool(
1051                    final int initialConnections, final int maxConnections,
1052                    final int initialConnectThreads,
1053                    final PostConnectProcessor beforeStartTLSProcessor,
1054                    final PostConnectProcessor afterStartTLSProcessor,
1055                    final boolean throwOnConnectFailure,
1056                    final LDAPConnectionPoolHealthCheck healthCheck)
1057            throws LDAPException
1058  {
1059    // Create the server set and bind request, if necessary.
1060    if (serverSet == null)
1061    {
1062      serverSet   = createServerSet();
1063      bindRequest = createBindRequest();
1064    }
1065
1066
1067    // Prepare the post-connect processor for the pool.
1068    final ArrayList<PostConnectProcessor> pcpList =
1069         new ArrayList<PostConnectProcessor>(3);
1070    if (beforeStartTLSProcessor != null)
1071    {
1072      pcpList.add(beforeStartTLSProcessor);
1073    }
1074
1075    if (useStartTLS.isPresent())
1076    {
1077      pcpList.add(new StartTLSPostConnectProcessor(startTLSSocketFactory));
1078    }
1079
1080    if (afterStartTLSProcessor != null)
1081    {
1082      pcpList.add(afterStartTLSProcessor);
1083    }
1084
1085    final PostConnectProcessor postConnectProcessor;
1086    switch (pcpList.size())
1087    {
1088      case 0:
1089        postConnectProcessor = null;
1090        break;
1091      case 1:
1092        postConnectProcessor = pcpList.get(0);
1093        break;
1094      default:
1095        postConnectProcessor = new AggregatePostConnectProcessor(pcpList);
1096        break;
1097    }
1098
1099    return new LDAPConnectionPool(serverSet, bindRequest, initialConnections,
1100         maxConnections, initialConnectThreads, postConnectProcessor,
1101         throwOnConnectFailure, healthCheck);
1102  }
1103
1104
1105
1106  /**
1107   * Creates the server set to use when creating connections or connection
1108   * pools.
1109   *
1110   * @return  The server set to use when creating connections or connection
1111   *          pools.
1112   *
1113   * @throws  LDAPException  If a problem occurs while creating the server set.
1114   */
1115  public ServerSet createServerSet()
1116         throws LDAPException
1117  {
1118    final SSLUtil sslUtil = createSSLUtil();
1119
1120    SocketFactory socketFactory = null;
1121    if (useSSL.isPresent())
1122    {
1123      try
1124      {
1125        socketFactory = sslUtil.createSSLSocketFactory();
1126      }
1127      catch (final Exception e)
1128      {
1129        debugException(e);
1130        throw new LDAPException(ResultCode.LOCAL_ERROR,
1131             ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
1132                  getExceptionMessage(e)), e);
1133      }
1134    }
1135    else if (useStartTLS.isPresent())
1136    {
1137      try
1138      {
1139        startTLSSocketFactory = sslUtil.createSSLSocketFactory();
1140      }
1141      catch (final Exception e)
1142      {
1143        debugException(e);
1144        throw new LDAPException(ResultCode.LOCAL_ERROR,
1145             ERR_LDAP_TOOL_CANNOT_CREATE_SSL_SOCKET_FACTORY.get(
1146                  getExceptionMessage(e)), e);
1147      }
1148    }
1149
1150    if (host.getValues().size() == 1)
1151    {
1152      return new SingleServerSet(host.getValue(), port.getValue(),
1153                                 socketFactory, getConnectionOptions());
1154    }
1155    else
1156    {
1157      final List<String>  hostList = host.getValues();
1158      final List<Integer> portList = port.getValues();
1159
1160      final String[] hosts = new String[hostList.size()];
1161      final int[]    ports = new int[hosts.length];
1162
1163      for (int i=0; i < hosts.length; i++)
1164      {
1165        hosts[i] = hostList.get(i);
1166        ports[i] = portList.get(i);
1167      }
1168
1169      return new RoundRobinServerSet(hosts, ports, socketFactory,
1170                                     getConnectionOptions());
1171    }
1172  }
1173
1174
1175
1176  /**
1177   * Creates the SSLUtil instance to use for secure communication.
1178   *
1179   * @return  The SSLUtil instance to use for secure communication, or
1180   *          {@code null} if secure communication is not needed.
1181   *
1182   * @throws  LDAPException  If a problem occurs while creating the SSLUtil
1183   *                         instance.
1184   */
1185  public SSLUtil createSSLUtil()
1186         throws LDAPException
1187  {
1188    return createSSLUtil(false);
1189  }
1190
1191
1192
1193  /**
1194   * Creates the SSLUtil instance to use for secure communication.
1195   *
1196   * @param  force  Indicates whether to create the SSLUtil object even if
1197   *                neither the "--useSSL" nor the "--useStartTLS" argument was
1198   *                provided.  The key store and/or trust store paths must still
1199   *                have been provided.  This may be useful for tools that
1200   *                accept SSL-based communication but do not themselves intend
1201   *                to perform SSL-based communication as an LDAP client.
1202   *
1203   * @return  The SSLUtil instance to use for secure communication, or
1204   *          {@code null} if secure communication is not needed.
1205   *
1206   * @throws  LDAPException  If a problem occurs while creating the SSLUtil
1207   *                         instance.
1208   */
1209  public SSLUtil createSSLUtil(final boolean force)
1210         throws LDAPException
1211  {
1212    if (force || useSSL.isPresent() || useStartTLS.isPresent())
1213    {
1214      KeyManager keyManager = null;
1215      if (keyStorePath.isPresent())
1216      {
1217        char[] pw = null;
1218        if (keyStorePassword.isPresent())
1219        {
1220          pw = keyStorePassword.getValue().toCharArray();
1221        }
1222        else if (keyStorePasswordFile.isPresent())
1223        {
1224          try
1225          {
1226            pw = keyStorePasswordFile.getNonBlankFileLines().get(0).
1227                      toCharArray();
1228          }
1229          catch (final Exception e)
1230          {
1231            debugException(e);
1232            throw new LDAPException(ResultCode.LOCAL_ERROR,
1233                 ERR_LDAP_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get(
1234                      getExceptionMessage(e)), e);
1235          }
1236        }
1237        else if (promptForKeyStorePassword.isPresent())
1238        {
1239          getOut().print(INFO_LDAP_TOOL_ENTER_KEY_STORE_PASSWORD.get());
1240          pw = StaticUtils.toUTF8String(
1241               PasswordReader.readPassword()).toCharArray();
1242          getOut().println();
1243        }
1244
1245        try
1246        {
1247          keyManager = new KeyStoreKeyManager(keyStorePath.getValue(), pw,
1248               keyStoreFormat.getValue(), certificateNickname.getValue());
1249        }
1250        catch (final Exception e)
1251        {
1252          debugException(e);
1253          throw new LDAPException(ResultCode.LOCAL_ERROR,
1254               ERR_LDAP_TOOL_CANNOT_CREATE_KEY_MANAGER.get(
1255                    getExceptionMessage(e)), e);
1256        }
1257      }
1258
1259      final TrustManager tm;
1260      if (trustAll.isPresent())
1261      {
1262        tm = new TrustAllTrustManager(false);
1263      }
1264      else if (trustStorePath.isPresent())
1265      {
1266        char[] pw = null;
1267        if (trustStorePassword.isPresent())
1268        {
1269          pw = trustStorePassword.getValue().toCharArray();
1270        }
1271        else if (trustStorePasswordFile.isPresent())
1272        {
1273          try
1274          {
1275            pw = trustStorePasswordFile.getNonBlankFileLines().get(0).
1276                      toCharArray();
1277          }
1278          catch (final Exception e)
1279          {
1280            debugException(e);
1281            throw new LDAPException(ResultCode.LOCAL_ERROR,
1282                 ERR_LDAP_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get(
1283                      getExceptionMessage(e)), e);
1284          }
1285        }
1286        else if (promptForTrustStorePassword.isPresent())
1287        {
1288          getOut().print(INFO_LDAP_TOOL_ENTER_TRUST_STORE_PASSWORD.get());
1289          pw = StaticUtils.toUTF8String(
1290               PasswordReader.readPassword()).toCharArray();
1291          getOut().println();
1292        }
1293
1294        tm = new TrustStoreTrustManager(trustStorePath.getValue(), pw,
1295             trustStoreFormat.getValue(), true);
1296      }
1297      else if (promptTrustManager.get() != null)
1298      {
1299        tm = promptTrustManager.get();
1300      }
1301      else
1302      {
1303        final ArrayList<String> expectedAddresses = new ArrayList<String>(5);
1304        if (useSSL.isPresent() || useStartTLS.isPresent())
1305        {
1306          expectedAddresses.addAll(host.getValues());
1307        }
1308
1309        final AggregateTrustManager atm = new AggregateTrustManager(false,
1310             JVMDefaultTrustManager.getInstance(),
1311             new PromptTrustManager(null, true, expectedAddresses, null,
1312                  null));
1313        if (promptTrustManager.compareAndSet(null, atm))
1314        {
1315          tm = atm;
1316        }
1317        else
1318        {
1319          tm = promptTrustManager.get();
1320        }
1321      }
1322
1323      return new SSLUtil(keyManager, tm);
1324    }
1325    else
1326    {
1327      return null;
1328    }
1329  }
1330
1331
1332
1333  /**
1334   * Creates the bind request to use to authenticate to the server.
1335   *
1336   * @return  The bind request to use to authenticate to the server, or
1337   *          {@code null} if no bind should be performed.
1338   *
1339   * @throws  LDAPException  If a problem occurs while creating the bind
1340   *                         request.
1341   */
1342  public BindRequest createBindRequest()
1343         throws LDAPException
1344  {
1345    if (! supportsAuthentication())
1346    {
1347      return null;
1348    }
1349
1350    final Control[] bindControls;
1351    final List<Control> bindControlList = getBindControls();
1352    if ((bindControlList == null) || bindControlList.isEmpty())
1353    {
1354      bindControls = NO_CONTROLS;
1355    }
1356    else
1357    {
1358      bindControls = new Control[bindControlList.size()];
1359      bindControlList.toArray(bindControls);
1360    }
1361
1362    byte[] pw;
1363    if (bindPassword.isPresent())
1364    {
1365      pw = StaticUtils.getBytes(bindPassword.getValue());
1366    }
1367    else if (bindPasswordFile.isPresent())
1368    {
1369      try
1370      {
1371        pw = StaticUtils.getBytes(
1372             bindPasswordFile.getNonBlankFileLines().get(0));
1373      }
1374      catch (final Exception e)
1375      {
1376        debugException(e);
1377        throw new LDAPException(ResultCode.LOCAL_ERROR,
1378             ERR_LDAP_TOOL_CANNOT_READ_BIND_PASSWORD.get(
1379                  getExceptionMessage(e)), e);
1380      }
1381    }
1382    else if (promptForBindPassword.isPresent())
1383    {
1384      getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1385      pw = PasswordReader.readPassword();
1386      getOriginalOut().println();
1387    }
1388    else
1389    {
1390      pw = null;
1391    }
1392
1393    if (saslOption.isPresent())
1394    {
1395      final String dnStr;
1396      if (bindDN.isPresent())
1397      {
1398        dnStr = bindDN.getValue().toString();
1399      }
1400      else
1401      {
1402        dnStr = null;
1403      }
1404
1405      return SASLUtils.createBindRequest(dnStr, pw,
1406           defaultToPromptForBindPassword(), this, null,
1407           saslOption.getValues(), bindControls);
1408    }
1409    else if (useSASLExternal.isPresent())
1410    {
1411      return new EXTERNALBindRequest(bindControls);
1412    }
1413    else if (bindDN.isPresent())
1414    {
1415      if ((pw == null) && (! bindDN.getValue().isNullDN()) &&
1416          defaultToPromptForBindPassword())
1417      {
1418        getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1419        pw = PasswordReader.readPassword();
1420        getOriginalOut().println();
1421      }
1422
1423      return new SimpleBindRequest(bindDN.getValue(), pw, bindControls);
1424    }
1425    else
1426    {
1427      return null;
1428    }
1429  }
1430
1431
1432
1433  /**
1434   * Indicates whether any of the LDAP-related arguments maintained by the
1435   * {@code LDAPCommandLineTool} class were provided on the command line.
1436   *
1437   * @return  {@code true} if any of the LDAP-related arguments maintained by
1438   *          the {@code LDAPCommandLineTool} were provided on the command line,
1439   *          or {@code false} if not.
1440   */
1441  public final boolean anyLDAPArgumentsProvided()
1442  {
1443    return isAnyPresent(host, port, bindDN, bindPassword, bindPasswordFile,
1444         promptForBindPassword, useSSL, useStartTLS, trustAll, keyStorePath,
1445         keyStorePassword, keyStorePasswordFile, promptForKeyStorePassword,
1446         keyStoreFormat, trustStorePath, trustStorePassword,
1447         trustStorePasswordFile, trustStoreFormat, certificateNickname,
1448         saslOption, useSASLExternal);
1449  }
1450
1451
1452
1453  /**
1454   * Indicates whether at least one of the provided arguments was provided on
1455   * the command line.
1456   *
1457   * @param  args  The set of command-line arguments for which to make the
1458   *               determination.
1459   *
1460   * @return  {@code true} if at least one of the provided arguments was
1461   *          provided on the command line, or {@code false} if not.
1462   */
1463  private static boolean isAnyPresent(final Argument... args)
1464  {
1465    for (final Argument a : args)
1466    {
1467      if ((a != null) && (a.getNumOccurrences() > 0))
1468      {
1469        return true;
1470      }
1471    }
1472
1473    return false;
1474  }
1475}