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