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