All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.util.SASLUtils Maven / Gradle / Ivy

Go to download

The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use Java API for communicating with LDAP directory servers and performing related tasks like reading and writing LDIF, encoding and decoding data using base64 and ASN.1 BER, and performing secure communication. This package contains the Standard Edition of the LDAP SDK, which is a complete, general-purpose library for communicating with LDAPv3 directory servers.

There is a newer version: 7.0.1
Show newest version
/*
 * Copyright 2011-2023 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2011-2023 Ping Identity Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Copyright (C) 2011-2023 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.util;



import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import com.unboundid.ldap.sdk.ANONYMOUSBindRequest;
import com.unboundid.ldap.sdk.Control;
import com.unboundid.ldap.sdk.CRAMMD5BindRequest;
import com.unboundid.ldap.sdk.DIGESTMD5BindRequest;
import com.unboundid.ldap.sdk.DIGESTMD5BindRequestProperties;
import com.unboundid.ldap.sdk.EXTERNALBindRequest;
import com.unboundid.ldap.sdk.GSSAPIBindRequest;
import com.unboundid.ldap.sdk.GSSAPIBindRequestProperties;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.OAUTHBEARERBindRequest;
import com.unboundid.ldap.sdk.PLAINBindRequest;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SASLBindRequest;
import com.unboundid.ldap.sdk.SASLQualityOfProtection;
import com.unboundid.ldap.sdk.SCRAMSHA1BindRequest;
import com.unboundid.ldap.sdk.SCRAMSHA256BindRequest;
import com.unboundid.ldap.sdk.SCRAMSHA512BindRequest;
import com.unboundid.ldap.sdk.unboundidds.SingleUseTOTPBindRequest;
import com.unboundid.ldap.sdk.unboundidds.
            UnboundIDCertificatePlusPasswordBindRequest;
import com.unboundid.ldap.sdk.unboundidds.UnboundIDDeliveredOTPBindRequest;
import com.unboundid.ldap.sdk.unboundidds.UnboundIDTOTPBindRequest;
import com.unboundid.ldap.sdk.unboundidds.UnboundIDYubiKeyOTPBindRequest;

import static com.unboundid.util.UtilityMessages.*;



/**
 * This class provides a utility that may be used to help process SASL bind
 * operations using the LDAP SDK.
 */
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class SASLUtils
{
  /**
   * The name of the SASL option that specifies the access token.  It may be
   * used in conjunction with the OAUTHBEARER mechanism.
   */
  @NotNull public static final String SASL_OPTION_ACCESS_TOKEN = "accessToken";



  /**
   * The name of the SASL option that specifies the authentication ID.  It may
   * be used in conjunction with the CRAM-MD5, DIGEST-MD5, GSSAPI, and PLAIN
   * mechanisms.
   */
  @NotNull public static final String SASL_OPTION_AUTH_ID = "authID";



  /**
   * The name of the SASL option that specifies the authorization ID.  It may
   * be used in conjunction with the DIGEST-MD5, GSSAPI, and PLAIN mechanisms.
   */
  @NotNull public static final String SASL_OPTION_AUTHZ_ID = "authzID";



  /**
   * The name of the SASL option that specifies the path to the JAAS config
   * file.  It may be used in conjunction with the GSSAPI mechanism.
   */
  @NotNull public static final String SASL_OPTION_CONFIG_FILE = "configFile";



  /**
   * The name of the SASL option that indicates whether debugging should be
   * enabled.  It may be used in conjunction with the GSSAPI mechanism.
   */
  @NotNull public static final String SASL_OPTION_DEBUG = "debug";



  /**
   * The name of the SASL option that specifies the KDC address.  It may be used
   * in conjunction with the GSSAPI mechanism.
   */
  @NotNull public static final String SASL_OPTION_KDC_ADDRESS = "kdcAddress";



  /**
   * The name of the SASL option that specifies the desired SASL mechanism to
   * use to authenticate to the server.
   */
  @NotNull public static final String SASL_OPTION_MECHANISM = "mech";



  /**
   * The name of the SASL option that specifies a one-time password.  It may be
   * used in conjunction with the UNBOUNDID-DELIVERED-OTP and
   * UNBOUNDID-YUBIKEY-OTP mechanisms.
   */
  @NotNull public static final String SASL_OPTION_OTP = "otp";



  /**
   * The name of the SASL option that may be used to indicate whether to
   * prompt for a static password.  It may be used in conjunction with the
   * UNBOUNDID-TOTP and UNBOUNDID-YUBIKEY-OTP mechanisms.
   */
  @NotNull public static final String SASL_OPTION_PROMPT_FOR_STATIC_PW =
       "promptForStaticPassword";



  /**
   * The name of the SASL option that specifies the GSSAPI service principal
   * protocol.  It may be used in conjunction with the GSSAPI mechanism.
   */
  @NotNull public static final String SASL_OPTION_PROTOCOL = "protocol";



  /**
   * The name of the SASL option that specifies the quality of protection that
   * should be used for communication that occurs after the authentication has
   * completed.
   */
  @NotNull public static final String SASL_OPTION_QOP = "qop";



  /**
   * The name of the SASL option that specifies the realm name.  It may be used
   * in conjunction with the DIGEST-MD5 and GSSAPI mechanisms.
   */
  @NotNull public static final String SASL_OPTION_REALM = "realm";



  /**
   * The name of the SASL option that indicates whether to require an existing
   * Kerberos session from the ticket cache.  It may be used in conjunction with
   * the GSSAPI mechanism.
   */
  @NotNull public static final String SASL_OPTION_REQUIRE_CACHE =
       "requireCache";



  /**
   * The name of the SASL option that indicates whether to attempt to renew the
   * Kerberos TGT for an existing session.  It may be used in conjunction with
   * the GSSAPI mechanism.
   */
  @NotNull public static final String SASL_OPTION_RENEW_TGT = "renewTGT";



  /**
   * The name of the SASL option that specifies the path to the Kerberos ticket
   * cache to use.  It may be used in conjunction with the GSSAPI mechanism.
   */
  @NotNull public static final String SASL_OPTION_TICKET_CACHE_PATH =
       "ticketCache";



  /**
   * The name of the SASL option that specifies the TOTP authentication code.
   * It may be used in conjunction with the UNBOUNDID-TOTP mechanism.
   */
  @NotNull public static final String SASL_OPTION_TOTP_PASSWORD =
       "totpPassword";



  /**
   * The name of the SASL option that specifies the trace string.  It may be
   * used in conjunction with the ANONYMOUS mechanism.
   */
  @NotNull public static final String SASL_OPTION_TRACE = "trace";



  /**
   * The name of the SASL option that specifies the username.  It may be
   * used in conjunction with the SCRAM-SHA-1, SCRAM-SHA-256, and SCRAM-SHA-512
   * mechanisms.
   */
  @NotNull public static final String SASL_OPTION_USERNAME = "username";



  /**
   * The name of the SASL option that specifies whether to use a Kerberos ticket
   * cache.  It may be used in conjunction with the GSSAPI mechanism.
   */
  @NotNull public static final String SASL_OPTION_USE_TICKET_CACHE =
       "useTicketCache";



  /**
   * A map with information about all supported SASL mechanisms, mapped from
   * lowercase mechanism name to an object with information about that
   * mechanism.
   */
  @NotNull private static final Map SASL_MECHANISMS;



  static
  {
    final TreeMap m = new TreeMap<>();

    m.put(
         StaticUtils.toLowerCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME),
         new SASLMechanismInfo(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME,
              INFO_SASL_ANONYMOUS_DESCRIPTION.get(), false, false,
              new SASLOption(SASL_OPTION_TRACE,
                   INFO_SASL_ANONYMOUS_OPTION_TRACE.get(), false, false)));

    m.put(StaticUtils.toLowerCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME),
         new SASLMechanismInfo(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME,
              INFO_SASL_CRAM_MD5_DESCRIPTION.get(), true, true,
              new SASLOption(SASL_OPTION_AUTH_ID,
                   INFO_SASL_CRAM_MD5_OPTION_AUTH_ID.get(), true, false)));

    m.put(
         StaticUtils.toLowerCase(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME),
         new SASLMechanismInfo(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME,
              INFO_SASL_DIGEST_MD5_DESCRIPTION.get(), true, true,
              new SASLOption(SASL_OPTION_AUTH_ID,
                   INFO_SASL_DIGEST_MD5_OPTION_AUTH_ID.get(), true, false),
              new SASLOption(SASL_OPTION_AUTHZ_ID,
                   INFO_SASL_DIGEST_MD5_OPTION_AUTHZ_ID.get(), false, false),
              new SASLOption(SASL_OPTION_REALM,
                   INFO_SASL_DIGEST_MD5_OPTION_REALM.get(), false, false),
              new SASLOption(SASL_OPTION_QOP,
                   INFO_SASL_DIGEST_MD5_OPTION_QOP.get(), false, false)));

    m.put(StaticUtils.toLowerCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME),
         new SASLMechanismInfo(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME,
              INFO_SASL_EXTERNAL_DESCRIPTION.get(), false, false));

    m.put(StaticUtils.toLowerCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME),
         new SASLMechanismInfo(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME,
              INFO_SASL_GSSAPI_DESCRIPTION.get(), true, false,
              new SASLOption(SASL_OPTION_AUTH_ID,
                   INFO_SASL_GSSAPI_OPTION_AUTH_ID.get(), true, false),
              new SASLOption(SASL_OPTION_AUTHZ_ID,
                   INFO_SASL_GSSAPI_OPTION_AUTHZ_ID.get(), false, false),
              new SASLOption(SASL_OPTION_CONFIG_FILE,
                   INFO_SASL_GSSAPI_OPTION_CONFIG_FILE.get(), false, false),
              new SASLOption(SASL_OPTION_DEBUG,
                   INFO_SASL_GSSAPI_OPTION_DEBUG.get(), false, false),
              new SASLOption(SASL_OPTION_KDC_ADDRESS,
                   INFO_SASL_GSSAPI_OPTION_KDC_ADDRESS.get(), false, false),
              new SASLOption(SASL_OPTION_PROTOCOL,
                   INFO_SASL_GSSAPI_OPTION_PROTOCOL.get(), false, false),
              new SASLOption(SASL_OPTION_REALM,
                   INFO_SASL_GSSAPI_OPTION_REALM.get(), false, false),
              new SASLOption(SASL_OPTION_QOP,
                   INFO_SASL_GSSAPI_OPTION_QOP.get(), false, false),
              new SASLOption(SASL_OPTION_RENEW_TGT,
                   INFO_SASL_GSSAPI_OPTION_RENEW_TGT.get(), false, false),
              new SASLOption(SASL_OPTION_REQUIRE_CACHE,
                   INFO_SASL_GSSAPI_OPTION_REQUIRE_TICKET_CACHE.get(), false,
                   false),
              new SASLOption(SASL_OPTION_TICKET_CACHE_PATH,
                   INFO_SASL_GSSAPI_OPTION_TICKET_CACHE.get(), false, false),
              new SASLOption(SASL_OPTION_USE_TICKET_CACHE,
                   INFO_SASL_GSSAPI_OPTION_USE_TICKET_CACHE.get(), false,
                   false)));

    m.put(StaticUtils.toLowerCase(
         OAUTHBEARERBindRequest.OAUTHBEARER_MECHANISM_NAME),
         new SASLMechanismInfo(
              OAUTHBEARERBindRequest.OAUTHBEARER_MECHANISM_NAME,
              INFO_SASL_PLAIN_DESCRIPTION.get(), false, false,
              new SASLOption(SASL_OPTION_ACCESS_TOKEN,
                   INFO_SASL_OAUTHBEARER_OPTION_ACCESS_TOKEN.get(), false,
                   false)));

    m.put(StaticUtils.toLowerCase(PLAINBindRequest.PLAIN_MECHANISM_NAME),
         new SASLMechanismInfo(PLAINBindRequest.PLAIN_MECHANISM_NAME,
              INFO_SASL_PLAIN_DESCRIPTION.get(), true, true,
              new SASLOption(SASL_OPTION_AUTH_ID,
                   INFO_SASL_PLAIN_OPTION_AUTH_ID.get(), true, false),
              new SASLOption(SASL_OPTION_AUTHZ_ID,
                   INFO_SASL_PLAIN_OPTION_AUTHZ_ID.get(), false, false)));

    m.put(
         StaticUtils.toLowerCase(
              SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME),
         new SASLMechanismInfo(SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME,
              INFO_SASL_SCRAM_SHA_1_DESCRIPTION.get(), true, true,
              new SASLOption(SASL_OPTION_USERNAME,
                   INFO_SASL_SCRAM_OPTION_USERNAME.get(), true, false)));

    m.put(
         StaticUtils.toLowerCase(
              SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME),
         new SASLMechanismInfo(
              SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME,
              INFO_SASL_SCRAM_SHA_256_DESCRIPTION.get(), true, true,
              new SASLOption(SASL_OPTION_USERNAME,
                   INFO_SASL_SCRAM_OPTION_USERNAME.get(), true, false)));

    m.put(
         StaticUtils.toLowerCase(
              SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME),
         new SASLMechanismInfo(
              SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME,
              INFO_SASL_SCRAM_SHA_512_DESCRIPTION.get(), true, true,
              new SASLOption(SASL_OPTION_USERNAME,
                   INFO_SASL_SCRAM_OPTION_USERNAME.get(), true, false)));

    m.put(StaticUtils.toLowerCase(
         UnboundIDCertificatePlusPasswordBindRequest.
              UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME),
         new SASLMechanismInfo(
              UnboundIDCertificatePlusPasswordBindRequest.
                   UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME,
              INFO_SASL_UNBOUNDID_CERT_PLUS_PASSWORD_DESCRIPTION.get(), true,
              true));

    m.put(
         StaticUtils.toLowerCase(
              UnboundIDDeliveredOTPBindRequest.
                   UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME),
         new SASLMechanismInfo(
              UnboundIDDeliveredOTPBindRequest.
                   UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME,
              INFO_SASL_UNBOUNDID_DELIVERED_OTP_DESCRIPTION.get(), false, false,
              new SASLOption(SASL_OPTION_AUTH_ID,
                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTH_ID.get(), true, false),
              new SASLOption(SASL_OPTION_AUTHZ_ID,
                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTHZ_ID.get(), false,
                   false),
              new SASLOption(SASL_OPTION_OTP,
                   INFO_SASL_UNBOUNDID_DELIVERED_OTP_OPTION_OTP.get(), true,
                   false)));

    m.put(
         StaticUtils.toLowerCase(
              UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME),
         new SASLMechanismInfo(
              UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME,
              INFO_SASL_UNBOUNDID_TOTP_DESCRIPTION.get(), true, false,
              new SASLOption(SASL_OPTION_AUTH_ID,
                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTH_ID.get(), true, false),
              new SASLOption(SASL_OPTION_AUTHZ_ID,
                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTHZ_ID.get(), false,
                   false),
              new SASLOption(SASL_OPTION_PROMPT_FOR_STATIC_PW,
                   INFO_SASL_UNBOUNDID_TOTP_OPTION_PROMPT_FOR_PW.get(), false,
                   false),
              new SASLOption(SASL_OPTION_TOTP_PASSWORD,
                   INFO_SASL_UNBOUNDID_TOTP_OPTION_TOTP_PASSWORD.get(), true,
                   false)));

    m.put(
         StaticUtils.toLowerCase(
              UnboundIDYubiKeyOTPBindRequest.
                   UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME),
         new SASLMechanismInfo(
              UnboundIDYubiKeyOTPBindRequest.
                   UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME,
              INFO_SASL_UNBOUNDID_YUBIKEY_OTP_DESCRIPTION.get(), true, false,
              new SASLOption(SASL_OPTION_AUTH_ID,
                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_AUTH_ID.get(), true,
                   false),
              new SASLOption(SASL_OPTION_AUTHZ_ID,
                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_AUTHZ_ID.get(), false,
                   false),
              new SASLOption(SASL_OPTION_OTP,
                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_OTP.get(), true,
                   false),
              new SASLOption(SASL_OPTION_PROMPT_FOR_STATIC_PW,
                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_PROMPT_FOR_PW.get(),
                   false, false)));

    SASL_MECHANISMS = Collections.unmodifiableMap(m);
  }



  /**
   * Prevent this utility class from being instantiated.
   */
  private SASLUtils()
  {
    // No implementation required.
  }



  /**
   * Retrieves information about the SASL mechanisms supported for use by this
   * class.
   *
   * @return  Information about the SASL mechanisms supported for use by this
   *          class.
   */
  @NotNull()
  public static List getSupportedSASLMechanisms()
  {
    return Collections.unmodifiableList(
         new ArrayList<>(SASL_MECHANISMS.values()));
  }



  /**
   * Retrieves information about the specified SASL mechanism.
   *
   * @param  mechanism  The name of the SASL mechanism for which to retrieve
   *                    information.  It will be treated in a case-insensitive
   *                    manner.
   *
   * @return  Information about the requested SASL mechanism, or {@code null} if
   *          no information about the specified mechanism is available.
   */
  @Nullable()
  public static SASLMechanismInfo getSASLMechanismInfo(
                                       @NotNull final String mechanism)
  {
    return SASL_MECHANISMS.get(StaticUtils.toLowerCase(mechanism));
  }



  /**
   * Creates a new SASL bind request using the provided information.
   *
   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
   *                    SASL mechanisms, this should be {@code null}, since the
   *                    identity of the target user should be specified in some
   *                    other way (e.g., via an "authID" SASL option).
   * @param  password   The password to use for the SASL bind request.  It may
   *                    be {@code null} if no password is required for the
   *                    desired SASL mechanism.
   * @param  mechanism  The name of the SASL mechanism to use.  It may be
   *                    {@code null} if the provided set of options contains a
   *                    "mech" option to specify the desired SASL option.
   * @param  options    The set of SASL options to use when creating the bind
   *                    request, in the form "name=value".  It may be
   *                    {@code null} or empty if no SASL options are needed and
   *                    a value was provided for the {@code mechanism} argument.
   *                    If the set of SASL options includes a "mech" option,
   *                    then the {@code mechanism} argument must be {@code null}
   *                    or have a value that matches the value of the "mech"
   *                    SASL option.
   *
   * @return  The SASL bind request created using the provided information.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  public static SASLBindRequest createBindRequest(@Nullable final String bindDN,
                                     @Nullable final String password,
                                     @Nullable final String mechanism,
                                     @Nullable final String... options)
         throws LDAPException
  {
    return createBindRequest(bindDN,
         (password == null ? null : StaticUtils.getBytes(password)), mechanism,
         StaticUtils.toList(options));
  }



  /**
   * Creates a new SASL bind request using the provided information.
   *
   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
   *                    SASL mechanisms, this should be {@code null}, since the
   *                    identity of the target user should be specified in some
   *                    other way (e.g., via an "authID" SASL option).
   * @param  password   The password to use for the SASL bind request.  It may
   *                    be {@code null} if no password is required for the
   *                    desired SASL mechanism.
   * @param  mechanism  The name of the SASL mechanism to use.  It may be
   *                    {@code null} if the provided set of options contains a
   *                    "mech" option to specify the desired SASL option.
   * @param  options    The set of SASL options to use when creating the bind
   *                    request, in the form "name=value".  It may be
   *                    {@code null} or empty if no SASL options are needed and
   *                    a value was provided for the {@code mechanism} argument.
   *                    If the set of SASL options includes a "mech" option,
   *                    then the {@code mechanism} argument must be {@code null}
   *                    or have a value that matches the value of the "mech"
   *                    SASL option.
   * @param  controls   The set of controls to include in the request.
   *
   * @return  The SASL bind request created using the provided information.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  public static SASLBindRequest createBindRequest(@Nullable final String bindDN,
                                     @Nullable final String password,
                                     @Nullable final String mechanism,
                                     @Nullable final List options,
                                     @Nullable final Control... controls)
         throws LDAPException
  {
    return createBindRequest(bindDN,
         (password == null
              ? null
              : StaticUtils.getBytes(password)), mechanism, options,
         controls);
  }



  /**
   * Creates a new SASL bind request using the provided information.
   *
   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
   *                    SASL mechanisms, this should be {@code null}, since the
   *                    identity of the target user should be specified in some
   *                    other way (e.g., via an "authID" SASL option).
   * @param  password   The password to use for the SASL bind request.  It may
   *                    be {@code null} if no password is required for the
   *                    desired SASL mechanism.
   * @param  mechanism  The name of the SASL mechanism to use.  It may be
   *                    {@code null} if the provided set of options contains a
   *                    "mech" option to specify the desired SASL option.
   * @param  options    The set of SASL options to use when creating the bind
   *                    request, in the form "name=value".  It may be
   *                    {@code null} or empty if no SASL options are needed and
   *                    a value was provided for the {@code mechanism} argument.
   *                    If the set of SASL options includes a "mech" option,
   *                    then the {@code mechanism} argument must be {@code null}
   *                    or have a value that matches the value of the "mech"
   *                    SASL option.
   *
   * @return  The SASL bind request created using the provided information.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  public static SASLBindRequest createBindRequest(@Nullable final String bindDN,
                                     @Nullable final byte[] password,
                                     @Nullable final String mechanism,
                                     @Nullable final String... options)
         throws LDAPException
  {
    return createBindRequest(bindDN, password, mechanism,
         StaticUtils.toList(options));
  }



  /**
   * Creates a new SASL bind request using the provided information.
   *
   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
   *                    SASL mechanisms, this should be {@code null}, since the
   *                    identity of the target user should be specified in some
   *                    other way (e.g., via an "authID" SASL option).
   * @param  password   The password to use for the SASL bind request.  It may
   *                    be {@code null} if no password is required for the
   *                    desired SASL mechanism.
   * @param  mechanism  The name of the SASL mechanism to use.  It may be
   *                    {@code null} if the provided set of options contains a
   *                    "mech" option to specify the desired SASL option.
   * @param  options    The set of SASL options to use when creating the bind
   *                    request, in the form "name=value".  It may be
   *                    {@code null} or empty if no SASL options are needed and
   *                    a value was provided for the {@code mechanism} argument.
   *                    If the set of SASL options includes a "mech" option,
   *                    then the {@code mechanism} argument must be {@code null}
   *                    or have a value that matches the value of the "mech"
   *                    SASL option.
   * @param  controls   The set of controls to include in the request.
   *
   * @return  The SASL bind request created using the provided information.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  public static SASLBindRequest createBindRequest(@Nullable final String bindDN,
                                     @Nullable final byte[] password,
                                     @Nullable final String mechanism,
                                     @Nullable final List options,
                                     @Nullable final Control... controls)
         throws LDAPException
  {
    return createBindRequest(bindDN, password, false, null, mechanism, options,
         controls);
  }



  /**
   * Creates a new SASL bind request using the provided information.
   *
   * @param  bindDN             The bind DN to use for the SASL bind request.
   *                            For most SASL mechanisms, this should be
   *                            {@code null}, since the identity of the target
   *                            user should be specified in some other way
   *                            (e.g., via an "authID" SASL option).
   * @param  password           The password to use for the SASL bind request.
   *                            It may be {@code null} if no password is
   *                            required for the desired SASL mechanism.
   * @param  promptForPassword  Indicates whether to interactively prompt for
   *                            the password if one is needed but none was
   *                            provided.
   * @param  tool               The command-line tool whose input and output
   *                            streams should be used when prompting for the
   *                            bind password.  It may be {@code null} if
   *                            {@code promptForPassword} is {@code false}.
   * @param  mechanism          The name of the SASL mechanism to use.  It may
   *                            be {@code null} if the provided set of options
   *                            contains a "mech" option to specify the desired
   *                            SASL option.
   * @param  options            The set of SASL options to use when creating the
   *                            bind request, in the form "name=value".  It may
   *                            be {@code null} or empty if no SASL options are
   *                            needed and a value was provided for the
   *                            {@code mechanism} argument.  If the set of SASL
   *                            options includes a "mech" option, then the
   *                            {@code mechanism} argument must be {@code null}
   *                            or have a value that matches the value of the
   *                            "mech" SASL option.
   * @param  controls           The set of controls to include in the request.
   *
   * @return  The SASL bind request created using the provided information.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  public static SASLBindRequest createBindRequest(@Nullable final String bindDN,
                                     @Nullable final byte[] password,
                                     final boolean promptForPassword,
                                     @Nullable final CommandLineTool tool,
                                     @Nullable final String mechanism,
                                     @Nullable final List options,
                                     @Nullable final Control... controls)
         throws LDAPException
  {
    if (promptForPassword)
    {
      Validator.ensureNotNull(tool);
    }

    // Parse the provided set of options to ensure that they are properly
    // formatted in name-value form, and extract the SASL mechanism.
    final String mech;
    final Map optionsMap = parseOptions(options);
    final String mechOption =
         optionsMap.remove(StaticUtils.toLowerCase(SASL_OPTION_MECHANISM));
    if (mechOption != null)
    {
      mech = mechOption;
      if ((mechanism != null) && (! mech.equalsIgnoreCase(mechanism)))
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_OPTION_MECH_CONFLICT.get(mechanism, mech));
      }
    }
    else
    {
      mech = mechanism;
    }

    if (mech == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_OPTION_NO_MECH.get());
    }

    if (mech.equalsIgnoreCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME))
    {
      return createANONYMOUSBindRequest(password, optionsMap, controls);
    }
    else if (mech.equalsIgnoreCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME))
    {
      return createCRAMMD5BindRequest(password, promptForPassword, tool,
           optionsMap, controls);
    }
    else if (mech.equalsIgnoreCase(
                  DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME))
    {
      return createDIGESTMD5BindRequest(password, promptForPassword, tool,
           optionsMap, controls);
    }
    else if (mech.equalsIgnoreCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME))
    {
      return createEXTERNALBindRequest(password, optionsMap, controls);
    }
    else if (mech.equalsIgnoreCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME))
    {
      return createGSSAPIBindRequest(password, promptForPassword, tool,
           optionsMap, controls);
    }
    else if (mech.equalsIgnoreCase(
         OAUTHBEARERBindRequest.OAUTHBEARER_MECHANISM_NAME))
    {
      return createOAUTHBEARERBindRequest(password, promptForPassword,
           tool, optionsMap, controls);
    }
    else if (mech.equalsIgnoreCase(PLAINBindRequest.PLAIN_MECHANISM_NAME))
    {
      return createPLAINBindRequest(password, promptForPassword, tool,
           optionsMap, controls);
    }
    else if (mech.equalsIgnoreCase(
         SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME))
    {
      return createSCRAMSHA1BindRequest(password, promptForPassword, tool,
           optionsMap, controls);
    }
    else if (mech.equalsIgnoreCase(
         SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME))
    {
      return createSCRAMSHA256BindRequest(password, promptForPassword, tool,
           optionsMap, controls);
    }
    else if (mech.equalsIgnoreCase(
         SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME))
    {
      return createSCRAMSHA512BindRequest(password, promptForPassword, tool,
           optionsMap, controls);
    }
    else if (mech.equalsIgnoreCase(UnboundIDCertificatePlusPasswordBindRequest.
             UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME))
    {
      return createUnboundIDCertificatePlusPasswordBindRequest(password, tool,
           optionsMap, controls);
    }
    else if (mech.equalsIgnoreCase(UnboundIDDeliveredOTPBindRequest.
             UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME))
    {
      return createUNBOUNDIDDeliveredOTPBindRequest(password, optionsMap,
           controls);
    }
    else if (mech.equalsIgnoreCase(
             UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME))
    {
      return createUNBOUNDIDTOTPBindRequest(password, tool, optionsMap,
           controls);
    }
    else if (mech.equalsIgnoreCase(
         UnboundIDYubiKeyOTPBindRequest.UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME))
    {
      return createUNBOUNDIDYUBIKEYOTPBindRequest(password, tool, optionsMap,
           controls);
    }
    else
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_OPTION_UNSUPPORTED_MECH.get(mech));
    }
  }



  /**
   * Creates a SASL ANONYMOUS bind request using the provided set of options.
   *
   * @param  password  The password to use for the bind request.
   * @param  options   The set of SASL options for the bind request.
   * @param  controls  The set of controls to include in the request.
   *
   * @return  The SASL ANONYMOUS bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static ANONYMOUSBindRequest createANONYMOUSBindRequest(
                      @Nullable final byte[] password,
                      @NotNull final Map options,
                      @Nullable final Control[] controls)
          throws LDAPException
  {
    if (password != null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
                ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME));
    }


    // The trace option is optional.
    final String trace =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_TRACE));

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME);

    return new ANONYMOUSBindRequest(trace, controls);
  }



  /**
   * Creates a SASL CRAM-MD5 bind request using the provided password and set of
   * options.
   *
   * @param  password           The password to use for the bind request.
   * @param  promptForPassword  Indicates whether to interactively prompt for
   *                            the password if one is needed but none was
   *                            provided.
   * @param  tool               The command-line tool whose input and output
   *                            streams should be used when prompting for the
   *                            bind password.  It may be {@code null} only if
   *                            {@code promptForPassword} is {@code false}.
   * @param  options            The set of SASL options for the bind request.
   * @param  controls           The set of controls to include in the request.
   *
   * @return  The SASL CRAM-MD5 bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static CRAMMD5BindRequest createCRAMMD5BindRequest(
                      @Nullable final byte[] password,
                      final boolean promptForPassword,
                      @Nullable final CommandLineTool tool,
                      @NotNull final Map options,
                      @Nullable final Control[] controls)
          throws LDAPException
  {
    final byte[] pw;
    if (password == null)
    {
      if (promptForPassword)
      {
        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
        pw = PasswordReader.readPassword();
        tool.getOriginalOut().println();
      }
      else
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
                  CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
      }
    }
    else
    {
      pw = password;
    }


    // The authID option is required.
    final String authID =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
    if (authID == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
                CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
    }


    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME);

    return new CRAMMD5BindRequest(authID, pw, controls);
  }



  /**
   * Creates a SASL DIGEST-MD5 bind request using the provided password and set
   * of options.
   *
   * @param  password           The password to use for the bind request.
   * @param  promptForPassword  Indicates whether to interactively prompt for
   *                            the password if one is needed but none was
   *                            provided.
   * @param  tool               The command-line tool whose input and output
   *                            streams should be used when prompting for the
   *                            bind password.  It may be {@code null} only if
   *                            {@code promptForPassword} is {@code false}.
   * @param  options            The set of SASL options for the bind request.
   * @param  controls           The set of controls to include in the request.
   *
   * @return  The SASL DIGEST-MD5 bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static DIGESTMD5BindRequest createDIGESTMD5BindRequest(
                      @Nullable() final byte[] password,
                      final boolean promptForPassword,
                      @Nullable final CommandLineTool tool,
                      @NotNull final Map options,
                      @Nullable final Control[] controls)
          throws LDAPException
  {
    final byte[] pw;
    if (password == null)
    {
      if (promptForPassword)
      {
        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
        pw = PasswordReader.readPassword();
        tool.getOriginalOut().println();
      }
      else
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
                  DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME));
      }
    }
    else
    {
      pw = password;
    }

    // The authID option is required.
    final String authID =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
    if (authID == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
                DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME));
    }

    final DIGESTMD5BindRequestProperties properties =
         new DIGESTMD5BindRequestProperties(authID, pw);

    // The authzID option is optional.
    properties.setAuthorizationID(
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID)));

    // The realm option is optional.
    properties.setRealm(options.remove(
         StaticUtils.toLowerCase(SASL_OPTION_REALM)));

    // The QoP option is optional, and may contain multiple values that need to
    // be parsed.
    final String qopString =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_QOP));
    if (qopString != null)
    {
      properties.setAllowedQoP(
           SASLQualityOfProtection.decodeQoPList(qopString));
    }

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME);

    return new DIGESTMD5BindRequest(properties, controls);
  }



  /**
   * Creates a SASL EXTERNAL bind request using the provided set of options.
   *
   * @param  password  The password to use for the bind request.
   * @param  options   The set of SASL options for the bind request.
   * @param  controls  The set of controls to include in the request.
   *
   * @return  The SASL EXTERNAL bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static EXTERNALBindRequest createEXTERNALBindRequest(
                      @Nullable final byte[] password,
                      @NotNull final Map options,
                      @Nullable final Control[] controls)
          throws LDAPException
  {
    if (password != null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
                EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME));
    }

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME);

    return new EXTERNALBindRequest(controls);
  }



  /**
   * Creates a SASL GSSAPI bind request using the provided password and set of
   * options.
   *
   * @param  password           The password to use for the bind request.
   * @param  promptForPassword  Indicates whether to interactively prompt for
   *                            the password if one is needed but none was
   *                            provided.
   * @param  tool               The command-line tool whose input and output
   *                            streams should be used when prompting for the
   *                            bind password.  It may be {@code null} only if
   *                            {@code promptForPassword} is {@code false}.
   * @param  options            The set of SASL options for the bind request.
   * @param  controls           The set of controls to include in the request.
   *
   * @return  The SASL GSSAPI bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static GSSAPIBindRequest createGSSAPIBindRequest(
                      @Nullable final byte[] password,
                      final boolean promptForPassword,
                      @Nullable final CommandLineTool tool,
                      @NotNull final Map options,
                      @Nullable final Control[] controls)
          throws LDAPException
  {
    // The authID option is required.
    final String authID =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
    if (authID == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
                GSSAPIBindRequest.GSSAPI_MECHANISM_NAME));
    }
    final GSSAPIBindRequestProperties gssapiProperties =
         new GSSAPIBindRequestProperties(authID, password);

    // The authzID option is optional.
    gssapiProperties.setAuthorizationID(
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID)));

    // The configFile option is optional.
    gssapiProperties.setConfigFilePath(options.remove(
         StaticUtils.toLowerCase(SASL_OPTION_CONFIG_FILE)));

    // The debug option is optional.
    gssapiProperties.setEnableGSSAPIDebugging(getBooleanValue(options,
         SASL_OPTION_DEBUG, false));

    // The kdcAddress option is optional.
    gssapiProperties.setKDCAddress(options.remove(
         StaticUtils.toLowerCase(SASL_OPTION_KDC_ADDRESS)));

    // The protocol option is optional.
    final String protocol =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_PROTOCOL));
    if (protocol != null)
    {
      gssapiProperties.setServicePrincipalProtocol(protocol);
    }

    // The realm option is optional.
    gssapiProperties.setRealm(options.remove(
         StaticUtils.toLowerCase(SASL_OPTION_REALM)));

    // The QoP option is optional, and may contain multiple values that need to
    // be parsed.
    final String qopString =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_QOP));
    if (qopString != null)
    {
      gssapiProperties.setAllowedQoP(
           SASLQualityOfProtection.decodeQoPList(qopString));
    }

    // The renewTGT option is optional.
    gssapiProperties.setRenewTGT(getBooleanValue(options, SASL_OPTION_RENEW_TGT,
         false));

    // The requireCache option is optional.
    gssapiProperties.setRequireCachedCredentials(getBooleanValue(options,
         SASL_OPTION_REQUIRE_CACHE, false));

    // The ticketCache option is optional.
    gssapiProperties.setTicketCachePath(options.remove(
         StaticUtils.toLowerCase(SASL_OPTION_TICKET_CACHE_PATH)));

    // The useTicketCache option is optional.
    gssapiProperties.setUseTicketCache(getBooleanValue(options,
         SASL_OPTION_USE_TICKET_CACHE, true));

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         GSSAPIBindRequest.GSSAPI_MECHANISM_NAME);

    // A password must have been provided unless useTicketCache=true and
    // requireTicketCache=true.
    if (password == null)
    {
      if (! (gssapiProperties.useTicketCache() &&
           gssapiProperties.requireCachedCredentials()))
      {
        if (promptForPassword)
        {
          tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
          gssapiProperties.setPassword(PasswordReader.readPassword());
          tool.getOriginalOut().println();
        }
        else
        {
          throw new LDAPException(ResultCode.PARAM_ERROR,
               ERR_SASL_OPTION_GSSAPI_PASSWORD_REQUIRED.get());
        }
      }
    }

    return new GSSAPIBindRequest(gssapiProperties, controls);
  }



  /**
   * Creates a SASL OAUTHBEARER bind request using the provided password and
   * set of options.
   *
   * @param  password           The password to use for the bind request.
   * @param  promptForPassword  Indicates whether to interactively prompt for
   *                            the password if one is needed but none was
   *                            provided.
   * @param  tool               The command-line tool whose input and output
   *                            streams should be used when prompting for the
   *                            bind password.  It may be {@code null} only if
   *                            {@code promptForPassword} is {@code false}.
   * @param  options            The set of SASL options for the bind request.
   * @param  controls           The set of controls to include in the request.
   *
   * @return  The SASL OAUTHBEARER bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static OAUTHBEARERBindRequest createOAUTHBEARERBindRequest(
                      @Nullable final byte[] password,
                      final boolean promptForPassword,
                      @Nullable final CommandLineTool tool,
                      @NotNull final Map options,
                      @Nullable final Control[] controls)
          throws LDAPException
  {
    // The accessToken option wasn't declared as required, but we will either
    // require it to have been provided or we will interactively prompt for it.
    String accessToken =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_ACCESS_TOKEN));
    if (accessToken == null)
    {
      if (promptForPassword)
      {
        tool.getOriginalOut().print(
             INFO_SASL_TOOL_ENTER_OAUTHBEARER_ACCESS_TOKEN.get());
        accessToken = StaticUtils.toUTF8String(PasswordReader.readPassword());
        tool.getOriginalOut().println();
      }
      else
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_ACCESS_TOKEN,
                  OAUTHBEARERBindRequest.OAUTHBEARER_MECHANISM_NAME));
      }
    }

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         OAUTHBEARERBindRequest.OAUTHBEARER_MECHANISM_NAME);

    return new OAUTHBEARERBindRequest(accessToken, controls);
  }



  /**
   * Creates a SASL PLAIN bind request using the provided password and set of
   * options.
   *
   * @param  password           The password to use for the bind request.
   * @param  promptForPassword  Indicates whether to interactively prompt for
   *                            the password if one is needed but none was
   *                            provided.
   * @param  tool               The command-line tool whose input and output
   *                            streams should be used when prompting for the
   *                            bind password.  It may be {@code null} only if
   *                            {@code promptForPassword} is {@code false}.
   * @param  options            The set of SASL options for the bind request.
   * @param  controls           The set of controls to include in the request.
   *
   * @return  The SASL PLAIN bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static PLAINBindRequest createPLAINBindRequest(
                      @Nullable final byte[] password,
                      final boolean promptForPassword,
                      @Nullable final CommandLineTool tool,
                      @NotNull final Map options,
                      @Nullable final Control[] controls)
          throws LDAPException
  {
    final byte[] pw;
    if (password == null)
    {
      if (promptForPassword)
      {
        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
        pw = PasswordReader.readPassword();
        tool.getOriginalOut().println();
      }
      else
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
                  PLAINBindRequest.PLAIN_MECHANISM_NAME));
      }
    }
    else
    {
      pw = password;
    }

    // The authID option is required.
    final String authID =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
    if (authID == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
                PLAINBindRequest.PLAIN_MECHANISM_NAME));
    }

    // The authzID option is optional.
    final String authzID =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         PLAINBindRequest.PLAIN_MECHANISM_NAME);

    return new PLAINBindRequest(authID, authzID, pw, controls);
  }



  /**
   * Creates a SASL SCRAM-SHA-1 bind request using the provided password and
   * set of options.
   *
   * @param  password           The password to use for the bind request.
   * @param  promptForPassword  Indicates whether to interactively prompt for
   *                            the password if one is needed but none was
   *                            provided.
   * @param  tool               The command-line tool whose input and output
   *                            streams should be used when prompting for the
   *                            bind password.  It may be {@code null} only if
   *                            {@code promptForPassword} is {@code false}.
   * @param  options            The set of SASL options for the bind request.
   * @param  controls           The set of controls to include in the request.
   *
   * @return  The SASL SCRAM-SHA-1 bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static SCRAMSHA1BindRequest createSCRAMSHA1BindRequest(
                      @Nullable final byte[] password,
                      final boolean promptForPassword,
                      @Nullable final CommandLineTool tool,
                      @NotNull final Map options,
                      @Nullable final Control[] controls)
          throws LDAPException
  {
    final byte[] pw;
    if (password == null)
    {
      if (promptForPassword)
      {
        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
        pw = PasswordReader.readPassword();
        tool.getOriginalOut().println();
      }
      else
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
                  SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME));
      }
    }
    else
    {
      pw = password;
    }

    // The username option is required.
    final String username =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_USERNAME));
    if (username == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_USERNAME,
                SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME));
    }

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME);

    return new SCRAMSHA1BindRequest(username, pw, controls);
  }



  /**
   * Creates a SASL SCRAM-SHA-256 bind request using the provided password and
   * set of options.
   *
   * @param  password           The password to use for the bind request.
   * @param  promptForPassword  Indicates whether to interactively prompt for
   *                            the password if one is needed but none was
   *                            provided.
   * @param  tool               The command-line tool whose input and output
   *                            streams should be used when prompting for the
   *                            bind password.  It may be {@code null} only if
   *                            {@code promptForPassword} is {@code false}.
   * @param  options            The set of SASL options for the bind request.
   * @param  controls           The set of controls to include in the request.
   *
   * @return  The SASL SCRAM-SHA-256 bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static SCRAMSHA256BindRequest createSCRAMSHA256BindRequest(
                      @Nullable final byte[] password,
                      final boolean promptForPassword,
                      @Nullable final CommandLineTool tool,
                      @NotNull final Map options,
                      @Nullable final Control[] controls)
          throws LDAPException
  {
    final byte[] pw;
    if (password == null)
    {
      if (promptForPassword)
      {
        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
        pw = PasswordReader.readPassword();
        tool.getOriginalOut().println();
      }
      else
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
                  SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME));
      }
    }
    else
    {
      pw = password;
    }

    // The username option is required.
    final String username =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_USERNAME));
    if (username == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_USERNAME,
                SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME));
    }

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME);

    return new SCRAMSHA256BindRequest(username, pw, controls);
  }



  /**
   * Creates a SASL SCRAM-SHA-512 bind request using the provided password and
   * set of options.
   *
   * @param  password           The password to use for the bind request.
   * @param  promptForPassword  Indicates whether to interactively prompt for
   *                            the password if one is needed but none was
   *                            provided.
   * @param  tool               The command-line tool whose input and output
   *                            streams should be used when prompting for the
   *                            bind password.  It may be {@code null} only if
   *                            {@code promptForPassword} is {@code false}.
   * @param  options            The set of SASL options for the bind request.
   * @param  controls           The set of controls to include in the request.
   *
   * @return  The SASL SCRAM-SHA-512 bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static SCRAMSHA512BindRequest createSCRAMSHA512BindRequest(
                      @Nullable final byte[] password,
                      final boolean promptForPassword,
                      @Nullable final CommandLineTool tool,
                      @NotNull final Map options,
                      @Nullable final Control[] controls)
          throws LDAPException
  {
    final byte[] pw;
    if (password == null)
    {
      if (promptForPassword)
      {
        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
        pw = PasswordReader.readPassword();
        tool.getOriginalOut().println();
      }
      else
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
                  SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME));
      }
    }
    else
    {
      pw = password;
    }

    // The username option is required.
    final String username =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_USERNAME));
    if (username == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_USERNAME,
                SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME));
    }

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME);

    return new SCRAMSHA512BindRequest(username, pw, controls);
  }



  /**
   * Creates a SASL UNBOUNDID-CERTIFICATE-PLUS-PASSWORD bind request using the
   * provided set of options.
   *
   * @param  password  The password to use for the bind request.
   * @param  tool      The command-line tool whose input and output streams
   *                   should be used when prompting for the bind password.  It
   *                   may be {@code null} only if {@code promptForPassword} is
   *                   {@code false}.
   * @param  options   The set of SASL options for the bind request.
   * @param  controls  The set of controls to include in the request.
   *
   * @return  The SASL UNBOUNDID-CERTIFICATE-PLUS-PASSWORD bind request that was
   *          created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static UnboundIDCertificatePlusPasswordBindRequest
                      createUnboundIDCertificatePlusPasswordBindRequest(
                           @Nullable final byte[] password,
                           @Nullable final CommandLineTool tool,
                           @NotNull final Map options,
                           @Nullable final Control[] controls)
          throws LDAPException
  {
    final byte[] pw;
    if (password == null)
    {
      tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
      pw = PasswordReader.readPassword();
      tool.getOriginalOut().println();
    }
    else
    {
      pw = password;
    }

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         UnboundIDCertificatePlusPasswordBindRequest.
              UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME);

    return new UnboundIDCertificatePlusPasswordBindRequest(pw, controls);
  }



  /**
   * Creates a SASL UNBOUNDID-DELIVERED-OTP bind request using the provided
   * password and set of options.
   *
   * @param  password  The password to use for the bind request.
   * @param  options   The set of SASL options for the bind request.
   * @param  controls  The set of controls to include in the request.
   *
   * @return  The SASL UNBOUNDID-DELIVERED-OTP bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static UnboundIDDeliveredOTPBindRequest
                      createUNBOUNDIDDeliveredOTPBindRequest(
                           @Nullable final byte[] password,
                           @NotNull final Map options,
                           @Nullable final Control... controls)
          throws LDAPException
  {
    if (password != null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
                UnboundIDDeliveredOTPBindRequest.
                     UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME));
    }

    // The authID option is required.
    final String authID =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
    if (authID == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
                UnboundIDDeliveredOTPBindRequest.
                     UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME));
    }

    // The OTP option is required.
    final String otp = options.remove(StaticUtils.toLowerCase(SASL_OPTION_OTP));
    if (otp == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_OTP,
                UnboundIDDeliveredOTPBindRequest.
                     UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME));
    }

    // The authzID option is optional.
    final String authzID =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         UnboundIDDeliveredOTPBindRequest.
              UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME);

    return new UnboundIDDeliveredOTPBindRequest(authID, authzID, otp, controls);
  }



  /**
   * Creates a SASL UNBOUNDID-TOTP bind request using the provided password and
   * set of options.
   *
   * @param  password  The password to use for the bind request.
   * @param  tool      The command-line tool whose input and output streams
   *                   should be used when prompting for the bind password.  It
   *                   may be {@code null} only if {@code promptForPassword} is
   *                   {@code false}.
   * @param  options   The set of SASL options for the bind request.
   * @param  controls  The set of controls to include in the request.
   *
   * @return  The SASL UNBOUNDID-TOTP bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static SingleUseTOTPBindRequest createUNBOUNDIDTOTPBindRequest(
                      @Nullable final byte[] password,
                      @Nullable final CommandLineTool tool,
                      @NotNull final Map options,
                      @Nullable final Control... controls)
          throws LDAPException
  {
    // The authID option is required.
    final String authID =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
    if (authID == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
                UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME));
    }

    // The TOTP password option is required.
    final String totpPassword =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_TOTP_PASSWORD));
    if (totpPassword == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_TOTP_PASSWORD,
                UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME));
    }

    // The authzID option is optional.
    byte[] pwBytes = password;
    final String authzID =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));

    // The promptForStaticPassword option is optional.
    final String promptStr = options.remove(StaticUtils.toLowerCase(
         SASL_OPTION_PROMPT_FOR_STATIC_PW));
    if (promptStr != null)
    {
      if (promptStr.equalsIgnoreCase("true"))
      {
        if (pwBytes == null)
        {
          tool.getOriginalOut().print(INFO_SASL_ENTER_STATIC_PW.get());
          pwBytes = PasswordReader.readPassword();
          tool.getOriginalOut().println();
        }
        else
        {
          throw new LDAPException(ResultCode.PARAM_ERROR,
               ERR_SASL_PROMPT_FOR_PROVIDED_PW.get(
                    SASL_OPTION_PROMPT_FOR_STATIC_PW));
        }
      }
      else if (! promptStr.equalsIgnoreCase("false"))
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_PROMPT_FOR_STATIC_PW_BAD_VALUE.get(
                  SASL_OPTION_PROMPT_FOR_STATIC_PW));
      }
    }

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME);

    return new SingleUseTOTPBindRequest(authID, authzID, totpPassword, pwBytes,
         controls);
  }



  /**
   * Creates a SASL UNBOUNDID-YUBIKEY-OTP bind request using the provided
   * password and set of options.
   *
   * @param  password  The password to use for the bind request.
   * @param  tool      The command-line tool whose input and output streams
   *                   should be used when prompting for the bind password.  It
   *                   may be {@code null} only if {@code promptForPassword} is
   *                   {@code false}.
   * @param  options   The set of SASL options for the bind request.
   * @param  controls  The set of controls to include in the request.
   *
   * @return  The SASL UNBOUNDID-YUBIKEY-OTP bind request that was created.
   *
   * @throws  LDAPException  If a problem is encountered while trying to create
   *                         the SASL bind request.
   */
  @NotNull()
  private static UnboundIDYubiKeyOTPBindRequest
                      createUNBOUNDIDYUBIKEYOTPBindRequest(
                           @Nullable final byte[] password,
                           @Nullable final CommandLineTool tool,
                           @NotNull final Map options,
                           @Nullable final Control... controls)
          throws LDAPException
  {
    // The authID option is required.
    final String authID =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
    if (authID == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
                UnboundIDYubiKeyOTPBindRequest.
                     UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
    }

    // The otp option is required.
    final String otp = options.remove(StaticUtils.toLowerCase(SASL_OPTION_OTP));
    if (otp == null)
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_OTP,
                UnboundIDYubiKeyOTPBindRequest.
                     UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
    }

    // The authzID option is optional.
    final String authzID =
         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));

    // The promptForStaticPassword option is optional.
    byte[] pwBytes = password;
    final String promptStr = options.remove(StaticUtils.toLowerCase(
         SASL_OPTION_PROMPT_FOR_STATIC_PW));
    if (promptStr != null)
    {
      if (promptStr.equalsIgnoreCase("true"))
      {
        if (pwBytes == null)
        {
          tool.getOriginalOut().print(INFO_SASL_ENTER_STATIC_PW.get());
          pwBytes = PasswordReader.readPassword();
          tool.getOriginalOut().println();
        }
        else
        {
          throw new LDAPException(ResultCode.PARAM_ERROR,
               ERR_SASL_PROMPT_FOR_PROVIDED_PW.get(
                    SASL_OPTION_PROMPT_FOR_STATIC_PW));
        }
      }
      else if (! promptStr.equalsIgnoreCase("false"))
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_PROMPT_FOR_STATIC_PW_BAD_VALUE.get(
                  SASL_OPTION_PROMPT_FOR_STATIC_PW));
      }
    }

    // Ensure no unsupported options were provided.
    ensureNoUnsupportedOptions(options,
         UnboundIDYubiKeyOTPBindRequest.UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME);

    return new UnboundIDYubiKeyOTPBindRequest(authID, authzID, pwBytes, otp,
         controls);
  }



  /**
   * Parses the provided list of SASL options.
   *
   * @param  options  The list of options to be parsed.
   *
   * @return  A map with the parsed set of options.
   *
   * @throws  LDAPException  If a problem is encountered while parsing options.
   */
  @NotNull()
  private static Map
                      parseOptions(@Nullable final List options)
          throws LDAPException
  {
    if (options == null)
    {
      return new HashMap<>(0);
    }

    final HashMap m =
         new HashMap<>(StaticUtils.computeMapCapacity(options.size()));
    for (final String s : options)
    {
      final int equalPos = s.indexOf('=');
      if (equalPos < 0)
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_OPTION_MISSING_EQUAL.get(s));
      }
      else if (equalPos == 0)
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_OPTION_STARTS_WITH_EQUAL.get(s));
      }

      final String name = s.substring(0, equalPos);
      final String value = s.substring(equalPos + 1);
      if (m.put(StaticUtils.toLowerCase(name), value) != null)
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_OPTION_NOT_MULTI_VALUED.get(name));
      }
    }

    return m;
  }



  /**
   * Ensures that the provided map is empty, and will throw an exception if it
   * isn't.  This method is intended for internal use only.
   *
   * @param  options    The map of options to ensure is empty.
   * @param  mechanism  The associated SASL mechanism.
   *
   * @throws  LDAPException  If the map of SASL options is not empty.
   */
  @InternalUseOnly()
  public static void ensureNoUnsupportedOptions(
                          @NotNull final Map options,
                          @NotNull final String mechanism)
          throws LDAPException
  {
    if (! options.isEmpty())
    {
      for (final String s : options.keySet())
      {
        throw new LDAPException(ResultCode.PARAM_ERROR,
             ERR_SASL_OPTION_UNSUPPORTED_FOR_MECH.get(s,mechanism));
      }
    }
  }



  /**
   * Retrieves the value of the specified option and parses it as a boolean.
   * Values of "true", "t", "yes", "y", "on", and "1" will be treated as
   * {@code true}.  Values of "false", "f", "no", "n", "off", and "0" will be
   * treated as {@code false}.
   *
   * @param  m  The map from which to retrieve the option.  It must not be
   *            {@code null}.
   * @param  o  The name of the option to examine.
   * @param  d  The default value to use if the given option was not provided.
   *
   * @return  The parsed boolean value.
   *
   * @throws  LDAPException  If the option value cannot be parsed as a boolean.
   */
  static boolean getBooleanValue(@NotNull final Map m,
                                 @NotNull final String o, final boolean d)
         throws LDAPException
  {
    final String s =
         StaticUtils.toLowerCase(m.remove(StaticUtils.toLowerCase(o)));
    if (s == null)
    {
      return d;
    }
    else if (s.equals("true") ||
             s.equals("t") ||
             s.equals("yes") ||
             s.equals("y") ||
             s.equals("on") ||
             s.equals("1"))
    {
      return true;
    }
    else if (s.equals("false") ||
             s.equals("f") ||
             s.equals("no") ||
             s.equals("n") ||
             s.equals("off") ||
             s.equals("0"))
    {
      return false;
    }
    else
    {
      throw new LDAPException(ResultCode.PARAM_ERROR,
           ERR_SASL_OPTION_MALFORMED_BOOLEAN_VALUE.get(o));
    }
  }



  /**
   * Retrieves a string representation of the SASL usage information.  This will
   * include the supported SASL mechanisms and the properties that may be used
   * with each.
   *
   * @param  maxWidth  The maximum line width to use for the output.  If this is
   *                   less than or equal to zero, then no wrapping will be
   *                   performed.
   *
   * @return  A string representation of the usage information.
   */
  @NotNull()
  public static String getUsageString(final int maxWidth)
  {
    return getUsageString(null, maxWidth);
  }



  /**
   * Retrieves a string representation of the SASL usage information.  This will
   * include the supported SASL mechanisms and the properties that may be used
   * with each.
   *
   * @param  mechanism  The name of the SASL mechanism for which to obtain usage
   *                    information  It may be {@code null} if usage should be
   *                    displayed for all available mechamisms.
   * @param  maxWidth   The maximum line width to use for the output.  If this
   *                    is less than or equal to zero, then no wrapping will be
   *                    performed.
   *
   * @return  A string representation of the usage information.
   */
  @NotNull()
  public static String getUsageString(@Nullable final String mechanism,
                                      final int maxWidth)
  {
    final StringBuilder buffer = new StringBuilder();

    for (final String line : getUsage(mechanism, maxWidth))
    {
      buffer.append(line);
      buffer.append(StaticUtils.EOL);
    }

    return buffer.toString();
  }



  /**
   * Retrieves lines that make up the SASL usage information, optionally
   * wrapping long lines.
   *
   * @param  maxWidth  The maximum line width to use for the output.  If this is
   *                   less than or equal to zero, then no wrapping will be
   *                   performed.
   *
   * @return  The lines that make up the SASL usage information.
   */
  @NotNull()
  public static List getUsage(final int maxWidth)
  {
    return getUsage(null, maxWidth);
  }



  /**
   * Retrieves lines that make up the SASL usage information, optionally
   * wrapping long lines.
   *
   * @param  mechanism  The name of the SASL mechanism for which to obtain usage
   *                    information  It may be {@code null} if usage should be
   *                    displayed for all available mechamisms.
   * @param  maxWidth   The maximum line width to use for the output.  If this
   *                    is less than or equal to zero, then no wrapping will be
   *                    performed.
   *
   * @return  The lines that make up the SASL usage information.
   */
  @NotNull()
  public static List getUsage(@Nullable final String mechanism,
                                      final int maxWidth)
  {
    final ArrayList lines = new ArrayList<>(100);

    boolean first = true;
    for (final SASLMechanismInfo i : getSupportedSASLMechanisms())
    {
      if ((mechanism != null) && (! i.getName().equalsIgnoreCase(mechanism)))
      {
        continue;
      }

      if (first)
      {
        first = false;
      }
      else
      {
        lines.add("");
        lines.add("");
      }

      lines.addAll(
           StaticUtils.wrapLine(INFO_SASL_HELP_MECHANISM.get(i.getName()),
                maxWidth));
      lines.add("");

      for (final String line :
           StaticUtils.wrapLine(i.getDescription(), maxWidth - 4))
      {
        lines.add("  " + line);
      }
      lines.add("");

      for (final String line :
           StaticUtils.wrapLine(INFO_SASL_HELP_MECHANISM_OPTIONS.get(
                i.getName()), maxWidth - 4))
      {
        lines.add("  " + line);
      }

      if (i.acceptsPassword())
      {
        lines.add("");
        if (i.requiresPassword())
        {
          for (final String line :
               StaticUtils.wrapLine(INFO_SASL_HELP_PASSWORD_REQUIRED.get(
                    i.getName()), maxWidth - 4))
          {
            lines.add("  " + line);
          }
        }
        else
        {
          for (final String line :
               StaticUtils.wrapLine(INFO_SASL_HELP_PASSWORD_OPTIONAL.get(
                    i.getName()), maxWidth - 4))
          {
            lines.add("  " + line);
          }
        }
      }

      for (final SASLOption o : i.getOptions())
      {
        lines.add("");
        lines.add("  * " + o.getName());
        for (final String line :
             StaticUtils.wrapLine(o.getDescription(), maxWidth - 14))
        {
          lines.add("       " + line);
        }
      }
    }

    if ((mechanism != null) && lines.isEmpty())
    {
      return getUsage(null, maxWidth);
    }


    return lines;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy