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