001/*
002 * Copyright 2011-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2011-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2011-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util;
037
038
039
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.HashMap;
043import java.util.List;
044import java.util.Map;
045import java.util.TreeMap;
046
047import com.unboundid.ldap.sdk.ANONYMOUSBindRequest;
048import com.unboundid.ldap.sdk.Control;
049import com.unboundid.ldap.sdk.CRAMMD5BindRequest;
050import com.unboundid.ldap.sdk.DIGESTMD5BindRequest;
051import com.unboundid.ldap.sdk.DIGESTMD5BindRequestProperties;
052import com.unboundid.ldap.sdk.EXTERNALBindRequest;
053import com.unboundid.ldap.sdk.GSSAPIBindRequest;
054import com.unboundid.ldap.sdk.GSSAPIBindRequestProperties;
055import com.unboundid.ldap.sdk.LDAPException;
056import com.unboundid.ldap.sdk.OAUTHBEARERBindRequest;
057import com.unboundid.ldap.sdk.PLAINBindRequest;
058import com.unboundid.ldap.sdk.ResultCode;
059import com.unboundid.ldap.sdk.SASLBindRequest;
060import com.unboundid.ldap.sdk.SASLQualityOfProtection;
061import com.unboundid.ldap.sdk.SCRAMSHA1BindRequest;
062import com.unboundid.ldap.sdk.SCRAMSHA256BindRequest;
063import com.unboundid.ldap.sdk.SCRAMSHA512BindRequest;
064import com.unboundid.ldap.sdk.unboundidds.SingleUseTOTPBindRequest;
065import com.unboundid.ldap.sdk.unboundidds.
066            UnboundIDCertificatePlusPasswordBindRequest;
067import com.unboundid.ldap.sdk.unboundidds.UnboundIDDeliveredOTPBindRequest;
068import com.unboundid.ldap.sdk.unboundidds.UnboundIDTOTPBindRequest;
069import com.unboundid.ldap.sdk.unboundidds.UnboundIDYubiKeyOTPBindRequest;
070
071import static com.unboundid.util.UtilityMessages.*;
072
073
074
075/**
076 * This class provides a utility that may be used to help process SASL bind
077 * operations using the LDAP SDK.
078 */
079@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
080public final class SASLUtils
081{
082  /**
083   * The name of the SASL option that specifies the access token.  It may be
084   * used in conjunction with the OAUTHBEARER mechanism.
085   */
086  @NotNull public static final String SASL_OPTION_ACCESS_TOKEN = "accessToken";
087
088
089
090  /**
091   * The name of the SASL option that specifies the authentication ID.  It may
092   * be used in conjunction with the CRAM-MD5, DIGEST-MD5, GSSAPI, and PLAIN
093   * mechanisms.
094   */
095  @NotNull public static final String SASL_OPTION_AUTH_ID = "authID";
096
097
098
099  /**
100   * The name of the SASL option that specifies the authorization ID.  It may
101   * be used in conjunction with the DIGEST-MD5, GSSAPI, and PLAIN mechanisms.
102   */
103  @NotNull public static final String SASL_OPTION_AUTHZ_ID = "authzID";
104
105
106
107  /**
108   * The name of the SASL option that specifies the path to the JAAS config
109   * file.  It may be used in conjunction with the GSSAPI mechanism.
110   */
111  @NotNull public static final String SASL_OPTION_CONFIG_FILE = "configFile";
112
113
114
115  /**
116   * The name of the SASL option that indicates whether debugging should be
117   * enabled.  It may be used in conjunction with the GSSAPI mechanism.
118   */
119  @NotNull public static final String SASL_OPTION_DEBUG = "debug";
120
121
122
123  /**
124   * The name of the SASL option that specifies the KDC address.  It may be used
125   * in conjunction with the GSSAPI mechanism.
126   */
127  @NotNull public static final String SASL_OPTION_KDC_ADDRESS = "kdcAddress";
128
129
130
131  /**
132   * The name of the SASL option that specifies the desired SASL mechanism to
133   * use to authenticate to the server.
134   */
135  @NotNull public static final String SASL_OPTION_MECHANISM = "mech";
136
137
138
139  /**
140   * The name of the SASL option that specifies a one-time password.  It may be
141   * used in conjunction with the UNBOUNDID-DELIVERED-OTP and
142   * UNBOUNDID-YUBIKEY-OTP mechanisms.
143   */
144  @NotNull public static final String SASL_OPTION_OTP = "otp";
145
146
147
148  /**
149   * The name of the SASL option that may be used to indicate whether to
150   * prompt for a static password.  It may be used in conjunction with the
151   * UNBOUNDID-TOTP and UNBOUNDID-YUBIKEY-OTP mechanisms.
152   */
153  @NotNull public static final String SASL_OPTION_PROMPT_FOR_STATIC_PW =
154       "promptForStaticPassword";
155
156
157
158  /**
159   * The name of the SASL option that specifies the GSSAPI service principal
160   * protocol.  It may be used in conjunction with the GSSAPI mechanism.
161   */
162  @NotNull public static final String SASL_OPTION_PROTOCOL = "protocol";
163
164
165
166  /**
167   * The name of the SASL option that specifies the quality of protection that
168   * should be used for communication that occurs after the authentication has
169   * completed.
170   */
171  @NotNull public static final String SASL_OPTION_QOP = "qop";
172
173
174
175  /**
176   * The name of the SASL option that specifies the realm name.  It may be used
177   * in conjunction with the DIGEST-MD5 and GSSAPI mechanisms.
178   */
179  @NotNull public static final String SASL_OPTION_REALM = "realm";
180
181
182
183  /**
184   * The name of the SASL option that indicates whether to require an existing
185   * Kerberos session from the ticket cache.  It may be used in conjunction with
186   * the GSSAPI mechanism.
187   */
188  @NotNull public static final String SASL_OPTION_REQUIRE_CACHE =
189       "requireCache";
190
191
192
193  /**
194   * The name of the SASL option that indicates whether to attempt to renew the
195   * Kerberos TGT for an existing session.  It may be used in conjunction with
196   * the GSSAPI mechanism.
197   */
198  @NotNull public static final String SASL_OPTION_RENEW_TGT = "renewTGT";
199
200
201
202  /**
203   * The name of the SASL option that specifies the path to the Kerberos ticket
204   * cache to use.  It may be used in conjunction with the GSSAPI mechanism.
205   */
206  @NotNull public static final String SASL_OPTION_TICKET_CACHE_PATH =
207       "ticketCache";
208
209
210
211  /**
212   * The name of the SASL option that specifies the TOTP authentication code.
213   * It may be used in conjunction with the UNBOUNDID-TOTP mechanism.
214   */
215  @NotNull public static final String SASL_OPTION_TOTP_PASSWORD =
216       "totpPassword";
217
218
219
220  /**
221   * The name of the SASL option that specifies the trace string.  It may be
222   * used in conjunction with the ANONYMOUS mechanism.
223   */
224  @NotNull public static final String SASL_OPTION_TRACE = "trace";
225
226
227
228  /**
229   * The name of the SASL option that specifies the username.  It may be
230   * used in conjunction with the SCRAM-SHA-1, SCRAM-SHA-256, and SCRAM-SHA-512
231   * mechanisms.
232   */
233  @NotNull public static final String SASL_OPTION_USERNAME = "username";
234
235
236
237  /**
238   * The name of the SASL option that specifies whether to use a Kerberos ticket
239   * cache.  It may be used in conjunction with the GSSAPI mechanism.
240   */
241  @NotNull public static final String SASL_OPTION_USE_TICKET_CACHE =
242       "useTicketCache";
243
244
245
246  /**
247   * A map with information about all supported SASL mechanisms, mapped from
248   * lowercase mechanism name to an object with information about that
249   * mechanism.
250   */
251  @NotNull private static final Map<String,SASLMechanismInfo> SASL_MECHANISMS;
252
253
254
255  static
256  {
257    final TreeMap<String,SASLMechanismInfo> m = new TreeMap<>();
258
259    m.put(
260         StaticUtils.toLowerCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME),
261         new SASLMechanismInfo(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME,
262              INFO_SASL_ANONYMOUS_DESCRIPTION.get(), false, false,
263              new SASLOption(SASL_OPTION_TRACE,
264                   INFO_SASL_ANONYMOUS_OPTION_TRACE.get(), false, false)));
265
266    m.put(StaticUtils.toLowerCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME),
267         new SASLMechanismInfo(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME,
268              INFO_SASL_CRAM_MD5_DESCRIPTION.get(), true, true,
269              new SASLOption(SASL_OPTION_AUTH_ID,
270                   INFO_SASL_CRAM_MD5_OPTION_AUTH_ID.get(), true, false)));
271
272    m.put(
273         StaticUtils.toLowerCase(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME),
274         new SASLMechanismInfo(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME,
275              INFO_SASL_DIGEST_MD5_DESCRIPTION.get(), true, true,
276              new SASLOption(SASL_OPTION_AUTH_ID,
277                   INFO_SASL_DIGEST_MD5_OPTION_AUTH_ID.get(), true, false),
278              new SASLOption(SASL_OPTION_AUTHZ_ID,
279                   INFO_SASL_DIGEST_MD5_OPTION_AUTHZ_ID.get(), false, false),
280              new SASLOption(SASL_OPTION_REALM,
281                   INFO_SASL_DIGEST_MD5_OPTION_REALM.get(), false, false),
282              new SASLOption(SASL_OPTION_QOP,
283                   INFO_SASL_DIGEST_MD5_OPTION_QOP.get(), false, false)));
284
285    m.put(StaticUtils.toLowerCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME),
286         new SASLMechanismInfo(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME,
287              INFO_SASL_EXTERNAL_DESCRIPTION.get(), false, false));
288
289    m.put(StaticUtils.toLowerCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME),
290         new SASLMechanismInfo(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME,
291              INFO_SASL_GSSAPI_DESCRIPTION.get(), true, false,
292              new SASLOption(SASL_OPTION_AUTH_ID,
293                   INFO_SASL_GSSAPI_OPTION_AUTH_ID.get(), true, false),
294              new SASLOption(SASL_OPTION_AUTHZ_ID,
295                   INFO_SASL_GSSAPI_OPTION_AUTHZ_ID.get(), false, false),
296              new SASLOption(SASL_OPTION_CONFIG_FILE,
297                   INFO_SASL_GSSAPI_OPTION_CONFIG_FILE.get(), false, false),
298              new SASLOption(SASL_OPTION_DEBUG,
299                   INFO_SASL_GSSAPI_OPTION_DEBUG.get(), false, false),
300              new SASLOption(SASL_OPTION_KDC_ADDRESS,
301                   INFO_SASL_GSSAPI_OPTION_KDC_ADDRESS.get(), false, false),
302              new SASLOption(SASL_OPTION_PROTOCOL,
303                   INFO_SASL_GSSAPI_OPTION_PROTOCOL.get(), false, false),
304              new SASLOption(SASL_OPTION_REALM,
305                   INFO_SASL_GSSAPI_OPTION_REALM.get(), false, false),
306              new SASLOption(SASL_OPTION_QOP,
307                   INFO_SASL_GSSAPI_OPTION_QOP.get(), false, false),
308              new SASLOption(SASL_OPTION_RENEW_TGT,
309                   INFO_SASL_GSSAPI_OPTION_RENEW_TGT.get(), false, false),
310              new SASLOption(SASL_OPTION_REQUIRE_CACHE,
311                   INFO_SASL_GSSAPI_OPTION_REQUIRE_TICKET_CACHE.get(), false,
312                   false),
313              new SASLOption(SASL_OPTION_TICKET_CACHE_PATH,
314                   INFO_SASL_GSSAPI_OPTION_TICKET_CACHE.get(), false, false),
315              new SASLOption(SASL_OPTION_USE_TICKET_CACHE,
316                   INFO_SASL_GSSAPI_OPTION_USE_TICKET_CACHE.get(), false,
317                   false)));
318
319    m.put(StaticUtils.toLowerCase(
320         OAUTHBEARERBindRequest.OAUTHBEARER_MECHANISM_NAME),
321         new SASLMechanismInfo(
322              OAUTHBEARERBindRequest.OAUTHBEARER_MECHANISM_NAME,
323              INFO_SASL_PLAIN_DESCRIPTION.get(), false, false,
324              new SASLOption(SASL_OPTION_ACCESS_TOKEN,
325                   INFO_SASL_OAUTHBEARER_OPTION_ACCESS_TOKEN.get(), false,
326                   false)));
327
328    m.put(StaticUtils.toLowerCase(PLAINBindRequest.PLAIN_MECHANISM_NAME),
329         new SASLMechanismInfo(PLAINBindRequest.PLAIN_MECHANISM_NAME,
330              INFO_SASL_PLAIN_DESCRIPTION.get(), true, true,
331              new SASLOption(SASL_OPTION_AUTH_ID,
332                   INFO_SASL_PLAIN_OPTION_AUTH_ID.get(), true, false),
333              new SASLOption(SASL_OPTION_AUTHZ_ID,
334                   INFO_SASL_PLAIN_OPTION_AUTHZ_ID.get(), false, false)));
335
336    m.put(
337         StaticUtils.toLowerCase(
338              SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME),
339         new SASLMechanismInfo(SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME,
340              INFO_SASL_SCRAM_SHA_1_DESCRIPTION.get(), true, true,
341              new SASLOption(SASL_OPTION_USERNAME,
342                   INFO_SASL_SCRAM_OPTION_USERNAME.get(), true, false)));
343
344    m.put(
345         StaticUtils.toLowerCase(
346              SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME),
347         new SASLMechanismInfo(
348              SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME,
349              INFO_SASL_SCRAM_SHA_256_DESCRIPTION.get(), true, true,
350              new SASLOption(SASL_OPTION_USERNAME,
351                   INFO_SASL_SCRAM_OPTION_USERNAME.get(), true, false)));
352
353    m.put(
354         StaticUtils.toLowerCase(
355              SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME),
356         new SASLMechanismInfo(
357              SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME,
358              INFO_SASL_SCRAM_SHA_512_DESCRIPTION.get(), true, true,
359              new SASLOption(SASL_OPTION_USERNAME,
360                   INFO_SASL_SCRAM_OPTION_USERNAME.get(), true, false)));
361
362    m.put(StaticUtils.toLowerCase(
363         UnboundIDCertificatePlusPasswordBindRequest.
364              UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME),
365         new SASLMechanismInfo(
366              UnboundIDCertificatePlusPasswordBindRequest.
367                   UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME,
368              INFO_SASL_UNBOUNDID_CERT_PLUS_PASSWORD_DESCRIPTION.get(), true,
369              true));
370
371    m.put(
372         StaticUtils.toLowerCase(
373              UnboundIDDeliveredOTPBindRequest.
374                   UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME),
375         new SASLMechanismInfo(
376              UnboundIDDeliveredOTPBindRequest.
377                   UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME,
378              INFO_SASL_UNBOUNDID_DELIVERED_OTP_DESCRIPTION.get(), false, false,
379              new SASLOption(SASL_OPTION_AUTH_ID,
380                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTH_ID.get(), true, false),
381              new SASLOption(SASL_OPTION_AUTHZ_ID,
382                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTHZ_ID.get(), false,
383                   false),
384              new SASLOption(SASL_OPTION_OTP,
385                   INFO_SASL_UNBOUNDID_DELIVERED_OTP_OPTION_OTP.get(), true,
386                   false)));
387
388    m.put(
389         StaticUtils.toLowerCase(
390              UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME),
391         new SASLMechanismInfo(
392              UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME,
393              INFO_SASL_UNBOUNDID_TOTP_DESCRIPTION.get(), true, false,
394              new SASLOption(SASL_OPTION_AUTH_ID,
395                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTH_ID.get(), true, false),
396              new SASLOption(SASL_OPTION_AUTHZ_ID,
397                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTHZ_ID.get(), false,
398                   false),
399              new SASLOption(SASL_OPTION_PROMPT_FOR_STATIC_PW,
400                   INFO_SASL_UNBOUNDID_TOTP_OPTION_PROMPT_FOR_PW.get(), false,
401                   false),
402              new SASLOption(SASL_OPTION_TOTP_PASSWORD,
403                   INFO_SASL_UNBOUNDID_TOTP_OPTION_TOTP_PASSWORD.get(), true,
404                   false)));
405
406    m.put(
407         StaticUtils.toLowerCase(
408              UnboundIDYubiKeyOTPBindRequest.
409                   UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME),
410         new SASLMechanismInfo(
411              UnboundIDYubiKeyOTPBindRequest.
412                   UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME,
413              INFO_SASL_UNBOUNDID_YUBIKEY_OTP_DESCRIPTION.get(), true, false,
414              new SASLOption(SASL_OPTION_AUTH_ID,
415                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_AUTH_ID.get(), true,
416                   false),
417              new SASLOption(SASL_OPTION_AUTHZ_ID,
418                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_AUTHZ_ID.get(), false,
419                   false),
420              new SASLOption(SASL_OPTION_OTP,
421                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_OTP.get(), true,
422                   false),
423              new SASLOption(SASL_OPTION_PROMPT_FOR_STATIC_PW,
424                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_PROMPT_FOR_PW.get(),
425                   false, false)));
426
427    SASL_MECHANISMS = Collections.unmodifiableMap(m);
428  }
429
430
431
432  /**
433   * Prevent this utility class from being instantiated.
434   */
435  private SASLUtils()
436  {
437    // No implementation required.
438  }
439
440
441
442  /**
443   * Retrieves information about the SASL mechanisms supported for use by this
444   * class.
445   *
446   * @return  Information about the SASL mechanisms supported for use by this
447   *          class.
448   */
449  @NotNull()
450  public static List<SASLMechanismInfo> getSupportedSASLMechanisms()
451  {
452    return Collections.unmodifiableList(
453         new ArrayList<>(SASL_MECHANISMS.values()));
454  }
455
456
457
458  /**
459   * Retrieves information about the specified SASL mechanism.
460   *
461   * @param  mechanism  The name of the SASL mechanism for which to retrieve
462   *                    information.  It will be treated in a case-insensitive
463   *                    manner.
464   *
465   * @return  Information about the requested SASL mechanism, or {@code null} if
466   *          no information about the specified mechanism is available.
467   */
468  @Nullable()
469  public static SASLMechanismInfo getSASLMechanismInfo(
470                                       @NotNull final String mechanism)
471  {
472    return SASL_MECHANISMS.get(StaticUtils.toLowerCase(mechanism));
473  }
474
475
476
477  /**
478   * Creates a new SASL bind request using the provided information.
479   *
480   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
481   *                    SASL mechanisms, this should be {@code null}, since the
482   *                    identity of the target user should be specified in some
483   *                    other way (e.g., via an "authID" SASL option).
484   * @param  password   The password to use for the SASL bind request.  It may
485   *                    be {@code null} if no password is required for the
486   *                    desired SASL mechanism.
487   * @param  mechanism  The name of the SASL mechanism to use.  It may be
488   *                    {@code null} if the provided set of options contains a
489   *                    "mech" option to specify the desired SASL option.
490   * @param  options    The set of SASL options to use when creating the bind
491   *                    request, in the form "name=value".  It may be
492   *                    {@code null} or empty if no SASL options are needed and
493   *                    a value was provided for the {@code mechanism} argument.
494   *                    If the set of SASL options includes a "mech" option,
495   *                    then the {@code mechanism} argument must be {@code null}
496   *                    or have a value that matches the value of the "mech"
497   *                    SASL option.
498   *
499   * @return  The SASL bind request created using the provided information.
500   *
501   * @throws  LDAPException  If a problem is encountered while trying to create
502   *                         the SASL bind request.
503   */
504  @NotNull()
505  public static SASLBindRequest createBindRequest(@Nullable final String bindDN,
506                                     @Nullable final String password,
507                                     @Nullable final String mechanism,
508                                     @Nullable final String... options)
509         throws LDAPException
510  {
511    return createBindRequest(bindDN,
512         (password == null ? null : StaticUtils.getBytes(password)), mechanism,
513         StaticUtils.toList(options));
514  }
515
516
517
518  /**
519   * Creates a new SASL bind request using the provided information.
520   *
521   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
522   *                    SASL mechanisms, this should be {@code null}, since the
523   *                    identity of the target user should be specified in some
524   *                    other way (e.g., via an "authID" SASL option).
525   * @param  password   The password to use for the SASL bind request.  It may
526   *                    be {@code null} if no password is required for the
527   *                    desired SASL mechanism.
528   * @param  mechanism  The name of the SASL mechanism to use.  It may be
529   *                    {@code null} if the provided set of options contains a
530   *                    "mech" option to specify the desired SASL option.
531   * @param  options    The set of SASL options to use when creating the bind
532   *                    request, in the form "name=value".  It may be
533   *                    {@code null} or empty if no SASL options are needed and
534   *                    a value was provided for the {@code mechanism} argument.
535   *                    If the set of SASL options includes a "mech" option,
536   *                    then the {@code mechanism} argument must be {@code null}
537   *                    or have a value that matches the value of the "mech"
538   *                    SASL option.
539   * @param  controls   The set of controls to include in the request.
540   *
541   * @return  The SASL bind request created using the provided information.
542   *
543   * @throws  LDAPException  If a problem is encountered while trying to create
544   *                         the SASL bind request.
545   */
546  @NotNull()
547  public static SASLBindRequest createBindRequest(@Nullable final String bindDN,
548                                     @Nullable final String password,
549                                     @Nullable final String mechanism,
550                                     @Nullable final List<String> options,
551                                     @Nullable final Control... controls)
552         throws LDAPException
553  {
554    return createBindRequest(bindDN,
555         (password == null
556              ? null
557              : StaticUtils.getBytes(password)), mechanism, options,
558         controls);
559  }
560
561
562
563  /**
564   * Creates a new SASL bind request using the provided information.
565   *
566   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
567   *                    SASL mechanisms, this should be {@code null}, since the
568   *                    identity of the target user should be specified in some
569   *                    other way (e.g., via an "authID" SASL option).
570   * @param  password   The password to use for the SASL bind request.  It may
571   *                    be {@code null} if no password is required for the
572   *                    desired SASL mechanism.
573   * @param  mechanism  The name of the SASL mechanism to use.  It may be
574   *                    {@code null} if the provided set of options contains a
575   *                    "mech" option to specify the desired SASL option.
576   * @param  options    The set of SASL options to use when creating the bind
577   *                    request, in the form "name=value".  It may be
578   *                    {@code null} or empty if no SASL options are needed and
579   *                    a value was provided for the {@code mechanism} argument.
580   *                    If the set of SASL options includes a "mech" option,
581   *                    then the {@code mechanism} argument must be {@code null}
582   *                    or have a value that matches the value of the "mech"
583   *                    SASL option.
584   *
585   * @return  The SASL bind request created using the provided information.
586   *
587   * @throws  LDAPException  If a problem is encountered while trying to create
588   *                         the SASL bind request.
589   */
590  @NotNull()
591  public static SASLBindRequest createBindRequest(@Nullable final String bindDN,
592                                     @Nullable final byte[] password,
593                                     @Nullable final String mechanism,
594                                     @Nullable final String... options)
595         throws LDAPException
596  {
597    return createBindRequest(bindDN, password, mechanism,
598         StaticUtils.toList(options));
599  }
600
601
602
603  /**
604   * Creates a new SASL bind request using the provided information.
605   *
606   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
607   *                    SASL mechanisms, this should be {@code null}, since the
608   *                    identity of the target user should be specified in some
609   *                    other way (e.g., via an "authID" SASL option).
610   * @param  password   The password to use for the SASL bind request.  It may
611   *                    be {@code null} if no password is required for the
612   *                    desired SASL mechanism.
613   * @param  mechanism  The name of the SASL mechanism to use.  It may be
614   *                    {@code null} if the provided set of options contains a
615   *                    "mech" option to specify the desired SASL option.
616   * @param  options    The set of SASL options to use when creating the bind
617   *                    request, in the form "name=value".  It may be
618   *                    {@code null} or empty if no SASL options are needed and
619   *                    a value was provided for the {@code mechanism} argument.
620   *                    If the set of SASL options includes a "mech" option,
621   *                    then the {@code mechanism} argument must be {@code null}
622   *                    or have a value that matches the value of the "mech"
623   *                    SASL option.
624   * @param  controls   The set of controls to include in the request.
625   *
626   * @return  The SASL bind request created using the provided information.
627   *
628   * @throws  LDAPException  If a problem is encountered while trying to create
629   *                         the SASL bind request.
630   */
631  @NotNull()
632  public static SASLBindRequest createBindRequest(@Nullable final String bindDN,
633                                     @Nullable final byte[] password,
634                                     @Nullable final String mechanism,
635                                     @Nullable final List<String> options,
636                                     @Nullable final Control... controls)
637         throws LDAPException
638  {
639    return createBindRequest(bindDN, password, false, null, mechanism, options,
640         controls);
641  }
642
643
644
645  /**
646   * Creates a new SASL bind request using the provided information.
647   *
648   * @param  bindDN             The bind DN to use for the SASL bind request.
649   *                            For most SASL mechanisms, this should be
650   *                            {@code null}, since the identity of the target
651   *                            user should be specified in some other way
652   *                            (e.g., via an "authID" SASL option).
653   * @param  password           The password to use for the SASL bind request.
654   *                            It may be {@code null} if no password is
655   *                            required for the desired SASL mechanism.
656   * @param  promptForPassword  Indicates whether to interactively prompt for
657   *                            the password if one is needed but none was
658   *                            provided.
659   * @param  tool               The command-line tool whose input and output
660   *                            streams should be used when prompting for the
661   *                            bind password.  It may be {@code null} if
662   *                            {@code promptForPassword} is {@code false}.
663   * @param  mechanism          The name of the SASL mechanism to use.  It may
664   *                            be {@code null} if the provided set of options
665   *                            contains a "mech" option to specify the desired
666   *                            SASL option.
667   * @param  options            The set of SASL options to use when creating the
668   *                            bind request, in the form "name=value".  It may
669   *                            be {@code null} or empty if no SASL options are
670   *                            needed and a value was provided for the
671   *                            {@code mechanism} argument.  If the set of SASL
672   *                            options includes a "mech" option, then the
673   *                            {@code mechanism} argument must be {@code null}
674   *                            or have a value that matches the value of the
675   *                            "mech" SASL option.
676   * @param  controls           The set of controls to include in the request.
677   *
678   * @return  The SASL bind request created using the provided information.
679   *
680   * @throws  LDAPException  If a problem is encountered while trying to create
681   *                         the SASL bind request.
682   */
683  @NotNull()
684  public static SASLBindRequest createBindRequest(@Nullable final String bindDN,
685                                     @Nullable final byte[] password,
686                                     final boolean promptForPassword,
687                                     @Nullable final CommandLineTool tool,
688                                     @Nullable final String mechanism,
689                                     @Nullable final List<String> options,
690                                     @Nullable final Control... controls)
691         throws LDAPException
692  {
693    if (promptForPassword)
694    {
695      Validator.ensureNotNull(tool);
696    }
697
698    // Parse the provided set of options to ensure that they are properly
699    // formatted in name-value form, and extract the SASL mechanism.
700    final String mech;
701    final Map<String,String> optionsMap = parseOptions(options);
702    final String mechOption =
703         optionsMap.remove(StaticUtils.toLowerCase(SASL_OPTION_MECHANISM));
704    if (mechOption != null)
705    {
706      mech = mechOption;
707      if ((mechanism != null) && (! mech.equalsIgnoreCase(mechanism)))
708      {
709        throw new LDAPException(ResultCode.PARAM_ERROR,
710             ERR_SASL_OPTION_MECH_CONFLICT.get(mechanism, mech));
711      }
712    }
713    else
714    {
715      mech = mechanism;
716    }
717
718    if (mech == null)
719    {
720      throw new LDAPException(ResultCode.PARAM_ERROR,
721           ERR_SASL_OPTION_NO_MECH.get());
722    }
723
724    if (mech.equalsIgnoreCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME))
725    {
726      return createANONYMOUSBindRequest(password, optionsMap, controls);
727    }
728    else if (mech.equalsIgnoreCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME))
729    {
730      return createCRAMMD5BindRequest(password, promptForPassword, tool,
731           optionsMap, controls);
732    }
733    else if (mech.equalsIgnoreCase(
734                  DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME))
735    {
736      return createDIGESTMD5BindRequest(password, promptForPassword, tool,
737           optionsMap, controls);
738    }
739    else if (mech.equalsIgnoreCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME))
740    {
741      return createEXTERNALBindRequest(password, optionsMap, controls);
742    }
743    else if (mech.equalsIgnoreCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME))
744    {
745      return createGSSAPIBindRequest(password, promptForPassword, tool,
746           optionsMap, controls);
747    }
748    else if (mech.equalsIgnoreCase(
749         OAUTHBEARERBindRequest.OAUTHBEARER_MECHANISM_NAME))
750    {
751      return createOAUTHBEARERBindRequest(password, promptForPassword,
752           tool, optionsMap, controls);
753    }
754    else if (mech.equalsIgnoreCase(PLAINBindRequest.PLAIN_MECHANISM_NAME))
755    {
756      return createPLAINBindRequest(password, promptForPassword, tool,
757           optionsMap, controls);
758    }
759    else if (mech.equalsIgnoreCase(
760         SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME))
761    {
762      return createSCRAMSHA1BindRequest(password, promptForPassword, tool,
763           optionsMap, controls);
764    }
765    else if (mech.equalsIgnoreCase(
766         SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME))
767    {
768      return createSCRAMSHA256BindRequest(password, promptForPassword, tool,
769           optionsMap, controls);
770    }
771    else if (mech.equalsIgnoreCase(
772         SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME))
773    {
774      return createSCRAMSHA512BindRequest(password, promptForPassword, tool,
775           optionsMap, controls);
776    }
777    else if (mech.equalsIgnoreCase(UnboundIDCertificatePlusPasswordBindRequest.
778             UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME))
779    {
780      return createUnboundIDCertificatePlusPasswordBindRequest(password, tool,
781           optionsMap, controls);
782    }
783    else if (mech.equalsIgnoreCase(UnboundIDDeliveredOTPBindRequest.
784             UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME))
785    {
786      return createUNBOUNDIDDeliveredOTPBindRequest(password, optionsMap,
787           controls);
788    }
789    else if (mech.equalsIgnoreCase(
790             UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME))
791    {
792      return createUNBOUNDIDTOTPBindRequest(password, tool, optionsMap,
793           controls);
794    }
795    else if (mech.equalsIgnoreCase(
796         UnboundIDYubiKeyOTPBindRequest.UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME))
797    {
798      return createUNBOUNDIDYUBIKEYOTPBindRequest(password, tool, optionsMap,
799           controls);
800    }
801    else
802    {
803      throw new LDAPException(ResultCode.PARAM_ERROR,
804           ERR_SASL_OPTION_UNSUPPORTED_MECH.get(mech));
805    }
806  }
807
808
809
810  /**
811   * Creates a SASL ANONYMOUS bind request using the provided set of options.
812   *
813   * @param  password  The password to use for the bind request.
814   * @param  options   The set of SASL options for the bind request.
815   * @param  controls  The set of controls to include in the request.
816   *
817   * @return  The SASL ANONYMOUS bind request that was created.
818   *
819   * @throws  LDAPException  If a problem is encountered while trying to create
820   *                         the SASL bind request.
821   */
822  @NotNull()
823  private static ANONYMOUSBindRequest createANONYMOUSBindRequest(
824                      @Nullable final byte[] password,
825                      @NotNull final Map<String,String> options,
826                      @Nullable final Control[] controls)
827          throws LDAPException
828  {
829    if (password != null)
830    {
831      throw new LDAPException(ResultCode.PARAM_ERROR,
832           ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
833                ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME));
834    }
835
836
837    // The trace option is optional.
838    final String trace =
839         options.remove(StaticUtils.toLowerCase(SASL_OPTION_TRACE));
840
841    // Ensure no unsupported options were provided.
842    ensureNoUnsupportedOptions(options,
843         ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME);
844
845    return new ANONYMOUSBindRequest(trace, controls);
846  }
847
848
849
850  /**
851   * Creates a SASL CRAM-MD5 bind request using the provided password and set of
852   * options.
853   *
854   * @param  password           The password to use for the bind request.
855   * @param  promptForPassword  Indicates whether to interactively prompt for
856   *                            the password if one is needed but none was
857   *                            provided.
858   * @param  tool               The command-line tool whose input and output
859   *                            streams should be used when prompting for the
860   *                            bind password.  It may be {@code null} only if
861   *                            {@code promptForPassword} is {@code false}.
862   * @param  options            The set of SASL options for the bind request.
863   * @param  controls           The set of controls to include in the request.
864   *
865   * @return  The SASL CRAM-MD5 bind request that was created.
866   *
867   * @throws  LDAPException  If a problem is encountered while trying to create
868   *                         the SASL bind request.
869   */
870  @NotNull()
871  private static CRAMMD5BindRequest createCRAMMD5BindRequest(
872                      @Nullable final byte[] password,
873                      final boolean promptForPassword,
874                      @Nullable final CommandLineTool tool,
875                      @NotNull final Map<String,String> options,
876                      @Nullable final Control[] controls)
877          throws LDAPException
878  {
879    final byte[] pw;
880    if (password == null)
881    {
882      if (promptForPassword)
883      {
884        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
885        pw = PasswordReader.readPassword();
886        tool.getOriginalOut().println();
887      }
888      else
889      {
890        throw new LDAPException(ResultCode.PARAM_ERROR,
891             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
892                  CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
893      }
894    }
895    else
896    {
897      pw = password;
898    }
899
900
901    // The authID option is required.
902    final String authID =
903         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
904    if (authID == null)
905    {
906      throw new LDAPException(ResultCode.PARAM_ERROR,
907           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
908                CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
909    }
910
911
912    // Ensure no unsupported options were provided.
913    ensureNoUnsupportedOptions(options,
914         CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME);
915
916    return new CRAMMD5BindRequest(authID, pw, controls);
917  }
918
919
920
921  /**
922   * Creates a SASL DIGEST-MD5 bind request using the provided password and set
923   * of options.
924   *
925   * @param  password           The password to use for the bind request.
926   * @param  promptForPassword  Indicates whether to interactively prompt for
927   *                            the password if one is needed but none was
928   *                            provided.
929   * @param  tool               The command-line tool whose input and output
930   *                            streams should be used when prompting for the
931   *                            bind password.  It may be {@code null} only if
932   *                            {@code promptForPassword} is {@code false}.
933   * @param  options            The set of SASL options for the bind request.
934   * @param  controls           The set of controls to include in the request.
935   *
936   * @return  The SASL DIGEST-MD5 bind request that was created.
937   *
938   * @throws  LDAPException  If a problem is encountered while trying to create
939   *                         the SASL bind request.
940   */
941  @NotNull()
942  private static DIGESTMD5BindRequest createDIGESTMD5BindRequest(
943                      @Nullable() final byte[] password,
944                      final boolean promptForPassword,
945                      @Nullable final CommandLineTool tool,
946                      @NotNull final Map<String,String> options,
947                      @Nullable final Control[] controls)
948          throws LDAPException
949  {
950    final byte[] pw;
951    if (password == null)
952    {
953      if (promptForPassword)
954      {
955        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
956        pw = PasswordReader.readPassword();
957        tool.getOriginalOut().println();
958      }
959      else
960      {
961        throw new LDAPException(ResultCode.PARAM_ERROR,
962             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
963                  DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME));
964      }
965    }
966    else
967    {
968      pw = password;
969    }
970
971    // The authID option is required.
972    final String authID =
973         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
974    if (authID == null)
975    {
976      throw new LDAPException(ResultCode.PARAM_ERROR,
977           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
978                DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME));
979    }
980
981    final DIGESTMD5BindRequestProperties properties =
982         new DIGESTMD5BindRequestProperties(authID, pw);
983
984    // The authzID option is optional.
985    properties.setAuthorizationID(
986         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID)));
987
988    // The realm option is optional.
989    properties.setRealm(options.remove(
990         StaticUtils.toLowerCase(SASL_OPTION_REALM)));
991
992    // The QoP option is optional, and may contain multiple values that need to
993    // be parsed.
994    final String qopString =
995         options.remove(StaticUtils.toLowerCase(SASL_OPTION_QOP));
996    if (qopString != null)
997    {
998      properties.setAllowedQoP(
999           SASLQualityOfProtection.decodeQoPList(qopString));
1000    }
1001
1002    // Ensure no unsupported options were provided.
1003    ensureNoUnsupportedOptions(options,
1004         DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME);
1005
1006    return new DIGESTMD5BindRequest(properties, controls);
1007  }
1008
1009
1010
1011  /**
1012   * Creates a SASL EXTERNAL bind request using the provided set of options.
1013   *
1014   * @param  password  The password to use for the bind request.
1015   * @param  options   The set of SASL options for the bind request.
1016   * @param  controls  The set of controls to include in the request.
1017   *
1018   * @return  The SASL EXTERNAL bind request that was created.
1019   *
1020   * @throws  LDAPException  If a problem is encountered while trying to create
1021   *                         the SASL bind request.
1022   */
1023  @NotNull()
1024  private static EXTERNALBindRequest createEXTERNALBindRequest(
1025                      @Nullable final byte[] password,
1026                      @NotNull final Map<String,String> options,
1027                      @Nullable final Control[] controls)
1028          throws LDAPException
1029  {
1030    if (password != null)
1031    {
1032      throw new LDAPException(ResultCode.PARAM_ERROR,
1033           ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
1034                EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME));
1035    }
1036
1037    // Ensure no unsupported options were provided.
1038    ensureNoUnsupportedOptions(options,
1039         EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME);
1040
1041    return new EXTERNALBindRequest(controls);
1042  }
1043
1044
1045
1046  /**
1047   * Creates a SASL GSSAPI bind request using the provided password and set of
1048   * options.
1049   *
1050   * @param  password           The password to use for the bind request.
1051   * @param  promptForPassword  Indicates whether to interactively prompt for
1052   *                            the password if one is needed but none was
1053   *                            provided.
1054   * @param  tool               The command-line tool whose input and output
1055   *                            streams should be used when prompting for the
1056   *                            bind password.  It may be {@code null} only if
1057   *                            {@code promptForPassword} is {@code false}.
1058   * @param  options            The set of SASL options for the bind request.
1059   * @param  controls           The set of controls to include in the request.
1060   *
1061   * @return  The SASL GSSAPI bind request that was created.
1062   *
1063   * @throws  LDAPException  If a problem is encountered while trying to create
1064   *                         the SASL bind request.
1065   */
1066  @NotNull()
1067  private static GSSAPIBindRequest createGSSAPIBindRequest(
1068                      @Nullable final byte[] password,
1069                      final boolean promptForPassword,
1070                      @Nullable final CommandLineTool tool,
1071                      @NotNull final Map<String,String> options,
1072                      @Nullable final Control[] controls)
1073          throws LDAPException
1074  {
1075    // The authID option is required.
1076    final String authID =
1077         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
1078    if (authID == null)
1079    {
1080      throw new LDAPException(ResultCode.PARAM_ERROR,
1081           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
1082                GSSAPIBindRequest.GSSAPI_MECHANISM_NAME));
1083    }
1084    final GSSAPIBindRequestProperties gssapiProperties =
1085         new GSSAPIBindRequestProperties(authID, password);
1086
1087    // The authzID option is optional.
1088    gssapiProperties.setAuthorizationID(
1089         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID)));
1090
1091    // The configFile option is optional.
1092    gssapiProperties.setConfigFilePath(options.remove(
1093         StaticUtils.toLowerCase(SASL_OPTION_CONFIG_FILE)));
1094
1095    // The debug option is optional.
1096    gssapiProperties.setEnableGSSAPIDebugging(getBooleanValue(options,
1097         SASL_OPTION_DEBUG, false));
1098
1099    // The kdcAddress option is optional.
1100    gssapiProperties.setKDCAddress(options.remove(
1101         StaticUtils.toLowerCase(SASL_OPTION_KDC_ADDRESS)));
1102
1103    // The protocol option is optional.
1104    final String protocol =
1105         options.remove(StaticUtils.toLowerCase(SASL_OPTION_PROTOCOL));
1106    if (protocol != null)
1107    {
1108      gssapiProperties.setServicePrincipalProtocol(protocol);
1109    }
1110
1111    // The realm option is optional.
1112    gssapiProperties.setRealm(options.remove(
1113         StaticUtils.toLowerCase(SASL_OPTION_REALM)));
1114
1115    // The QoP option is optional, and may contain multiple values that need to
1116    // be parsed.
1117    final String qopString =
1118         options.remove(StaticUtils.toLowerCase(SASL_OPTION_QOP));
1119    if (qopString != null)
1120    {
1121      gssapiProperties.setAllowedQoP(
1122           SASLQualityOfProtection.decodeQoPList(qopString));
1123    }
1124
1125    // The renewTGT option is optional.
1126    gssapiProperties.setRenewTGT(getBooleanValue(options, SASL_OPTION_RENEW_TGT,
1127         false));
1128
1129    // The requireCache option is optional.
1130    gssapiProperties.setRequireCachedCredentials(getBooleanValue(options,
1131         SASL_OPTION_REQUIRE_CACHE, false));
1132
1133    // The ticketCache option is optional.
1134    gssapiProperties.setTicketCachePath(options.remove(
1135         StaticUtils.toLowerCase(SASL_OPTION_TICKET_CACHE_PATH)));
1136
1137    // The useTicketCache option is optional.
1138    gssapiProperties.setUseTicketCache(getBooleanValue(options,
1139         SASL_OPTION_USE_TICKET_CACHE, true));
1140
1141    // Ensure no unsupported options were provided.
1142    ensureNoUnsupportedOptions(options,
1143         GSSAPIBindRequest.GSSAPI_MECHANISM_NAME);
1144
1145    // A password must have been provided unless useTicketCache=true and
1146    // requireTicketCache=true.
1147    if (password == null)
1148    {
1149      if (! (gssapiProperties.useTicketCache() &&
1150           gssapiProperties.requireCachedCredentials()))
1151      {
1152        if (promptForPassword)
1153        {
1154          tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1155          gssapiProperties.setPassword(PasswordReader.readPassword());
1156          tool.getOriginalOut().println();
1157        }
1158        else
1159        {
1160          throw new LDAPException(ResultCode.PARAM_ERROR,
1161               ERR_SASL_OPTION_GSSAPI_PASSWORD_REQUIRED.get());
1162        }
1163      }
1164    }
1165
1166    return new GSSAPIBindRequest(gssapiProperties, controls);
1167  }
1168
1169
1170
1171  /**
1172   * Creates a SASL OAUTHBEARER bind request using the provided password and
1173   * set of options.
1174   *
1175   * @param  password           The password to use for the bind request.
1176   * @param  promptForPassword  Indicates whether to interactively prompt for
1177   *                            the password if one is needed but none was
1178   *                            provided.
1179   * @param  tool               The command-line tool whose input and output
1180   *                            streams should be used when prompting for the
1181   *                            bind password.  It may be {@code null} only if
1182   *                            {@code promptForPassword} is {@code false}.
1183   * @param  options            The set of SASL options for the bind request.
1184   * @param  controls           The set of controls to include in the request.
1185   *
1186   * @return  The SASL OAUTHBEARER bind request that was created.
1187   *
1188   * @throws  LDAPException  If a problem is encountered while trying to create
1189   *                         the SASL bind request.
1190   */
1191  @NotNull()
1192  private static OAUTHBEARERBindRequest createOAUTHBEARERBindRequest(
1193                      @Nullable final byte[] password,
1194                      final boolean promptForPassword,
1195                      @Nullable final CommandLineTool tool,
1196                      @NotNull final Map<String,String> options,
1197                      @Nullable final Control[] controls)
1198          throws LDAPException
1199  {
1200    // The accessToken option wasn't declared as required, but we will either
1201    // require it to have been provided or we will interactively prompt for it.
1202    String accessToken =
1203         options.remove(StaticUtils.toLowerCase(SASL_OPTION_ACCESS_TOKEN));
1204    if (accessToken == null)
1205    {
1206      if (promptForPassword)
1207      {
1208        tool.getOriginalOut().print(
1209             INFO_SASL_TOOL_ENTER_OAUTHBEARER_ACCESS_TOKEN.get());
1210        accessToken = StaticUtils.toUTF8String(PasswordReader.readPassword());
1211        tool.getOriginalOut().println();
1212      }
1213      else
1214      {
1215        throw new LDAPException(ResultCode.PARAM_ERROR,
1216             ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_ACCESS_TOKEN,
1217                  OAUTHBEARERBindRequest.OAUTHBEARER_MECHANISM_NAME));
1218      }
1219    }
1220
1221    // Ensure no unsupported options were provided.
1222    ensureNoUnsupportedOptions(options,
1223         OAUTHBEARERBindRequest.OAUTHBEARER_MECHANISM_NAME);
1224
1225    return new OAUTHBEARERBindRequest(accessToken, controls);
1226  }
1227
1228
1229
1230  /**
1231   * Creates a SASL PLAIN bind request using the provided password and set of
1232   * options.
1233   *
1234   * @param  password           The password to use for the bind request.
1235   * @param  promptForPassword  Indicates whether to interactively prompt for
1236   *                            the password if one is needed but none was
1237   *                            provided.
1238   * @param  tool               The command-line tool whose input and output
1239   *                            streams should be used when prompting for the
1240   *                            bind password.  It may be {@code null} only if
1241   *                            {@code promptForPassword} is {@code false}.
1242   * @param  options            The set of SASL options for the bind request.
1243   * @param  controls           The set of controls to include in the request.
1244   *
1245   * @return  The SASL PLAIN bind request that was created.
1246   *
1247   * @throws  LDAPException  If a problem is encountered while trying to create
1248   *                         the SASL bind request.
1249   */
1250  @NotNull()
1251  private static PLAINBindRequest createPLAINBindRequest(
1252                      @Nullable final byte[] password,
1253                      final boolean promptForPassword,
1254                      @Nullable final CommandLineTool tool,
1255                      @NotNull final Map<String,String> options,
1256                      @Nullable final Control[] controls)
1257          throws LDAPException
1258  {
1259    final byte[] pw;
1260    if (password == null)
1261    {
1262      if (promptForPassword)
1263      {
1264        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1265        pw = PasswordReader.readPassword();
1266        tool.getOriginalOut().println();
1267      }
1268      else
1269      {
1270        throw new LDAPException(ResultCode.PARAM_ERROR,
1271             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
1272                  PLAINBindRequest.PLAIN_MECHANISM_NAME));
1273      }
1274    }
1275    else
1276    {
1277      pw = password;
1278    }
1279
1280    // The authID option is required.
1281    final String authID =
1282         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
1283    if (authID == null)
1284    {
1285      throw new LDAPException(ResultCode.PARAM_ERROR,
1286           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
1287                PLAINBindRequest.PLAIN_MECHANISM_NAME));
1288    }
1289
1290    // The authzID option is optional.
1291    final String authzID =
1292         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));
1293
1294    // Ensure no unsupported options were provided.
1295    ensureNoUnsupportedOptions(options,
1296         PLAINBindRequest.PLAIN_MECHANISM_NAME);
1297
1298    return new PLAINBindRequest(authID, authzID, pw, controls);
1299  }
1300
1301
1302
1303  /**
1304   * Creates a SASL SCRAM-SHA-1 bind request using the provided password and
1305   * set of options.
1306   *
1307   * @param  password           The password to use for the bind request.
1308   * @param  promptForPassword  Indicates whether to interactively prompt for
1309   *                            the password if one is needed but none was
1310   *                            provided.
1311   * @param  tool               The command-line tool whose input and output
1312   *                            streams should be used when prompting for the
1313   *                            bind password.  It may be {@code null} only if
1314   *                            {@code promptForPassword} is {@code false}.
1315   * @param  options            The set of SASL options for the bind request.
1316   * @param  controls           The set of controls to include in the request.
1317   *
1318   * @return  The SASL SCRAM-SHA-1 bind request that was created.
1319   *
1320   * @throws  LDAPException  If a problem is encountered while trying to create
1321   *                         the SASL bind request.
1322   */
1323  @NotNull()
1324  private static SCRAMSHA1BindRequest createSCRAMSHA1BindRequest(
1325                      @Nullable final byte[] password,
1326                      final boolean promptForPassword,
1327                      @Nullable final CommandLineTool tool,
1328                      @NotNull final Map<String,String> options,
1329                      @Nullable final Control[] controls)
1330          throws LDAPException
1331  {
1332    final byte[] pw;
1333    if (password == null)
1334    {
1335      if (promptForPassword)
1336      {
1337        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1338        pw = PasswordReader.readPassword();
1339        tool.getOriginalOut().println();
1340      }
1341      else
1342      {
1343        throw new LDAPException(ResultCode.PARAM_ERROR,
1344             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
1345                  SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME));
1346      }
1347    }
1348    else
1349    {
1350      pw = password;
1351    }
1352
1353    // The username option is required.
1354    final String username =
1355         options.remove(StaticUtils.toLowerCase(SASL_OPTION_USERNAME));
1356    if (username == null)
1357    {
1358      throw new LDAPException(ResultCode.PARAM_ERROR,
1359           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_USERNAME,
1360                SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME));
1361    }
1362
1363    // Ensure no unsupported options were provided.
1364    ensureNoUnsupportedOptions(options,
1365         SCRAMSHA1BindRequest.SCRAM_SHA_1_MECHANISM_NAME);
1366
1367    return new SCRAMSHA1BindRequest(username, pw, controls);
1368  }
1369
1370
1371
1372  /**
1373   * Creates a SASL SCRAM-SHA-256 bind request using the provided password and
1374   * set of options.
1375   *
1376   * @param  password           The password to use for the bind request.
1377   * @param  promptForPassword  Indicates whether to interactively prompt for
1378   *                            the password if one is needed but none was
1379   *                            provided.
1380   * @param  tool               The command-line tool whose input and output
1381   *                            streams should be used when prompting for the
1382   *                            bind password.  It may be {@code null} only if
1383   *                            {@code promptForPassword} is {@code false}.
1384   * @param  options            The set of SASL options for the bind request.
1385   * @param  controls           The set of controls to include in the request.
1386   *
1387   * @return  The SASL SCRAM-SHA-256 bind request that was created.
1388   *
1389   * @throws  LDAPException  If a problem is encountered while trying to create
1390   *                         the SASL bind request.
1391   */
1392  @NotNull()
1393  private static SCRAMSHA256BindRequest createSCRAMSHA256BindRequest(
1394                      @Nullable final byte[] password,
1395                      final boolean promptForPassword,
1396                      @Nullable final CommandLineTool tool,
1397                      @NotNull final Map<String,String> options,
1398                      @Nullable final Control[] controls)
1399          throws LDAPException
1400  {
1401    final byte[] pw;
1402    if (password == null)
1403    {
1404      if (promptForPassword)
1405      {
1406        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1407        pw = PasswordReader.readPassword();
1408        tool.getOriginalOut().println();
1409      }
1410      else
1411      {
1412        throw new LDAPException(ResultCode.PARAM_ERROR,
1413             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
1414                  SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME));
1415      }
1416    }
1417    else
1418    {
1419      pw = password;
1420    }
1421
1422    // The username option is required.
1423    final String username =
1424         options.remove(StaticUtils.toLowerCase(SASL_OPTION_USERNAME));
1425    if (username == null)
1426    {
1427      throw new LDAPException(ResultCode.PARAM_ERROR,
1428           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_USERNAME,
1429                SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME));
1430    }
1431
1432    // Ensure no unsupported options were provided.
1433    ensureNoUnsupportedOptions(options,
1434         SCRAMSHA256BindRequest.SCRAM_SHA_256_MECHANISM_NAME);
1435
1436    return new SCRAMSHA256BindRequest(username, pw, controls);
1437  }
1438
1439
1440
1441  /**
1442   * Creates a SASL SCRAM-SHA-512 bind request using the provided password and
1443   * set of options.
1444   *
1445   * @param  password           The password to use for the bind request.
1446   * @param  promptForPassword  Indicates whether to interactively prompt for
1447   *                            the password if one is needed but none was
1448   *                            provided.
1449   * @param  tool               The command-line tool whose input and output
1450   *                            streams should be used when prompting for the
1451   *                            bind password.  It may be {@code null} only if
1452   *                            {@code promptForPassword} is {@code false}.
1453   * @param  options            The set of SASL options for the bind request.
1454   * @param  controls           The set of controls to include in the request.
1455   *
1456   * @return  The SASL SCRAM-SHA-512 bind request that was created.
1457   *
1458   * @throws  LDAPException  If a problem is encountered while trying to create
1459   *                         the SASL bind request.
1460   */
1461  @NotNull()
1462  private static SCRAMSHA512BindRequest createSCRAMSHA512BindRequest(
1463                      @Nullable final byte[] password,
1464                      final boolean promptForPassword,
1465                      @Nullable final CommandLineTool tool,
1466                      @NotNull final Map<String,String> options,
1467                      @Nullable final Control[] controls)
1468          throws LDAPException
1469  {
1470    final byte[] pw;
1471    if (password == null)
1472    {
1473      if (promptForPassword)
1474      {
1475        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1476        pw = PasswordReader.readPassword();
1477        tool.getOriginalOut().println();
1478      }
1479      else
1480      {
1481        throw new LDAPException(ResultCode.PARAM_ERROR,
1482             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
1483                  SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME));
1484      }
1485    }
1486    else
1487    {
1488      pw = password;
1489    }
1490
1491    // The username option is required.
1492    final String username =
1493         options.remove(StaticUtils.toLowerCase(SASL_OPTION_USERNAME));
1494    if (username == null)
1495    {
1496      throw new LDAPException(ResultCode.PARAM_ERROR,
1497           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_USERNAME,
1498                SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME));
1499    }
1500
1501    // Ensure no unsupported options were provided.
1502    ensureNoUnsupportedOptions(options,
1503         SCRAMSHA512BindRequest.SCRAM_SHA_512_MECHANISM_NAME);
1504
1505    return new SCRAMSHA512BindRequest(username, pw, controls);
1506  }
1507
1508
1509
1510  /**
1511   * Creates a SASL UNBOUNDID-CERTIFICATE-PLUS-PASSWORD bind request using the
1512   * provided set of options.
1513   *
1514   * @param  password  The password to use for the bind request.
1515   * @param  tool      The command-line tool whose input and output streams
1516   *                   should be used when prompting for the bind password.  It
1517   *                   may be {@code null} only if {@code promptForPassword} is
1518   *                   {@code false}.
1519   * @param  options   The set of SASL options for the bind request.
1520   * @param  controls  The set of controls to include in the request.
1521   *
1522   * @return  The SASL UNBOUNDID-CERTIFICATE-PLUS-PASSWORD bind request that was
1523   *          created.
1524   *
1525   * @throws  LDAPException  If a problem is encountered while trying to create
1526   *                         the SASL bind request.
1527   */
1528  @NotNull()
1529  private static UnboundIDCertificatePlusPasswordBindRequest
1530                      createUnboundIDCertificatePlusPasswordBindRequest(
1531                           @Nullable final byte[] password,
1532                           @Nullable final CommandLineTool tool,
1533                           @NotNull final Map<String,String> options,
1534                           @Nullable final Control[] controls)
1535          throws LDAPException
1536  {
1537    final byte[] pw;
1538    if (password == null)
1539    {
1540      tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1541      pw = PasswordReader.readPassword();
1542      tool.getOriginalOut().println();
1543    }
1544    else
1545    {
1546      pw = password;
1547    }
1548
1549    // Ensure no unsupported options were provided.
1550    ensureNoUnsupportedOptions(options,
1551         UnboundIDCertificatePlusPasswordBindRequest.
1552              UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME);
1553
1554    return new UnboundIDCertificatePlusPasswordBindRequest(pw, controls);
1555  }
1556
1557
1558
1559  /**
1560   * Creates a SASL UNBOUNDID-DELIVERED-OTP bind request using the provided
1561   * password and set of options.
1562   *
1563   * @param  password  The password to use for the bind request.
1564   * @param  options   The set of SASL options for the bind request.
1565   * @param  controls  The set of controls to include in the request.
1566   *
1567   * @return  The SASL UNBOUNDID-DELIVERED-OTP bind request that was created.
1568   *
1569   * @throws  LDAPException  If a problem is encountered while trying to create
1570   *                         the SASL bind request.
1571   */
1572  @NotNull()
1573  private static UnboundIDDeliveredOTPBindRequest
1574                      createUNBOUNDIDDeliveredOTPBindRequest(
1575                           @Nullable final byte[] password,
1576                           @NotNull final Map<String,String> options,
1577                           @Nullable final Control... controls)
1578          throws LDAPException
1579  {
1580    if (password != null)
1581    {
1582      throw new LDAPException(ResultCode.PARAM_ERROR,
1583           ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
1584                UnboundIDDeliveredOTPBindRequest.
1585                     UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME));
1586    }
1587
1588    // The authID option is required.
1589    final String authID =
1590         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
1591    if (authID == null)
1592    {
1593      throw new LDAPException(ResultCode.PARAM_ERROR,
1594           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
1595                UnboundIDDeliveredOTPBindRequest.
1596                     UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME));
1597    }
1598
1599    // The OTP option is required.
1600    final String otp = options.remove(StaticUtils.toLowerCase(SASL_OPTION_OTP));
1601    if (otp == null)
1602    {
1603      throw new LDAPException(ResultCode.PARAM_ERROR,
1604           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_OTP,
1605                UnboundIDDeliveredOTPBindRequest.
1606                     UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME));
1607    }
1608
1609    // The authzID option is optional.
1610    final String authzID =
1611         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));
1612
1613    // Ensure no unsupported options were provided.
1614    ensureNoUnsupportedOptions(options,
1615         UnboundIDDeliveredOTPBindRequest.
1616              UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME);
1617
1618    return new UnboundIDDeliveredOTPBindRequest(authID, authzID, otp, controls);
1619  }
1620
1621
1622
1623  /**
1624   * Creates a SASL UNBOUNDID-TOTP bind request using the provided password and
1625   * set of options.
1626   *
1627   * @param  password  The password to use for the bind request.
1628   * @param  tool      The command-line tool whose input and output streams
1629   *                   should be used when prompting for the bind password.  It
1630   *                   may be {@code null} only if {@code promptForPassword} is
1631   *                   {@code false}.
1632   * @param  options   The set of SASL options for the bind request.
1633   * @param  controls  The set of controls to include in the request.
1634   *
1635   * @return  The SASL UNBOUNDID-TOTP bind request that was created.
1636   *
1637   * @throws  LDAPException  If a problem is encountered while trying to create
1638   *                         the SASL bind request.
1639   */
1640  @NotNull()
1641  private static SingleUseTOTPBindRequest createUNBOUNDIDTOTPBindRequest(
1642                      @Nullable final byte[] password,
1643                      @Nullable final CommandLineTool tool,
1644                      @NotNull final Map<String,String> options,
1645                      @Nullable final Control... controls)
1646          throws LDAPException
1647  {
1648    // The authID option is required.
1649    final String authID =
1650         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
1651    if (authID == null)
1652    {
1653      throw new LDAPException(ResultCode.PARAM_ERROR,
1654           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
1655                UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME));
1656    }
1657
1658    // The TOTP password option is required.
1659    final String totpPassword =
1660         options.remove(StaticUtils.toLowerCase(SASL_OPTION_TOTP_PASSWORD));
1661    if (totpPassword == null)
1662    {
1663      throw new LDAPException(ResultCode.PARAM_ERROR,
1664           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_TOTP_PASSWORD,
1665                UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME));
1666    }
1667
1668    // The authzID option is optional.
1669    byte[] pwBytes = password;
1670    final String authzID =
1671         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));
1672
1673    // The promptForStaticPassword option is optional.
1674    final String promptStr = options.remove(StaticUtils.toLowerCase(
1675         SASL_OPTION_PROMPT_FOR_STATIC_PW));
1676    if (promptStr != null)
1677    {
1678      if (promptStr.equalsIgnoreCase("true"))
1679      {
1680        if (pwBytes == null)
1681        {
1682          tool.getOriginalOut().print(INFO_SASL_ENTER_STATIC_PW.get());
1683          pwBytes = PasswordReader.readPassword();
1684          tool.getOriginalOut().println();
1685        }
1686        else
1687        {
1688          throw new LDAPException(ResultCode.PARAM_ERROR,
1689               ERR_SASL_PROMPT_FOR_PROVIDED_PW.get(
1690                    SASL_OPTION_PROMPT_FOR_STATIC_PW));
1691        }
1692      }
1693      else if (! promptStr.equalsIgnoreCase("false"))
1694      {
1695        throw new LDAPException(ResultCode.PARAM_ERROR,
1696             ERR_SASL_PROMPT_FOR_STATIC_PW_BAD_VALUE.get(
1697                  SASL_OPTION_PROMPT_FOR_STATIC_PW));
1698      }
1699    }
1700
1701    // Ensure no unsupported options were provided.
1702    ensureNoUnsupportedOptions(options,
1703         UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME);
1704
1705    return new SingleUseTOTPBindRequest(authID, authzID, totpPassword, pwBytes,
1706         controls);
1707  }
1708
1709
1710
1711  /**
1712   * Creates a SASL UNBOUNDID-YUBIKEY-OTP bind request using the provided
1713   * password and set of options.
1714   *
1715   * @param  password  The password to use for the bind request.
1716   * @param  tool      The command-line tool whose input and output streams
1717   *                   should be used when prompting for the bind password.  It
1718   *                   may be {@code null} only if {@code promptForPassword} is
1719   *                   {@code false}.
1720   * @param  options   The set of SASL options for the bind request.
1721   * @param  controls  The set of controls to include in the request.
1722   *
1723   * @return  The SASL UNBOUNDID-YUBIKEY-OTP bind request that was created.
1724   *
1725   * @throws  LDAPException  If a problem is encountered while trying to create
1726   *                         the SASL bind request.
1727   */
1728  @NotNull()
1729  private static UnboundIDYubiKeyOTPBindRequest
1730                      createUNBOUNDIDYUBIKEYOTPBindRequest(
1731                           @Nullable final byte[] password,
1732                           @Nullable final CommandLineTool tool,
1733                           @NotNull final Map<String,String> options,
1734                           @Nullable final Control... controls)
1735          throws LDAPException
1736  {
1737    // The authID option is required.
1738    final String authID =
1739         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
1740    if (authID == null)
1741    {
1742      throw new LDAPException(ResultCode.PARAM_ERROR,
1743           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
1744                UnboundIDYubiKeyOTPBindRequest.
1745                     UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
1746    }
1747
1748    // The otp option is required.
1749    final String otp = options.remove(StaticUtils.toLowerCase(SASL_OPTION_OTP));
1750    if (otp == null)
1751    {
1752      throw new LDAPException(ResultCode.PARAM_ERROR,
1753           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_OTP,
1754                UnboundIDYubiKeyOTPBindRequest.
1755                     UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
1756    }
1757
1758    // The authzID option is optional.
1759    final String authzID =
1760         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));
1761
1762    // The promptForStaticPassword option is optional.
1763    byte[] pwBytes = password;
1764    final String promptStr = options.remove(StaticUtils.toLowerCase(
1765         SASL_OPTION_PROMPT_FOR_STATIC_PW));
1766    if (promptStr != null)
1767    {
1768      if (promptStr.equalsIgnoreCase("true"))
1769      {
1770        if (pwBytes == null)
1771        {
1772          tool.getOriginalOut().print(INFO_SASL_ENTER_STATIC_PW.get());
1773          pwBytes = PasswordReader.readPassword();
1774          tool.getOriginalOut().println();
1775        }
1776        else
1777        {
1778          throw new LDAPException(ResultCode.PARAM_ERROR,
1779               ERR_SASL_PROMPT_FOR_PROVIDED_PW.get(
1780                    SASL_OPTION_PROMPT_FOR_STATIC_PW));
1781        }
1782      }
1783      else if (! promptStr.equalsIgnoreCase("false"))
1784      {
1785        throw new LDAPException(ResultCode.PARAM_ERROR,
1786             ERR_SASL_PROMPT_FOR_STATIC_PW_BAD_VALUE.get(
1787                  SASL_OPTION_PROMPT_FOR_STATIC_PW));
1788      }
1789    }
1790
1791    // Ensure no unsupported options were provided.
1792    ensureNoUnsupportedOptions(options,
1793         UnboundIDYubiKeyOTPBindRequest.UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME);
1794
1795    return new UnboundIDYubiKeyOTPBindRequest(authID, authzID, pwBytes, otp,
1796         controls);
1797  }
1798
1799
1800
1801  /**
1802   * Parses the provided list of SASL options.
1803   *
1804   * @param  options  The list of options to be parsed.
1805   *
1806   * @return  A map with the parsed set of options.
1807   *
1808   * @throws  LDAPException  If a problem is encountered while parsing options.
1809   */
1810  @NotNull()
1811  private static Map<String,String>
1812                      parseOptions(@Nullable final List<String> options)
1813          throws LDAPException
1814  {
1815    if (options == null)
1816    {
1817      return new HashMap<>(0);
1818    }
1819
1820    final HashMap<String,String> m =
1821         new HashMap<>(StaticUtils.computeMapCapacity(options.size()));
1822    for (final String s : options)
1823    {
1824      final int equalPos = s.indexOf('=');
1825      if (equalPos < 0)
1826      {
1827        throw new LDAPException(ResultCode.PARAM_ERROR,
1828             ERR_SASL_OPTION_MISSING_EQUAL.get(s));
1829      }
1830      else if (equalPos == 0)
1831      {
1832        throw new LDAPException(ResultCode.PARAM_ERROR,
1833             ERR_SASL_OPTION_STARTS_WITH_EQUAL.get(s));
1834      }
1835
1836      final String name = s.substring(0, equalPos);
1837      final String value = s.substring(equalPos + 1);
1838      if (m.put(StaticUtils.toLowerCase(name), value) != null)
1839      {
1840        throw new LDAPException(ResultCode.PARAM_ERROR,
1841             ERR_SASL_OPTION_NOT_MULTI_VALUED.get(name));
1842      }
1843    }
1844
1845    return m;
1846  }
1847
1848
1849
1850  /**
1851   * Ensures that the provided map is empty, and will throw an exception if it
1852   * isn't.  This method is intended for internal use only.
1853   *
1854   * @param  options    The map of options to ensure is empty.
1855   * @param  mechanism  The associated SASL mechanism.
1856   *
1857   * @throws  LDAPException  If the map of SASL options is not empty.
1858   */
1859  @InternalUseOnly()
1860  public static void ensureNoUnsupportedOptions(
1861                          @NotNull final Map<String,String> options,
1862                          @NotNull final String mechanism)
1863          throws LDAPException
1864  {
1865    if (! options.isEmpty())
1866    {
1867      for (final String s : options.keySet())
1868      {
1869        throw new LDAPException(ResultCode.PARAM_ERROR,
1870             ERR_SASL_OPTION_UNSUPPORTED_FOR_MECH.get(s,mechanism));
1871      }
1872    }
1873  }
1874
1875
1876
1877  /**
1878   * Retrieves the value of the specified option and parses it as a boolean.
1879   * Values of "true", "t", "yes", "y", "on", and "1" will be treated as
1880   * {@code true}.  Values of "false", "f", "no", "n", "off", and "0" will be
1881   * treated as {@code false}.
1882   *
1883   * @param  m  The map from which to retrieve the option.  It must not be
1884   *            {@code null}.
1885   * @param  o  The name of the option to examine.
1886   * @param  d  The default value to use if the given option was not provided.
1887   *
1888   * @return  The parsed boolean value.
1889   *
1890   * @throws  LDAPException  If the option value cannot be parsed as a boolean.
1891   */
1892  static boolean getBooleanValue(@NotNull final Map<String,String> m,
1893                                 @NotNull final String o, final boolean d)
1894         throws LDAPException
1895  {
1896    final String s =
1897         StaticUtils.toLowerCase(m.remove(StaticUtils.toLowerCase(o)));
1898    if (s == null)
1899    {
1900      return d;
1901    }
1902    else if (s.equals("true") ||
1903             s.equals("t") ||
1904             s.equals("yes") ||
1905             s.equals("y") ||
1906             s.equals("on") ||
1907             s.equals("1"))
1908    {
1909      return true;
1910    }
1911    else if (s.equals("false") ||
1912             s.equals("f") ||
1913             s.equals("no") ||
1914             s.equals("n") ||
1915             s.equals("off") ||
1916             s.equals("0"))
1917    {
1918      return false;
1919    }
1920    else
1921    {
1922      throw new LDAPException(ResultCode.PARAM_ERROR,
1923           ERR_SASL_OPTION_MALFORMED_BOOLEAN_VALUE.get(o));
1924    }
1925  }
1926
1927
1928
1929  /**
1930   * Retrieves a string representation of the SASL usage information.  This will
1931   * include the supported SASL mechanisms and the properties that may be used
1932   * with each.
1933   *
1934   * @param  maxWidth  The maximum line width to use for the output.  If this is
1935   *                   less than or equal to zero, then no wrapping will be
1936   *                   performed.
1937   *
1938   * @return  A string representation of the usage information.
1939   */
1940  @NotNull()
1941  public static String getUsageString(final int maxWidth)
1942  {
1943    return getUsageString(null, maxWidth);
1944  }
1945
1946
1947
1948  /**
1949   * Retrieves a string representation of the SASL usage information.  This will
1950   * include the supported SASL mechanisms and the properties that may be used
1951   * with each.
1952   *
1953   * @param  mechanism  The name of the SASL mechanism for which to obtain usage
1954   *                    information  It may be {@code null} if usage should be
1955   *                    displayed for all available mechamisms.
1956   * @param  maxWidth   The maximum line width to use for the output.  If this
1957   *                    is less than or equal to zero, then no wrapping will be
1958   *                    performed.
1959   *
1960   * @return  A string representation of the usage information.
1961   */
1962  @NotNull()
1963  public static String getUsageString(@Nullable final String mechanism,
1964                                      final int maxWidth)
1965  {
1966    final StringBuilder buffer = new StringBuilder();
1967
1968    for (final String line : getUsage(mechanism, maxWidth))
1969    {
1970      buffer.append(line);
1971      buffer.append(StaticUtils.EOL);
1972    }
1973
1974    return buffer.toString();
1975  }
1976
1977
1978
1979  /**
1980   * Retrieves lines that make up the SASL usage information, optionally
1981   * wrapping long lines.
1982   *
1983   * @param  maxWidth  The maximum line width to use for the output.  If this is
1984   *                   less than or equal to zero, then no wrapping will be
1985   *                   performed.
1986   *
1987   * @return  The lines that make up the SASL usage information.
1988   */
1989  @NotNull()
1990  public static List<String> getUsage(final int maxWidth)
1991  {
1992    return getUsage(null, maxWidth);
1993  }
1994
1995
1996
1997  /**
1998   * Retrieves lines that make up the SASL usage information, optionally
1999   * wrapping long lines.
2000   *
2001   * @param  mechanism  The name of the SASL mechanism for which to obtain usage
2002   *                    information  It may be {@code null} if usage should be
2003   *                    displayed for all available mechamisms.
2004   * @param  maxWidth   The maximum line width to use for the output.  If this
2005   *                    is less than or equal to zero, then no wrapping will be
2006   *                    performed.
2007   *
2008   * @return  The lines that make up the SASL usage information.
2009   */
2010  @NotNull()
2011  public static List<String> getUsage(@Nullable final String mechanism,
2012                                      final int maxWidth)
2013  {
2014    final ArrayList<String> lines = new ArrayList<>(100);
2015
2016    boolean first = true;
2017    for (final SASLMechanismInfo i : getSupportedSASLMechanisms())
2018    {
2019      if ((mechanism != null) && (! i.getName().equalsIgnoreCase(mechanism)))
2020      {
2021        continue;
2022      }
2023
2024      if (first)
2025      {
2026        first = false;
2027      }
2028      else
2029      {
2030        lines.add("");
2031        lines.add("");
2032      }
2033
2034      lines.addAll(
2035           StaticUtils.wrapLine(INFO_SASL_HELP_MECHANISM.get(i.getName()),
2036                maxWidth));
2037      lines.add("");
2038
2039      for (final String line :
2040           StaticUtils.wrapLine(i.getDescription(), maxWidth - 4))
2041      {
2042        lines.add("  " + line);
2043      }
2044      lines.add("");
2045
2046      for (final String line :
2047           StaticUtils.wrapLine(INFO_SASL_HELP_MECHANISM_OPTIONS.get(
2048                i.getName()), maxWidth - 4))
2049      {
2050        lines.add("  " + line);
2051      }
2052
2053      if (i.acceptsPassword())
2054      {
2055        lines.add("");
2056        if (i.requiresPassword())
2057        {
2058          for (final String line :
2059               StaticUtils.wrapLine(INFO_SASL_HELP_PASSWORD_REQUIRED.get(
2060                    i.getName()), maxWidth - 4))
2061          {
2062            lines.add("  " + line);
2063          }
2064        }
2065        else
2066        {
2067          for (final String line :
2068               StaticUtils.wrapLine(INFO_SASL_HELP_PASSWORD_OPTIONAL.get(
2069                    i.getName()), maxWidth - 4))
2070          {
2071            lines.add("  " + line);
2072          }
2073        }
2074      }
2075
2076      for (final SASLOption o : i.getOptions())
2077      {
2078        lines.add("");
2079        lines.add("  * " + o.getName());
2080        for (final String line :
2081             StaticUtils.wrapLine(o.getDescription(), maxWidth - 14))
2082        {
2083          lines.add("       " + line);
2084        }
2085      }
2086    }
2087
2088    if ((mechanism != null) && lines.isEmpty())
2089    {
2090      return getUsage(null, maxWidth);
2091    }
2092
2093
2094    return lines;
2095  }
2096}