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