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