001/*
002 * Copyright 2011-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2011-2020 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-2020 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.PLAINBindRequest;
057import com.unboundid.ldap.sdk.ResultCode;
058import com.unboundid.ldap.sdk.SASLBindRequest;
059import com.unboundid.ldap.sdk.SASLQualityOfProtection;
060import com.unboundid.ldap.sdk.unboundidds.SingleUseTOTPBindRequest;
061import com.unboundid.ldap.sdk.unboundidds.
062            UnboundIDCertificatePlusPasswordBindRequest;
063import com.unboundid.ldap.sdk.unboundidds.UnboundIDDeliveredOTPBindRequest;
064import com.unboundid.ldap.sdk.unboundidds.UnboundIDTOTPBindRequest;
065import com.unboundid.ldap.sdk.unboundidds.UnboundIDYubiKeyOTPBindRequest;
066
067import static com.unboundid.util.UtilityMessages.*;
068
069
070
071/**
072 * This class provides a utility that may be used to help process SASL bind
073 * operations using the LDAP SDK.
074 */
075@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
076public final class SASLUtils
077{
078  /**
079   * The name of the SASL option that specifies the authentication ID.  It may
080   * be used in conjunction with the CRAM-MD5, DIGEST-MD5, GSSAPI, and PLAIN
081   * mechanisms.
082   */
083  public static final String SASL_OPTION_AUTH_ID = "authID";
084
085
086
087  /**
088   * The name of the SASL option that specifies the authorization ID.  It may
089   * be used in conjunction with the DIGEST-MD5, GSSAPI, and PLAIN mechanisms.
090   */
091  public static final String SASL_OPTION_AUTHZ_ID = "authzID";
092
093
094
095  /**
096   * The name of the SASL option that specifies the path to the JAAS config
097   * file.  It may be used in conjunction with the GSSAPI mechanism.
098   */
099  public static final String SASL_OPTION_CONFIG_FILE = "configFile";
100
101
102
103  /**
104   * The name of the SASL option that indicates whether debugging should be
105   * enabled.  It may be used in conjunction with the GSSAPI mechanism.
106   */
107  public static final String SASL_OPTION_DEBUG = "debug";
108
109
110
111  /**
112   * The name of the SASL option that specifies the KDC address.  It may be used
113   * in conjunction with the GSSAPI mechanism.
114   */
115  public static final String SASL_OPTION_KDC_ADDRESS = "kdcAddress";
116
117
118
119  /**
120   * The name of the SASL option that specifies the desired SASL mechanism to
121   * use to authenticate to the server.
122   */
123  public static final String SASL_OPTION_MECHANISM = "mech";
124
125
126
127  /**
128   * The name of the SASL option that specifies a one-time password.  It may be
129   * used in conjunction with the UNBOUNDID-DELIVERED-OTP and
130   * UNBOUNDID-YUBIKEY-OTP mechanisms.
131   */
132  public static final String SASL_OPTION_OTP = "otp";
133
134
135
136  /**
137   * The name of the SASL option that may be used to indicate whether to
138   * prompt for a static password.  It may be used in conjunction with the
139   * UNBOUNDID-TOTP and UNBOUNDID-YUBIKEY-OTP mechanisms.
140   */
141  public static final String SASL_OPTION_PROMPT_FOR_STATIC_PW =
142       "promptForStaticPassword";
143
144
145
146  /**
147   * The name of the SASL option that specifies the GSSAPI service principal
148   * protocol.  It may be used in conjunction with the GSSAPI mechanism.
149   */
150  public static final String SASL_OPTION_PROTOCOL = "protocol";
151
152
153
154  /**
155   * The name of the SASL option that specifies the quality of protection that
156   * should be used for communication that occurs after the authentication has
157   * completed.
158   */
159  public static final String SASL_OPTION_QOP = "qop";
160
161
162
163  /**
164   * The name of the SASL option that specifies the realm name.  It may be used
165   * in conjunction with the DIGEST-MD5 and GSSAPI mechanisms.
166   */
167  public static final String SASL_OPTION_REALM = "realm";
168
169
170
171  /**
172   * The name of the SASL option that indicates whether to require an existing
173   * Kerberos session from the ticket cache.  It may be used in conjunction with
174   * the GSSAPI mechanism.
175   */
176  public static final String SASL_OPTION_REQUIRE_CACHE = "requireCache";
177
178
179
180  /**
181   * The name of the SASL option that indicates whether to attempt to renew the
182   * Kerberos TGT for an existing session.  It may be used in conjunction with
183   * the GSSAPI mechanism.
184   */
185  public static final String SASL_OPTION_RENEW_TGT = "renewTGT";
186
187
188
189  /**
190   * The name of the SASL option that specifies the path to the Kerberos ticket
191   * cache to use.  It may be used in conjunction with the GSSAPI mechanism.
192   */
193  public static final String SASL_OPTION_TICKET_CACHE_PATH = "ticketCache";
194
195
196
197  /**
198   * The name of the SASL option that specifies the TOTP authentication code.
199   * It may be used in conjunction with the UNBOUNDID-TOTP mechanism.
200   */
201  public static final String SASL_OPTION_TOTP_PASSWORD = "totpPassword";
202
203
204
205  /**
206   * The name of the SASL option that specifies the trace string.  It may be
207   * used in conjunction with the ANONYMOUS mechanism.
208   */
209  public static final String SASL_OPTION_TRACE = "trace";
210
211
212
213  /**
214   * The name of the SASL option that specifies whether to use a Kerberos ticket
215   * cache.  It may be used in conjunction with the GSSAPI mechanism.
216   */
217  public static final String SASL_OPTION_USE_TICKET_CACHE = "useTicketCache";
218
219
220
221  /**
222   * A map with information about all supported SASL mechanisms, mapped from
223   * lowercase mechanism name to an object with information about that
224   * mechanism.
225   */
226  private static final Map<String,SASLMechanismInfo> SASL_MECHANISMS;
227
228
229
230  static
231  {
232    final TreeMap<String,SASLMechanismInfo> m = new TreeMap<>();
233
234    m.put(
235         StaticUtils.toLowerCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME),
236         new SASLMechanismInfo(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME,
237              INFO_SASL_ANONYMOUS_DESCRIPTION.get(), false, false,
238              new SASLOption(SASL_OPTION_TRACE,
239                   INFO_SASL_ANONYMOUS_OPTION_TRACE.get(), false, false)));
240
241    m.put(StaticUtils.toLowerCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME),
242         new SASLMechanismInfo(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME,
243              INFO_SASL_CRAM_MD5_DESCRIPTION.get(), true, true,
244              new SASLOption(SASL_OPTION_AUTH_ID,
245                   INFO_SASL_CRAM_MD5_OPTION_AUTH_ID.get(), true, false)));
246
247    m.put(
248         StaticUtils.toLowerCase(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME),
249         new SASLMechanismInfo(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME,
250              INFO_SASL_DIGEST_MD5_DESCRIPTION.get(), true, true,
251              new SASLOption(SASL_OPTION_AUTH_ID,
252                   INFO_SASL_DIGEST_MD5_OPTION_AUTH_ID.get(), true, false),
253              new SASLOption(SASL_OPTION_AUTHZ_ID,
254                   INFO_SASL_DIGEST_MD5_OPTION_AUTHZ_ID.get(), false, false),
255              new SASLOption(SASL_OPTION_REALM,
256                   INFO_SASL_DIGEST_MD5_OPTION_REALM.get(), false, false),
257              new SASLOption(SASL_OPTION_QOP,
258                   INFO_SASL_DIGEST_MD5_OPTION_QOP.get(), false, false)));
259
260    m.put(StaticUtils.toLowerCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME),
261         new SASLMechanismInfo(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME,
262              INFO_SASL_EXTERNAL_DESCRIPTION.get(), false, false));
263
264    m.put(StaticUtils.toLowerCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME),
265         new SASLMechanismInfo(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME,
266              INFO_SASL_GSSAPI_DESCRIPTION.get(), true, false,
267              new SASLOption(SASL_OPTION_AUTH_ID,
268                   INFO_SASL_GSSAPI_OPTION_AUTH_ID.get(), true, false),
269              new SASLOption(SASL_OPTION_AUTHZ_ID,
270                   INFO_SASL_GSSAPI_OPTION_AUTHZ_ID.get(), false, false),
271              new SASLOption(SASL_OPTION_CONFIG_FILE,
272                   INFO_SASL_GSSAPI_OPTION_CONFIG_FILE.get(), false, false),
273              new SASLOption(SASL_OPTION_DEBUG,
274                   INFO_SASL_GSSAPI_OPTION_DEBUG.get(), false, false),
275              new SASLOption(SASL_OPTION_KDC_ADDRESS,
276                   INFO_SASL_GSSAPI_OPTION_KDC_ADDRESS.get(), false, false),
277              new SASLOption(SASL_OPTION_PROTOCOL,
278                   INFO_SASL_GSSAPI_OPTION_PROTOCOL.get(), false, false),
279              new SASLOption(SASL_OPTION_REALM,
280                   INFO_SASL_GSSAPI_OPTION_REALM.get(), false, false),
281              new SASLOption(SASL_OPTION_QOP,
282                   INFO_SASL_GSSAPI_OPTION_QOP.get(), false, false),
283              new SASLOption(SASL_OPTION_RENEW_TGT,
284                   INFO_SASL_GSSAPI_OPTION_RENEW_TGT.get(), false, false),
285              new SASLOption(SASL_OPTION_REQUIRE_CACHE,
286                   INFO_SASL_GSSAPI_OPTION_REQUIRE_TICKET_CACHE.get(), false,
287                   false),
288              new SASLOption(SASL_OPTION_TICKET_CACHE_PATH,
289                   INFO_SASL_GSSAPI_OPTION_TICKET_CACHE.get(), false, false),
290              new SASLOption(SASL_OPTION_USE_TICKET_CACHE,
291                   INFO_SASL_GSSAPI_OPTION_USE_TICKET_CACHE.get(), false,
292                   false)));
293
294    m.put(StaticUtils.toLowerCase(PLAINBindRequest.PLAIN_MECHANISM_NAME),
295         new SASLMechanismInfo(PLAINBindRequest.PLAIN_MECHANISM_NAME,
296              INFO_SASL_PLAIN_DESCRIPTION.get(), true, true,
297              new SASLOption(SASL_OPTION_AUTH_ID,
298                   INFO_SASL_PLAIN_OPTION_AUTH_ID.get(), true, false),
299              new SASLOption(SASL_OPTION_AUTHZ_ID,
300                   INFO_SASL_PLAIN_OPTION_AUTHZ_ID.get(), false, false)));
301
302    m.put(StaticUtils.toLowerCase(
303         UnboundIDCertificatePlusPasswordBindRequest.
304              UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME),
305         new SASLMechanismInfo(
306              UnboundIDCertificatePlusPasswordBindRequest.
307                   UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME,
308              INFO_SASL_UNBOUNDID_CERT_PLUS_PASSWORD_DESCRIPTION.get(), true,
309              true));
310
311    m.put(
312         StaticUtils.toLowerCase(
313              UnboundIDDeliveredOTPBindRequest.
314                   UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME),
315         new SASLMechanismInfo(
316              UnboundIDDeliveredOTPBindRequest.
317                   UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME,
318              INFO_SASL_UNBOUNDID_DELIVERED_OTP_DESCRIPTION.get(), false, false,
319              new SASLOption(SASL_OPTION_AUTH_ID,
320                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTH_ID.get(), true, false),
321              new SASLOption(SASL_OPTION_AUTHZ_ID,
322                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTHZ_ID.get(), false,
323                   false),
324              new SASLOption(SASL_OPTION_OTP,
325                   INFO_SASL_UNBOUNDID_DELIVERED_OTP_OPTION_OTP.get(), true,
326                   false)));
327
328    m.put(
329         StaticUtils.toLowerCase(
330              UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME),
331         new SASLMechanismInfo(
332              UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME,
333              INFO_SASL_UNBOUNDID_TOTP_DESCRIPTION.get(), true, false,
334              new SASLOption(SASL_OPTION_AUTH_ID,
335                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTH_ID.get(), true, false),
336              new SASLOption(SASL_OPTION_AUTHZ_ID,
337                   INFO_SASL_UNBOUNDID_TOTP_OPTION_AUTHZ_ID.get(), false,
338                   false),
339              new SASLOption(SASL_OPTION_PROMPT_FOR_STATIC_PW,
340                   INFO_SASL_UNBOUNDID_TOTP_OPTION_PROMPT_FOR_PW.get(), false,
341                   false),
342              new SASLOption(SASL_OPTION_TOTP_PASSWORD,
343                   INFO_SASL_UNBOUNDID_TOTP_OPTION_TOTP_PASSWORD.get(), true,
344                   false)));
345
346    m.put(
347         StaticUtils.toLowerCase(
348              UnboundIDYubiKeyOTPBindRequest.
349                   UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME),
350         new SASLMechanismInfo(
351              UnboundIDYubiKeyOTPBindRequest.
352                   UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME,
353              INFO_SASL_UNBOUNDID_YUBIKEY_OTP_DESCRIPTION.get(), true, false,
354              new SASLOption(SASL_OPTION_AUTH_ID,
355                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_AUTH_ID.get(), true,
356                   false),
357              new SASLOption(SASL_OPTION_AUTHZ_ID,
358                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_AUTHZ_ID.get(), false,
359                   false),
360              new SASLOption(SASL_OPTION_OTP,
361                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_OTP.get(), true,
362                   false),
363              new SASLOption(SASL_OPTION_PROMPT_FOR_STATIC_PW,
364                   INFO_SASL_UNBOUNDID_YUBIKEY_OTP_OPTION_PROMPT_FOR_PW.get(),
365                   false, false)));
366
367    SASL_MECHANISMS = Collections.unmodifiableMap(m);
368  }
369
370
371
372  /**
373   * Prevent this utility class from being instantiated.
374   */
375  private SASLUtils()
376  {
377    // No implementation required.
378  }
379
380
381
382  /**
383   * Retrieves information about the SASL mechanisms supported for use by this
384   * class.
385   *
386   * @return  Information about the SASL mechanisms supported for use by this
387   *          class.
388   */
389  public static List<SASLMechanismInfo> getSupportedSASLMechanisms()
390  {
391    return Collections.unmodifiableList(
392         new ArrayList<>(SASL_MECHANISMS.values()));
393  }
394
395
396
397  /**
398   * Retrieves information about the specified SASL mechanism.
399   *
400   * @param  mechanism  The name of the SASL mechanism for which to retrieve
401   *                    information.  It will not be treated in a case-sensitive
402   *                    manner.
403   *
404   * @return  Information about the requested SASL mechanism, or {@code null} if
405   *          no information about the specified mechanism is available.
406   */
407  public static SASLMechanismInfo getSASLMechanismInfo(final String mechanism)
408  {
409    return SASL_MECHANISMS.get(StaticUtils.toLowerCase(mechanism));
410  }
411
412
413
414  /**
415   * Creates a new SASL bind request using the provided information.
416   *
417   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
418   *                    SASL mechanisms, this should be {@code null}, since the
419   *                    identity of the target user should be specified in some
420   *                    other way (e.g., via an "authID" SASL option).
421   * @param  password   The password to use for the SASL bind request.  It may
422   *                    be {@code null} if no password is required for the
423   *                    desired SASL mechanism.
424   * @param  mechanism  The name of the SASL mechanism to use.  It may be
425   *                    {@code null} if the provided set of options contains a
426   *                    "mech" option to specify the desired SASL option.
427   * @param  options    The set of SASL options to use when creating the bind
428   *                    request, in the form "name=value".  It may be
429   *                    {@code null} or empty if no SASL options are needed and
430   *                    a value was provided for the {@code mechanism} argument.
431   *                    If the set of SASL options includes a "mech" option,
432   *                    then the {@code mechanism} argument must be {@code null}
433   *                    or have a value that matches the value of the "mech"
434   *                    SASL option.
435   *
436   * @return  The SASL bind request created using the provided information.
437   *
438   * @throws  LDAPException  If a problem is encountered while trying to create
439   *                         the SASL bind request.
440   */
441  public static SASLBindRequest createBindRequest(final String bindDN,
442                                                  final String password,
443                                                  final String mechanism,
444                                                  final String... options)
445         throws LDAPException
446  {
447    return createBindRequest(bindDN,
448         (password == null ? null : StaticUtils.getBytes(password)), mechanism,
449         StaticUtils.toList(options));
450  }
451
452
453
454  /**
455   * Creates a new SASL bind request using the provided information.
456   *
457   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
458   *                    SASL mechanisms, this should be {@code null}, since the
459   *                    identity of the target user should be specified in some
460   *                    other way (e.g., via an "authID" SASL option).
461   * @param  password   The password to use for the SASL bind request.  It may
462   *                    be {@code null} if no password is required for the
463   *                    desired SASL mechanism.
464   * @param  mechanism  The name of the SASL mechanism to use.  It may be
465   *                    {@code null} if the provided set of options contains a
466   *                    "mech" option to specify the desired SASL option.
467   * @param  options    The set of SASL options to use when creating the bind
468   *                    request, in the form "name=value".  It may be
469   *                    {@code null} or empty if no SASL options are needed and
470   *                    a value was provided for the {@code mechanism} argument.
471   *                    If the set of SASL options includes a "mech" option,
472   *                    then the {@code mechanism} argument must be {@code null}
473   *                    or have a value that matches the value of the "mech"
474   *                    SASL option.
475   * @param  controls   The set of controls to include in the request.
476   *
477   * @return  The SASL bind request created using the provided information.
478   *
479   * @throws  LDAPException  If a problem is encountered while trying to create
480   *                         the SASL bind request.
481   */
482  public static SASLBindRequest createBindRequest(final String bindDN,
483                                                  final String password,
484                                                  final String mechanism,
485                                                  final List<String> options,
486                                                  final Control... controls)
487         throws LDAPException
488  {
489    return createBindRequest(bindDN,
490         (password == null
491              ? null
492              : StaticUtils.getBytes(password)), mechanism, options,
493         controls);
494  }
495
496
497
498  /**
499   * Creates a new SASL bind request using the provided information.
500   *
501   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
502   *                    SASL mechanisms, this should be {@code null}, since the
503   *                    identity of the target user should be specified in some
504   *                    other way (e.g., via an "authID" SASL option).
505   * @param  password   The password to use for the SASL bind request.  It may
506   *                    be {@code null} if no password is required for the
507   *                    desired SASL mechanism.
508   * @param  mechanism  The name of the SASL mechanism to use.  It may be
509   *                    {@code null} if the provided set of options contains a
510   *                    "mech" option to specify the desired SASL option.
511   * @param  options    The set of SASL options to use when creating the bind
512   *                    request, in the form "name=value".  It may be
513   *                    {@code null} or empty if no SASL options are needed and
514   *                    a value was provided for the {@code mechanism} argument.
515   *                    If the set of SASL options includes a "mech" option,
516   *                    then the {@code mechanism} argument must be {@code null}
517   *                    or have a value that matches the value of the "mech"
518   *                    SASL option.
519   *
520   * @return  The SASL bind request created using the provided information.
521   *
522   * @throws  LDAPException  If a problem is encountered while trying to create
523   *                         the SASL bind request.
524   */
525  public static SASLBindRequest createBindRequest(final String bindDN,
526                                                  final byte[] password,
527                                                  final String mechanism,
528                                                  final String... options)
529         throws LDAPException
530  {
531    return createBindRequest(bindDN, password, mechanism,
532         StaticUtils.toList(options));
533  }
534
535
536
537  /**
538   * Creates a new SASL bind request using the provided information.
539   *
540   * @param  bindDN     The bind DN to use for the SASL bind request.  For most
541   *                    SASL mechanisms, this should be {@code null}, since the
542   *                    identity of the target user should be specified in some
543   *                    other way (e.g., via an "authID" SASL option).
544   * @param  password   The password to use for the SASL bind request.  It may
545   *                    be {@code null} if no password is required for the
546   *                    desired SASL mechanism.
547   * @param  mechanism  The name of the SASL mechanism to use.  It may be
548   *                    {@code null} if the provided set of options contains a
549   *                    "mech" option to specify the desired SASL option.
550   * @param  options    The set of SASL options to use when creating the bind
551   *                    request, in the form "name=value".  It may be
552   *                    {@code null} or empty if no SASL options are needed and
553   *                    a value was provided for the {@code mechanism} argument.
554   *                    If the set of SASL options includes a "mech" option,
555   *                    then the {@code mechanism} argument must be {@code null}
556   *                    or have a value that matches the value of the "mech"
557   *                    SASL option.
558   * @param  controls   The set of controls to include in the request.
559   *
560   * @return  The SASL bind request created using the provided information.
561   *
562   * @throws  LDAPException  If a problem is encountered while trying to create
563   *                         the SASL bind request.
564   */
565  public static SASLBindRequest createBindRequest(final String bindDN,
566                                                  final byte[] password,
567                                                  final String mechanism,
568                                                  final List<String> options,
569                                                  final Control... controls)
570         throws LDAPException
571  {
572    return createBindRequest(bindDN, password, false, null, mechanism, options,
573         controls);
574  }
575
576
577
578  /**
579   * Creates a new SASL bind request using the provided information.
580   *
581   * @param  bindDN             The bind DN to use for the SASL bind request.
582   *                            For most SASL mechanisms, this should be
583   *                            {@code null}, since the identity of the target
584   *                            user should be specified in some other way
585   *                            (e.g., via an "authID" SASL option).
586   * @param  password           The password to use for the SASL bind request.
587   *                            It may be {@code null} if no password is
588   *                            required for the desired SASL mechanism.
589   * @param  promptForPassword  Indicates whether to interactively prompt for
590   *                            the password if one is needed but none was
591   *                            provided.
592   * @param  tool               The command-line tool whose input and output
593   *                            streams should be used when prompting for the
594   *                            bind password.  It may be {@code null} if
595   *                            {@code promptForPassword} is {@code false}.
596   * @param  mechanism          The name of the SASL mechanism to use.  It may
597   *                            be {@code null} if the provided set of options
598   *                            contains a "mech" option to specify the desired
599   *                            SASL option.
600   * @param  options            The set of SASL options to use when creating the
601   *                            bind request, in the form "name=value".  It may
602   *                            be {@code null} or empty if no SASL options are
603   *                            needed and a value was provided for the
604   *                            {@code mechanism} argument.  If the set of SASL
605   *                            options includes a "mech" option, then the
606   *                            {@code mechanism} argument must be {@code null}
607   *                            or have a value that matches the value of the
608   *                            "mech" SASL option.
609   * @param  controls           The set of controls to include in the request.
610   *
611   * @return  The SASL bind request created using the provided information.
612   *
613   * @throws  LDAPException  If a problem is encountered while trying to create
614   *                         the SASL bind request.
615   */
616  public static SASLBindRequest createBindRequest(final String bindDN,
617                                     final byte[] password,
618                                     final boolean promptForPassword,
619                                     final CommandLineTool tool,
620                                     final String mechanism,
621                                     final List<String> options,
622                                     final Control... controls)
623         throws LDAPException
624  {
625    if (promptForPassword)
626    {
627      Validator.ensureNotNull(tool);
628    }
629
630    // Parse the provided set of options to ensure that they are properly
631    // formatted in name-value form, and extract the SASL mechanism.
632    final String mech;
633    final Map<String,String> optionsMap = parseOptions(options);
634    final String mechOption =
635         optionsMap.remove(StaticUtils.toLowerCase(SASL_OPTION_MECHANISM));
636    if (mechOption != null)
637    {
638      mech = mechOption;
639      if ((mechanism != null) && (! mech.equalsIgnoreCase(mechanism)))
640      {
641        throw new LDAPException(ResultCode.PARAM_ERROR,
642             ERR_SASL_OPTION_MECH_CONFLICT.get(mechanism, mech));
643      }
644    }
645    else
646    {
647      mech = mechanism;
648    }
649
650    if (mech == null)
651    {
652      throw new LDAPException(ResultCode.PARAM_ERROR,
653           ERR_SASL_OPTION_NO_MECH.get());
654    }
655
656    if (mech.equalsIgnoreCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME))
657    {
658      return createANONYMOUSBindRequest(password, optionsMap, controls);
659    }
660    else if (mech.equalsIgnoreCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME))
661    {
662      return createCRAMMD5BindRequest(password, promptForPassword, tool,
663           optionsMap, controls);
664    }
665    else if (mech.equalsIgnoreCase(
666                  DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME))
667    {
668      return createDIGESTMD5BindRequest(password, promptForPassword, tool,
669           optionsMap, controls);
670    }
671    else if (mech.equalsIgnoreCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME))
672    {
673      return createEXTERNALBindRequest(password, optionsMap, controls);
674    }
675    else if (mech.equalsIgnoreCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME))
676    {
677      return createGSSAPIBindRequest(password, promptForPassword, tool,
678           optionsMap, controls);
679    }
680    else if (mech.equalsIgnoreCase(PLAINBindRequest.PLAIN_MECHANISM_NAME))
681    {
682      return createPLAINBindRequest(password, promptForPassword, tool,
683           optionsMap, controls);
684    }
685    else if (mech.equalsIgnoreCase(UnboundIDCertificatePlusPasswordBindRequest.
686             UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME))
687    {
688      return createUnboundIDCertificatePlusPasswordBindRequest(password, tool,
689           optionsMap, controls);
690    }
691    else if (mech.equalsIgnoreCase(UnboundIDDeliveredOTPBindRequest.
692             UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME))
693    {
694      return createUNBOUNDIDDeliveredOTPBindRequest(password, optionsMap,
695           controls);
696    }
697    else if (mech.equalsIgnoreCase(
698             UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME))
699    {
700      return createUNBOUNDIDTOTPBindRequest(password, tool, optionsMap,
701           controls);
702    }
703    else if (mech.equalsIgnoreCase(
704         UnboundIDYubiKeyOTPBindRequest.UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME))
705    {
706      return createUNBOUNDIDYUBIKEYOTPBindRequest(password, tool, optionsMap,
707           controls);
708    }
709    else
710    {
711      throw new LDAPException(ResultCode.PARAM_ERROR,
712           ERR_SASL_OPTION_UNSUPPORTED_MECH.get(mech));
713    }
714  }
715
716
717
718  /**
719   * Creates a SASL ANONYMOUS bind request using the provided set of options.
720   *
721   * @param  password  The password to use for the bind request.
722   * @param  options   The set of SASL options for the bind request.
723   * @param  controls  The set of controls to include in the request.
724   *
725   * @return  The SASL ANONYMOUS bind request that was created.
726   *
727   * @throws  LDAPException  If a problem is encountered while trying to create
728   *                         the SASL bind request.
729   */
730  private static ANONYMOUSBindRequest createANONYMOUSBindRequest(
731                                           final byte[] password,
732                                           final Map<String,String> options,
733                                           final Control[] controls)
734          throws LDAPException
735  {
736    if (password != null)
737    {
738      throw new LDAPException(ResultCode.PARAM_ERROR,
739           ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
740                ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME));
741    }
742
743
744    // The trace option is optional.
745    final String trace =
746         options.remove(StaticUtils.toLowerCase(SASL_OPTION_TRACE));
747
748    // Ensure no unsupported options were provided.
749    ensureNoUnsupportedOptions(options,
750         ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME);
751
752    return new ANONYMOUSBindRequest(trace, controls);
753  }
754
755
756
757  /**
758   * Creates a SASL CRAM-MD5 bind request using the provided password and set of
759   * options.
760   *
761   * @param  password           The password to use for the bind request.
762   * @param  promptForPassword  Indicates whether to interactively prompt for
763   *                            the password if one is needed but none was
764   *                            provided.
765   * @param  tool               The command-line tool whose input and output
766   *                            streams should be used when prompting for the
767   *                            bind password.  It may be {@code null} if
768   *                            {@code promptForPassword} is {@code false}.
769   * @param  options            The set of SASL options for the bind request.
770   * @param  controls           The set of controls to include in the request.
771   *
772   * @return  The SASL CRAM-MD5 bind request that was created.
773   *
774   * @throws  LDAPException  If a problem is encountered while trying to create
775   *                         the SASL bind request.
776   */
777  private static CRAMMD5BindRequest createCRAMMD5BindRequest(
778                                         final byte[] password,
779                                         final boolean promptForPassword,
780                                         final CommandLineTool tool,
781                                         final Map<String,String> options,
782                                         final Control[] controls)
783          throws LDAPException
784  {
785    final byte[] pw;
786    if (password == null)
787    {
788      if (promptForPassword)
789      {
790        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
791        pw = PasswordReader.readPassword();
792        tool.getOriginalOut().println();
793      }
794      else
795      {
796        throw new LDAPException(ResultCode.PARAM_ERROR,
797             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
798                  CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
799      }
800    }
801    else
802    {
803      pw = password;
804    }
805
806
807    // The authID option is required.
808    final String authID =
809         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
810    if (authID == null)
811    {
812      throw new LDAPException(ResultCode.PARAM_ERROR,
813           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
814                CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
815    }
816
817
818    // Ensure no unsupported options were provided.
819    ensureNoUnsupportedOptions(options,
820         CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME);
821
822    return new CRAMMD5BindRequest(authID, pw, controls);
823  }
824
825
826
827  /**
828   * Creates a SASL DIGEST-MD5 bind request using the provided password and set
829   * of options.
830   *
831   * @param  password           The password to use for the bind request.
832   * @param  promptForPassword  Indicates whether to interactively prompt for
833   *                            the password if one is needed but none was
834   *                            provided.
835   * @param  tool               The command-line tool whose input and output
836   *                            streams should be used when prompting for the
837   *                            bind password.  It may be {@code null} if
838   *                            {@code promptForPassword} is {@code false}.
839   * @param  options            The set of SASL options for the bind request.
840   * @param  controls           The set of controls to include in the request.
841   *
842   * @return  The SASL DIGEST-MD5 bind request that was created.
843   *
844   * @throws  LDAPException  If a problem is encountered while trying to create
845   *                         the SASL bind request.
846   */
847  private static DIGESTMD5BindRequest createDIGESTMD5BindRequest(
848                                           final byte[] password,
849                                           final boolean promptForPassword,
850                                           final CommandLineTool tool,
851                                           final Map<String,String> options,
852                                           final Control[] controls)
853          throws LDAPException
854  {
855    final byte[] pw;
856    if (password == null)
857    {
858      if (promptForPassword)
859      {
860        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
861        pw = PasswordReader.readPassword();
862        tool.getOriginalOut().println();
863      }
864      else
865      {
866        throw new LDAPException(ResultCode.PARAM_ERROR,
867             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
868                  CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
869      }
870    }
871    else
872    {
873      pw = password;
874    }
875
876    // The authID option is required.
877    final String authID =
878         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
879    if (authID == null)
880    {
881      throw new LDAPException(ResultCode.PARAM_ERROR,
882           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
883                CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
884    }
885
886    final DIGESTMD5BindRequestProperties properties =
887         new DIGESTMD5BindRequestProperties(authID, pw);
888
889    // The authzID option is optional.
890    properties.setAuthorizationID(
891         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID)));
892
893    // The realm option is optional.
894    properties.setRealm(options.remove(
895         StaticUtils.toLowerCase(SASL_OPTION_REALM)));
896
897    // The QoP option is optional, and may contain multiple values that need to
898    // be parsed.
899    final String qopString =
900         options.remove(StaticUtils.toLowerCase(SASL_OPTION_QOP));
901    if (qopString != null)
902    {
903      properties.setAllowedQoP(
904           SASLQualityOfProtection.decodeQoPList(qopString));
905    }
906
907    // Ensure no unsupported options were provided.
908    ensureNoUnsupportedOptions(options,
909         DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME);
910
911    return new DIGESTMD5BindRequest(properties, controls);
912  }
913
914
915
916  /**
917   * Creates a SASL EXTERNAL bind request using the provided set of options.
918   *
919   * @param  password  The password to use for the bind request.
920   * @param  options   The set of SASL options for the bind request.
921   * @param  controls  The set of controls to include in the request.
922   *
923   * @return  The SASL EXTERNAL bind request that was created.
924   *
925   * @throws  LDAPException  If a problem is encountered while trying to create
926   *                         the SASL bind request.
927   */
928  private static EXTERNALBindRequest createEXTERNALBindRequest(
929                                          final byte[] password,
930                                          final Map<String,String> options,
931                                          final Control[] controls)
932          throws LDAPException
933  {
934    if (password != null)
935    {
936      throw new LDAPException(ResultCode.PARAM_ERROR,
937           ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
938                EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME));
939    }
940
941    // Ensure no unsupported options were provided.
942    ensureNoUnsupportedOptions(options,
943         EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME);
944
945    return new EXTERNALBindRequest(controls);
946  }
947
948
949
950  /**
951   * Creates a SASL GSSAPI bind request using the provided password and set of
952   * options.
953   *
954   * @param  password           The password to use for the bind request.
955   * @param  promptForPassword  Indicates whether to interactively prompt for
956   *                            the password if one is needed but none was
957   *                            provided.
958   * @param  tool               The command-line tool whose input and output
959   *                            streams should be used when prompting for the
960   *                            bind password.  It may be {@code null} if
961   *                            {@code promptForPassword} is {@code false}.
962   * @param  options            The set of SASL options for the bind request.
963   * @param  controls           The set of controls to include in the request.
964   *
965   * @return  The SASL GSSAPI bind request that was created.
966   *
967   * @throws  LDAPException  If a problem is encountered while trying to create
968   *                         the SASL bind request.
969   */
970  private static GSSAPIBindRequest createGSSAPIBindRequest(
971                                        final byte[] password,
972                                        final boolean promptForPassword,
973                                        final CommandLineTool tool,
974                                        final Map<String,String> options,
975                                        final Control[] controls)
976          throws LDAPException
977  {
978    // The authID option is required.
979    final String authID =
980         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
981    if (authID == null)
982    {
983      throw new LDAPException(ResultCode.PARAM_ERROR,
984           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
985                GSSAPIBindRequest.GSSAPI_MECHANISM_NAME));
986    }
987    final GSSAPIBindRequestProperties gssapiProperties =
988         new GSSAPIBindRequestProperties(authID, password);
989
990    // The authzID option is optional.
991    gssapiProperties.setAuthorizationID(
992         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID)));
993
994    // The configFile option is optional.
995    gssapiProperties.setConfigFilePath(options.remove(
996         StaticUtils.toLowerCase(SASL_OPTION_CONFIG_FILE)));
997
998    // The debug option is optional.
999    gssapiProperties.setEnableGSSAPIDebugging(getBooleanValue(options,
1000         SASL_OPTION_DEBUG, false));
1001
1002    // The kdcAddress option is optional.
1003    gssapiProperties.setKDCAddress(options.remove(
1004         StaticUtils.toLowerCase(SASL_OPTION_KDC_ADDRESS)));
1005
1006    // The protocol option is optional.
1007    final String protocol =
1008         options.remove(StaticUtils.toLowerCase(SASL_OPTION_PROTOCOL));
1009    if (protocol != null)
1010    {
1011      gssapiProperties.setServicePrincipalProtocol(protocol);
1012    }
1013
1014    // The realm option is optional.
1015    gssapiProperties.setRealm(options.remove(
1016         StaticUtils.toLowerCase(SASL_OPTION_REALM)));
1017
1018    // The QoP option is optional, and may contain multiple values that need to
1019    // be parsed.
1020    final String qopString =
1021         options.remove(StaticUtils.toLowerCase(SASL_OPTION_QOP));
1022    if (qopString != null)
1023    {
1024      gssapiProperties.setAllowedQoP(
1025           SASLQualityOfProtection.decodeQoPList(qopString));
1026    }
1027
1028    // The renewTGT option is optional.
1029    gssapiProperties.setRenewTGT(getBooleanValue(options, SASL_OPTION_RENEW_TGT,
1030         false));
1031
1032    // The requireCache option is optional.
1033    gssapiProperties.setRequireCachedCredentials(getBooleanValue(options,
1034         SASL_OPTION_REQUIRE_CACHE, false));
1035
1036    // The ticketCache option is optional.
1037    gssapiProperties.setTicketCachePath(options.remove(
1038         StaticUtils.toLowerCase(SASL_OPTION_TICKET_CACHE_PATH)));
1039
1040    // The useTicketCache option is optional.
1041    gssapiProperties.setUseTicketCache(getBooleanValue(options,
1042         SASL_OPTION_USE_TICKET_CACHE, true));
1043
1044    // Ensure no unsupported options were provided.
1045    ensureNoUnsupportedOptions(options,
1046         GSSAPIBindRequest.GSSAPI_MECHANISM_NAME);
1047
1048    // A password must have been provided unless useTicketCache=true and
1049    // requireTicketCache=true.
1050    if (password == null)
1051    {
1052      if (! (gssapiProperties.useTicketCache() &&
1053           gssapiProperties.requireCachedCredentials()))
1054      {
1055        if (promptForPassword)
1056        {
1057          tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1058          gssapiProperties.setPassword(PasswordReader.readPassword());
1059          tool.getOriginalOut().println();
1060        }
1061        else
1062        {
1063          throw new LDAPException(ResultCode.PARAM_ERROR,
1064               ERR_SASL_OPTION_GSSAPI_PASSWORD_REQUIRED.get());
1065        }
1066      }
1067    }
1068
1069    return new GSSAPIBindRequest(gssapiProperties, controls);
1070  }
1071
1072
1073
1074  /**
1075   * Creates a SASL PLAIN bind request using the provided password and set of
1076   * options.
1077   *
1078   * @param  password           The password to use for the bind request.
1079   * @param  promptForPassword  Indicates whether to interactively prompt for
1080   *                            the password if one is needed but none was
1081   *                            provided.
1082   * @param  tool               The command-line tool whose input and output
1083   *                            streams should be used when prompting for the
1084   *                            bind password.  It may be {@code null} if
1085   *                            {@code promptForPassword} is {@code false}.
1086   * @param  options            The set of SASL options for the bind request.
1087   * @param  controls           The set of controls to include in the request.
1088   *
1089   * @return  The SASL PLAIN bind request that was created.
1090   *
1091   * @throws  LDAPException  If a problem is encountered while trying to create
1092   *                         the SASL bind request.
1093   */
1094  private static PLAINBindRequest createPLAINBindRequest(
1095                                        final byte[] password,
1096                                        final boolean promptForPassword,
1097                                        final CommandLineTool tool,
1098                                        final Map<String,String> options,
1099                                        final Control[] controls)
1100          throws LDAPException
1101  {
1102    final byte[] pw;
1103    if (password == null)
1104    {
1105      if (promptForPassword)
1106      {
1107        tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1108        pw = PasswordReader.readPassword();
1109        tool.getOriginalOut().println();
1110      }
1111      else
1112      {
1113        throw new LDAPException(ResultCode.PARAM_ERROR,
1114             ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
1115                  CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
1116      }
1117    }
1118    else
1119    {
1120      pw = password;
1121    }
1122
1123    // The authID option is required.
1124    final String authID =
1125         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
1126    if (authID == null)
1127    {
1128      throw new LDAPException(ResultCode.PARAM_ERROR,
1129           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
1130                PLAINBindRequest.PLAIN_MECHANISM_NAME));
1131    }
1132
1133    // The authzID option is optional.
1134    final String authzID =
1135         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));
1136
1137    // Ensure no unsupported options were provided.
1138    ensureNoUnsupportedOptions(options,
1139         PLAINBindRequest.PLAIN_MECHANISM_NAME);
1140
1141    return new PLAINBindRequest(authID, authzID, pw, controls);
1142  }
1143
1144
1145
1146  /**
1147   * Creates a SASL UNBOUNDID-CERTIFICATE-PLUS-PASSWORD bind request using the
1148   * provided set of options.
1149   *
1150   * @param  password  The password to use for the bind request.
1151   * @param  tool      The command-line tool whose input and output streams
1152   *                   should be used when prompting for the bind password.  It
1153   *                   may be {@code null} if {@code promptForPassword} is
1154   *                   {@code false}.
1155   * @param  options   The set of SASL options for the bind request.
1156   * @param  controls  The set of controls to include in the request.
1157   *
1158   * @return  The SASL UNBOUNDID-CERTIFICATE-PLUS-PASSWORD bind request that was
1159   *          created.
1160   *
1161   * @throws  LDAPException  If a problem is encountered while trying to create
1162   *                         the SASL bind request.
1163   */
1164  private static UnboundIDCertificatePlusPasswordBindRequest
1165                      createUnboundIDCertificatePlusPasswordBindRequest(
1166                           final byte[] password, final CommandLineTool tool,
1167                           final Map<String,String> options,
1168                           final Control[] controls)
1169          throws LDAPException
1170  {
1171    final byte[] pw;
1172    if (password == null)
1173    {
1174      tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1175      pw = PasswordReader.readPassword();
1176      tool.getOriginalOut().println();
1177    }
1178    else
1179    {
1180      pw = password;
1181    }
1182
1183    // Ensure no unsupported options were provided.
1184    ensureNoUnsupportedOptions(options,
1185         UnboundIDCertificatePlusPasswordBindRequest.
1186              UNBOUNDID_CERT_PLUS_PW_MECHANISM_NAME);
1187
1188    return new UnboundIDCertificatePlusPasswordBindRequest(pw, controls);
1189  }
1190
1191
1192
1193  /**
1194   * Creates a SASL UNBOUNDID-DELIVERED-OTP bind request using the provided
1195   * password and set of options.
1196   *
1197   * @param  password  The password to use for the bind request.
1198   * @param  options   The set of SASL options for the bind request.
1199   * @param  controls  The set of controls to include in the request.
1200   *
1201   * @return  The SASL UNBOUNDID-DELIVERED-OTP bind request that was created.
1202   *
1203   * @throws  LDAPException  If a problem is encountered while trying to create
1204   *                         the SASL bind request.
1205   */
1206  private static UnboundIDDeliveredOTPBindRequest
1207                      createUNBOUNDIDDeliveredOTPBindRequest(
1208                           final byte[] password,
1209                           final Map<String,String> options,
1210                           final Control... controls)
1211          throws LDAPException
1212  {
1213    if (password != null)
1214    {
1215      throw new LDAPException(ResultCode.PARAM_ERROR,
1216           ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
1217                UnboundIDDeliveredOTPBindRequest.
1218                     UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME));
1219    }
1220
1221    // The authID option is required.
1222    final String authID =
1223         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
1224    if (authID == null)
1225    {
1226      throw new LDAPException(ResultCode.PARAM_ERROR,
1227           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
1228                UnboundIDDeliveredOTPBindRequest.
1229                     UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME));
1230    }
1231
1232    // The OTP option is required.
1233    final String otp = options.remove(StaticUtils.toLowerCase(SASL_OPTION_OTP));
1234    if (otp == null)
1235    {
1236      throw new LDAPException(ResultCode.PARAM_ERROR,
1237           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_OTP,
1238                UnboundIDDeliveredOTPBindRequest.
1239                     UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME));
1240    }
1241
1242    // The authzID option is optional.
1243    final String authzID =
1244         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));
1245
1246    // Ensure no unsupported options were provided.
1247    ensureNoUnsupportedOptions(options,
1248         UnboundIDDeliveredOTPBindRequest.
1249              UNBOUNDID_DELIVERED_OTP_MECHANISM_NAME);
1250
1251    return new UnboundIDDeliveredOTPBindRequest(authID, authzID, otp, controls);
1252  }
1253
1254
1255
1256  /**
1257   * Creates a SASL UNBOUNDID-TOTP bind request using the provided password and
1258   * set of options.
1259   *
1260   * @param  password  The password to use for the bind request.
1261   * @param  tool      The command-line tool whose input and output streams
1262   *                   should be used when prompting for the bind password.  It
1263   *                   may be {@code null} if {@code promptForPassword} is
1264   *                   {@code false}.
1265   * @param  options   The set of SASL options for the bind request.
1266   * @param  controls  The set of controls to include in the request.
1267   *
1268   * @return  The SASL UNBOUNDID-TOTP bind request that was created.
1269   *
1270   * @throws  LDAPException  If a problem is encountered while trying to create
1271   *                         the SASL bind request.
1272   */
1273  private static SingleUseTOTPBindRequest createUNBOUNDIDTOTPBindRequest(
1274                                               final byte[] password,
1275                                               final CommandLineTool tool,
1276                                               final Map<String,String> options,
1277                                               final Control... controls)
1278          throws LDAPException
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                UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME));
1288    }
1289
1290    // The TOTP password option is required.
1291    final String totpPassword =
1292         options.remove(StaticUtils.toLowerCase(SASL_OPTION_TOTP_PASSWORD));
1293    if (totpPassword == null)
1294    {
1295      throw new LDAPException(ResultCode.PARAM_ERROR,
1296           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_TOTP_PASSWORD,
1297                UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME));
1298    }
1299
1300    // The authzID option is optional.
1301    byte[] pwBytes = password;
1302    final String authzID =
1303         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));
1304
1305    // The promptForStaticPassword option is optional.
1306    final String promptStr = options.remove(StaticUtils.toLowerCase(
1307         SASL_OPTION_PROMPT_FOR_STATIC_PW));
1308    if (promptStr != null)
1309    {
1310      if (promptStr.equalsIgnoreCase("true"))
1311      {
1312        if (pwBytes == null)
1313        {
1314          tool.getOriginalOut().print(INFO_SASL_ENTER_STATIC_PW.get());
1315          pwBytes = PasswordReader.readPassword();
1316          tool.getOriginalOut().println();
1317        }
1318        else
1319        {
1320          throw new LDAPException(ResultCode.PARAM_ERROR,
1321               ERR_SASL_PROMPT_FOR_PROVIDED_PW.get(
1322                    SASL_OPTION_PROMPT_FOR_STATIC_PW));
1323        }
1324      }
1325      else if (! promptStr.equalsIgnoreCase("false"))
1326      {
1327        throw new LDAPException(ResultCode.PARAM_ERROR,
1328             ERR_SASL_PROMPT_FOR_STATIC_PW_BAD_VALUE.get(
1329                  SASL_OPTION_PROMPT_FOR_STATIC_PW));
1330      }
1331    }
1332
1333    // Ensure no unsupported options were provided.
1334    ensureNoUnsupportedOptions(options,
1335         UnboundIDTOTPBindRequest.UNBOUNDID_TOTP_MECHANISM_NAME);
1336
1337    return new SingleUseTOTPBindRequest(authID, authzID, totpPassword, pwBytes,
1338         controls);
1339  }
1340
1341
1342
1343  /**
1344   * Creates a SASL UNBOUNDID-YUBIKEY-OTP bind request using the provided
1345   * password and set of options.
1346   *
1347   * @param  password  The password to use for the bind request.
1348   * @param  tool      The command-line tool whose input and output streams
1349   *                   should be used when prompting for the bind password.  It
1350   *                   may be {@code null} if {@code promptForPassword} is
1351   *                   {@code false}.
1352   * @param  options   The set of SASL options for the bind request.
1353   * @param  controls  The set of controls to include in the request.
1354   *
1355   * @return  The SASL UNBOUNDID-YUBIKEY-OTP bind request that was created.
1356   *
1357   * @throws  LDAPException  If a problem is encountered while trying to create
1358   *                         the SASL bind request.
1359   */
1360  private static UnboundIDYubiKeyOTPBindRequest
1361                      createUNBOUNDIDYUBIKEYOTPBindRequest(
1362                           final byte[] password, final CommandLineTool tool,
1363                           final Map<String,String> options,
1364                           final Control... controls)
1365          throws LDAPException
1366  {
1367    // The authID option is required.
1368    final String authID =
1369         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTH_ID));
1370    if (authID == null)
1371    {
1372      throw new LDAPException(ResultCode.PARAM_ERROR,
1373           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
1374                UnboundIDYubiKeyOTPBindRequest.
1375                     UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
1376    }
1377
1378    // The otp option is required.
1379    final String otp = options.remove(StaticUtils.toLowerCase(SASL_OPTION_OTP));
1380    if (otp == null)
1381    {
1382      throw new LDAPException(ResultCode.PARAM_ERROR,
1383           ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_OTP,
1384                UnboundIDYubiKeyOTPBindRequest.
1385                     UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
1386    }
1387
1388    // The authzID option is optional.
1389    final String authzID =
1390         options.remove(StaticUtils.toLowerCase(SASL_OPTION_AUTHZ_ID));
1391
1392    // The promptForStaticPassword option is optional.
1393    byte[] pwBytes = password;
1394    final String promptStr = options.remove(StaticUtils.toLowerCase(
1395         SASL_OPTION_PROMPT_FOR_STATIC_PW));
1396    if (promptStr != null)
1397    {
1398      if (promptStr.equalsIgnoreCase("true"))
1399      {
1400        if (pwBytes == null)
1401        {
1402          tool.getOriginalOut().print(INFO_SASL_ENTER_STATIC_PW.get());
1403          pwBytes = PasswordReader.readPassword();
1404          tool.getOriginalOut().println();
1405        }
1406        else
1407        {
1408          throw new LDAPException(ResultCode.PARAM_ERROR,
1409               ERR_SASL_PROMPT_FOR_PROVIDED_PW.get(
1410                    SASL_OPTION_PROMPT_FOR_STATIC_PW));
1411        }
1412      }
1413      else if (! promptStr.equalsIgnoreCase("false"))
1414      {
1415        throw new LDAPException(ResultCode.PARAM_ERROR,
1416             ERR_SASL_PROMPT_FOR_STATIC_PW_BAD_VALUE.get(
1417                  SASL_OPTION_PROMPT_FOR_STATIC_PW));
1418      }
1419    }
1420
1421    // Ensure no unsupported options were provided.
1422    ensureNoUnsupportedOptions(options,
1423         UnboundIDYubiKeyOTPBindRequest.UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME);
1424
1425    return new UnboundIDYubiKeyOTPBindRequest(authID, authzID, pwBytes, otp,
1426         controls);
1427  }
1428
1429
1430
1431  /**
1432   * Parses the provided list of SASL options.
1433   *
1434   * @param  options  The list of options to be parsed.
1435   *
1436   * @return  A map with the parsed set of options.
1437   *
1438   * @throws  LDAPException  If a problem is encountered while parsing options.
1439   */
1440  private static Map<String,String>
1441                      parseOptions(final List<String> options)
1442          throws LDAPException
1443  {
1444    if (options == null)
1445    {
1446      return new HashMap<>(0);
1447    }
1448
1449    final HashMap<String,String> m =
1450         new HashMap<>(StaticUtils.computeMapCapacity(options.size()));
1451    for (final String s : options)
1452    {
1453      final int equalPos = s.indexOf('=');
1454      if (equalPos < 0)
1455      {
1456        throw new LDAPException(ResultCode.PARAM_ERROR,
1457             ERR_SASL_OPTION_MISSING_EQUAL.get(s));
1458      }
1459      else if (equalPos == 0)
1460      {
1461        throw new LDAPException(ResultCode.PARAM_ERROR,
1462             ERR_SASL_OPTION_STARTS_WITH_EQUAL.get(s));
1463      }
1464
1465      final String name = s.substring(0, equalPos);
1466      final String value = s.substring(equalPos + 1);
1467      if (m.put(StaticUtils.toLowerCase(name), value) != null)
1468      {
1469        throw new LDAPException(ResultCode.PARAM_ERROR,
1470             ERR_SASL_OPTION_NOT_MULTI_VALUED.get(name));
1471      }
1472    }
1473
1474    return m;
1475  }
1476
1477
1478
1479  /**
1480   * Ensures that the provided map is empty, and will throw an exception if it
1481   * isn't.  This method is intended for internal use only.
1482   *
1483   * @param  options    The map of options to ensure is empty.
1484   * @param  mechanism  The associated SASL mechanism.
1485   *
1486   * @throws  LDAPException  If the map of SASL options is not empty.
1487   */
1488  @InternalUseOnly()
1489  public static void ensureNoUnsupportedOptions(
1490                          final Map<String,String> options,
1491                          final String mechanism)
1492          throws LDAPException
1493  {
1494    if (! options.isEmpty())
1495    {
1496      for (final String s : options.keySet())
1497      {
1498        throw new LDAPException(ResultCode.PARAM_ERROR,
1499             ERR_SASL_OPTION_UNSUPPORTED_FOR_MECH.get(s,mechanism));
1500      }
1501    }
1502  }
1503
1504
1505
1506  /**
1507   * Retrieves the value of the specified option and parses it as a boolean.
1508   * Values of "true", "t", "yes", "y", "on", and "1" will be treated as
1509   * {@code true}.  Values of "false", "f", "no", "n", "off", and "0" will be
1510   * treated as {@code false}.
1511   *
1512   * @param  m  The map from which to retrieve the option.  It must not be
1513   *            {@code null}.
1514   * @param  o  The name of the option to examine.
1515   * @param  d  The default value to use if the given option was not provided.
1516   *
1517   * @return  The parsed boolean value.
1518   *
1519   * @throws  LDAPException  If the option value cannot be parsed as a boolean.
1520   */
1521  static boolean getBooleanValue(final Map<String,String> m, final String o,
1522                                 final boolean d)
1523         throws LDAPException
1524  {
1525    final String s =
1526         StaticUtils.toLowerCase(m.remove(StaticUtils.toLowerCase(o)));
1527    if (s == null)
1528    {
1529      return d;
1530    }
1531    else if (s.equals("true") ||
1532             s.equals("t") ||
1533             s.equals("yes") ||
1534             s.equals("y") ||
1535             s.equals("on") ||
1536             s.equals("1"))
1537    {
1538      return true;
1539    }
1540    else if (s.equals("false") ||
1541             s.equals("f") ||
1542             s.equals("no") ||
1543             s.equals("n") ||
1544             s.equals("off") ||
1545             s.equals("0"))
1546    {
1547      return false;
1548    }
1549    else
1550    {
1551      throw new LDAPException(ResultCode.PARAM_ERROR,
1552           ERR_SASL_OPTION_MALFORMED_BOOLEAN_VALUE.get(o));
1553    }
1554  }
1555
1556
1557
1558  /**
1559   * Retrieves a string representation of the SASL usage information.  This will
1560   * include the supported SASL mechanisms and the properties that may be used
1561   * with each.
1562   *
1563   * @param  maxWidth  The maximum line width to use for the output.  If this is
1564   *                   less than or equal to zero, then no wrapping will be
1565   *                   performed.
1566   *
1567   * @return  A string representation of the usage information
1568   */
1569  public static String getUsageString(final int maxWidth)
1570  {
1571    final StringBuilder buffer = new StringBuilder();
1572
1573    for (final String line : getUsage(maxWidth))
1574    {
1575      buffer.append(line);
1576      buffer.append(StaticUtils.EOL);
1577    }
1578
1579    return buffer.toString();
1580  }
1581
1582
1583
1584  /**
1585   * Retrieves lines that make up the SASL usage information, optionally
1586   * wrapping long lines.
1587   *
1588   * @param  maxWidth  The maximum line width to use for the output.  If this is
1589   *                   less than or equal to zero, then no wrapping will be
1590   *                   performed.
1591   *
1592   * @return  The lines that make up the SASL usage information.
1593   */
1594  public static List<String> getUsage(final int maxWidth)
1595  {
1596    final ArrayList<String> lines = new ArrayList<>(100);
1597
1598    boolean first = true;
1599    for (final SASLMechanismInfo i : getSupportedSASLMechanisms())
1600    {
1601      if (first)
1602      {
1603        first = false;
1604      }
1605      else
1606      {
1607        lines.add("");
1608        lines.add("");
1609      }
1610
1611      lines.addAll(
1612           StaticUtils.wrapLine(INFO_SASL_HELP_MECHANISM.get(i.getName()),
1613                maxWidth));
1614      lines.add("");
1615
1616      for (final String line :
1617           StaticUtils.wrapLine(i.getDescription(), maxWidth - 4))
1618      {
1619        lines.add("  " + line);
1620      }
1621      lines.add("");
1622
1623      for (final String line :
1624           StaticUtils.wrapLine(INFO_SASL_HELP_MECHANISM_OPTIONS.get(
1625                i.getName()), maxWidth - 4))
1626      {
1627        lines.add("  " + line);
1628      }
1629
1630      if (i.acceptsPassword())
1631      {
1632        lines.add("");
1633        if (i.requiresPassword())
1634        {
1635          for (final String line :
1636               StaticUtils.wrapLine(INFO_SASL_HELP_PASSWORD_REQUIRED.get(
1637                    i.getName()), maxWidth - 4))
1638          {
1639            lines.add("  " + line);
1640          }
1641        }
1642        else
1643        {
1644          for (final String line :
1645               StaticUtils.wrapLine(INFO_SASL_HELP_PASSWORD_OPTIONAL.get(
1646                    i.getName()), maxWidth - 4))
1647          {
1648            lines.add("  " + line);
1649          }
1650        }
1651      }
1652
1653      for (final SASLOption o : i.getOptions())
1654      {
1655        lines.add("");
1656        lines.add("  * " + o.getName());
1657        for (final String line :
1658             StaticUtils.wrapLine(o.getDescription(), maxWidth - 14))
1659        {
1660          lines.add("       " + line);
1661        }
1662      }
1663    }
1664
1665    return lines;
1666  }
1667}