001/* 002 * Copyright 2011-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2011-2014 UnboundID Corp. 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.lang.reflect.InvocationTargetException; 026import java.lang.reflect.Method; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.TreeMap; 033 034import com.unboundid.ldap.sdk.ANONYMOUSBindRequest; 035import com.unboundid.ldap.sdk.Control; 036import com.unboundid.ldap.sdk.CRAMMD5BindRequest; 037import com.unboundid.ldap.sdk.DIGESTMD5BindRequest; 038import com.unboundid.ldap.sdk.DIGESTMD5BindRequestProperties; 039import com.unboundid.ldap.sdk.EXTERNALBindRequest; 040import com.unboundid.ldap.sdk.GSSAPIBindRequest; 041import com.unboundid.ldap.sdk.GSSAPIBindRequestProperties; 042import com.unboundid.ldap.sdk.LDAPException; 043import com.unboundid.ldap.sdk.PLAINBindRequest; 044import com.unboundid.ldap.sdk.ResultCode; 045import com.unboundid.ldap.sdk.SASLBindRequest; 046import com.unboundid.ldap.sdk.SASLQualityOfProtection; 047 048import static com.unboundid.util.StaticUtils.*; 049import static com.unboundid.util.UtilityMessages.*; 050 051 052 053/** 054 * This class provides a utility that may be used to help process SASL bind 055 * operations using the LDAP SDK. 056 */ 057@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 058public final class SASLUtils 059{ 060 /** 061 * The name of the SASL option that specifies the authentication ID. It may 062 * be used in conjunction with the CRAM-MD5, DIGEST-MD5, GSSAPI, and PLAIN 063 * mechanisms. 064 */ 065 public static final String SASL_OPTION_AUTH_ID = "authID"; 066 067 068 069 /** 070 * The name of the SASL option that specifies the authorization ID. It may 071 * be used in conjunction with the DIGEST-MD5, GSSAPI, and PLAIN mechanisms. 072 */ 073 public static final String SASL_OPTION_AUTHZ_ID = "authzID"; 074 075 076 077 /** 078 * The name of the SASL option that specifies the path to the JAAS config 079 * file. It may be used in conjunction with the GSSAPI mechanism. 080 */ 081 public static final String SASL_OPTION_CONFIG_FILE = "configFile"; 082 083 084 085 /** 086 * The name of the SASL option that indicates whether debugging should be 087 * enabled. It may be used in conjunction with the GSSAPI mechanism. 088 */ 089 public static final String SASL_OPTION_DEBUG = "debug"; 090 091 092 093 /** 094 * The name of the SASL option that specifies the KDC address. It may be used 095 * in conjunction with the GSSAPI mechanism. 096 */ 097 public static final String SASL_OPTION_KDC_ADDRESS = "kdcAddress"; 098 099 100 101 102 /** 103 * The name of the SASL option that specifies the desired SASL mechanism to 104 * use to authenticate to the server. 105 */ 106 public static final String SASL_OPTION_MECHANISM = "mech"; 107 108 109 110 /** 111 * The name of the SASL option that specifies the GSSAPI service principal 112 * protocol. It may be used in conjunction with the GSSAPI mechanism. 113 */ 114 public static final String SASL_OPTION_PROTOCOL = "protocol"; 115 116 117 118 /** 119 * The name of the SASL option that specifies the quality of protection that 120 * should be used for communication that occurs after the authentication has 121 * completed. 122 */ 123 public static final String SASL_OPTION_QOP = "qop"; 124 125 126 127 /** 128 * The name of the SASL option that specifies the realm name. It may be used 129 * in conjunction with the DIGEST-MD5 and GSSAPI mechanisms. 130 */ 131 public static final String SASL_OPTION_REALM = "realm"; 132 133 134 135 /** 136 * The name of the SASL option that indicates whether to require an existing 137 * Kerberos session from the ticket cache. It may be used in conjunction with 138 * the GSSAPI mechanism. 139 */ 140 public static final String SASL_OPTION_REQUIRE_CACHE = "requireCache"; 141 142 143 144 /** 145 * The name of the SASL option that indicates whether to attempt to renew the 146 * Kerberos TGT for an existing session. It may be used in conjunction with 147 * the GSSAPI mechanism. 148 */ 149 public static final String SASL_OPTION_RENEW_TGT = "renewTGT"; 150 151 152 153 /** 154 * The name of the SASL option that specifies the path to the Kerberos ticket 155 * cache to use. It may be used in conjunction with the GSSAPI mechanism. 156 */ 157 public static final String SASL_OPTION_TICKET_CACHE_PATH = "ticketCache"; 158 159 160 161 /** 162 * The name of the SASL option that specifies the trace string. It may be 163 * used in conjunction with the ANONYMOUS mechanism. 164 */ 165 public static final String SASL_OPTION_TRACE = "trace"; 166 167 168 169 /** 170 * The name of the SASL option that specifies whether to use a Kerberos ticket 171 * cache. It may be used in conjunction with the GSSAPI mechanism. 172 */ 173 public static final String SASL_OPTION_USE_TICKET_CACHE = "useTicketCache"; 174 175 176 177 /** 178 * A map with information about all supported SASL mechanisms, mapped from 179 * lowercase mechanism name to an object with information about that 180 * mechanism. 181 */ 182 private static final Map<String,SASLMechanismInfo> SASL_MECHANISMS; 183 184 185 186 static 187 { 188 final TreeMap<String,SASLMechanismInfo> m = 189 new TreeMap<String,SASLMechanismInfo>(); 190 191 m.put(toLowerCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME), 192 new SASLMechanismInfo(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME, 193 INFO_SASL_ANONYMOUS_DESCRIPTION.get(), false, false, 194 new SASLOption(SASL_OPTION_TRACE, 195 INFO_SASL_ANONYMOUS_OPTION_TRACE.get(), false, false))); 196 197 m.put(toLowerCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME), 198 new SASLMechanismInfo(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME, 199 INFO_SASL_CRAM_MD5_DESCRIPTION.get(), true, true, 200 new SASLOption(SASL_OPTION_AUTH_ID, 201 INFO_SASL_CRAM_MD5_OPTION_AUTH_ID.get(), true, false))); 202 203 m.put(toLowerCase(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME), 204 new SASLMechanismInfo(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME, 205 INFO_SASL_DIGEST_MD5_DESCRIPTION.get(), true, true, 206 new SASLOption(SASL_OPTION_AUTH_ID, 207 INFO_SASL_DIGEST_MD5_OPTION_AUTH_ID.get(), true, false), 208 new SASLOption(SASL_OPTION_AUTHZ_ID, 209 INFO_SASL_DIGEST_MD5_OPTION_AUTHZ_ID.get(), false, false), 210 new SASLOption(SASL_OPTION_REALM, 211 INFO_SASL_DIGEST_MD5_OPTION_REALM.get(), false, false), 212 new SASLOption(SASL_OPTION_QOP, 213 INFO_SASL_DIGEST_MD5_OPTION_QOP.get(), false, false))); 214 215 m.put(toLowerCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME), 216 new SASLMechanismInfo(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME, 217 INFO_SASL_EXTERNAL_DESCRIPTION.get(), false, false)); 218 219 m.put(toLowerCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME), 220 new SASLMechanismInfo(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME, 221 INFO_SASL_GSSAPI_DESCRIPTION.get(), true, false, 222 new SASLOption(SASL_OPTION_AUTH_ID, 223 INFO_SASL_GSSAPI_OPTION_AUTH_ID.get(), true, false), 224 new SASLOption(SASL_OPTION_AUTHZ_ID, 225 INFO_SASL_GSSAPI_OPTION_AUTHZ_ID.get(), false, false), 226 new SASLOption(SASL_OPTION_CONFIG_FILE, 227 INFO_SASL_GSSAPI_OPTION_CONFIG_FILE.get(), false, false), 228 new SASLOption(SASL_OPTION_DEBUG, 229 INFO_SASL_GSSAPI_OPTION_DEBUG.get(), false, false), 230 new SASLOption(SASL_OPTION_KDC_ADDRESS, 231 INFO_SASL_GSSAPI_OPTION_KDC_ADDRESS.get(), false, false), 232 new SASLOption(SASL_OPTION_PROTOCOL, 233 INFO_SASL_GSSAPI_OPTION_PROTOCOL.get(), false, false), 234 new SASLOption(SASL_OPTION_REALM, 235 INFO_SASL_GSSAPI_OPTION_REALM.get(), false, false), 236 new SASLOption(SASL_OPTION_QOP, 237 INFO_SASL_GSSAPI_OPTION_QOP.get(), false, false), 238 new SASLOption(SASL_OPTION_RENEW_TGT, 239 INFO_SASL_GSSAPI_OPTION_RENEW_TGT.get(), false, false), 240 new SASLOption(SASL_OPTION_REQUIRE_CACHE, 241 INFO_SASL_GSSAPI_OPTION_REQUIRE_TICKET_CACHE.get(), false, 242 false), 243 new SASLOption(SASL_OPTION_TICKET_CACHE_PATH, 244 INFO_SASL_GSSAPI_OPTION_TICKET_CACHE.get(), false, false), 245 new SASLOption(SASL_OPTION_USE_TICKET_CACHE, 246 INFO_SASL_GSSAPI_OPTION_USE_TICKET_CACHE.get(), false, 247 false))); 248 249 m.put(toLowerCase(PLAINBindRequest.PLAIN_MECHANISM_NAME), 250 new SASLMechanismInfo(PLAINBindRequest.PLAIN_MECHANISM_NAME, 251 INFO_SASL_PLAIN_DESCRIPTION.get(), true, true, 252 new SASLOption(SASL_OPTION_AUTH_ID, 253 INFO_SASL_PLAIN_OPTION_AUTH_ID.get(), true, false), 254 new SASLOption(SASL_OPTION_AUTHZ_ID, 255 INFO_SASL_PLAIN_OPTION_AUTHZ_ID.get(), false, false))); 256 257 258 // If Commercial Edition classes are available, then register support for 259 // any additional SASL mechanisms that it provides. 260 try 261 { 262 final Class<?> c = 263 Class.forName("com.unboundid.ldap.sdk.unboundidds.SASLHelper"); 264 final Method addCESASLInfoMethod = 265 c.getMethod("addCESASLInfo", Map.class); 266 addCESASLInfoMethod.invoke(null, m); 267 } 268 catch (final Exception e) 269 { 270 // This is fine. It simply means that the Commercial Edition classes 271 // are not available. 272 Debug.debugException(e); 273 } 274 275 SASL_MECHANISMS = Collections.unmodifiableMap(m); 276 } 277 278 279 280 /** 281 * Prevent this utility class from being instantiated. 282 */ 283 private SASLUtils() 284 { 285 // No implementation required. 286 } 287 288 289 290 /** 291 * Retrieves information about the SASL mechanisms supported for use by this 292 * class. 293 * 294 * @return Information about the SASL mechanisms supported for use by this 295 * class. 296 */ 297 public static List<SASLMechanismInfo> getSupportedSASLMechanisms() 298 { 299 return Collections.unmodifiableList(new ArrayList<SASLMechanismInfo>( 300 SASL_MECHANISMS.values())); 301 } 302 303 304 305 /** 306 * Retrieves information about the specified SASL mechanism. 307 * 308 * @param mechanism The name of the SASL mechanism for which to retrieve 309 * information. It will not be treated in a case-sensitive 310 * manner. 311 * 312 * @return Information about the requested SASL mechanism, or {@code null} if 313 * no information about the specified mechanism is available. 314 */ 315 public static SASLMechanismInfo getSASLMechanismInfo(final String mechanism) 316 { 317 return SASL_MECHANISMS.get(toLowerCase(mechanism)); 318 } 319 320 321 322 /** 323 * Creates a new SASL bind request using the provided information. 324 * 325 * @param bindDN The bind DN to use for the SASL bind request. For most 326 * SASL mechanisms, this should be {@code null}, since the 327 * identity of the target user should be specified in some 328 * other way (e.g., via an "authID" SASL option). 329 * @param password The password to use for the SASL bind request. It may 330 * be {@code null} if no password is required for the 331 * desired SASL mechanism. 332 * @param mechanism The name of the SASL mechanism to use. It may be 333 * {@code null} if the provided set of options contains a 334 * "mech" option to specify the desired SASL option. 335 * @param options The set of SASL options to use when creating the bind 336 * request, in the form "name=value". It may be 337 * {@code null} or empty if no SASL options are needed and 338 * a value was provided for the {@code mechanism} argument. 339 * If the set of SASL options includes a "mech" option, 340 * then the {@code mechanism} argument must be {@code null} 341 * or have a value that matches the value of the "mech" 342 * SASL option. 343 * 344 * @return The SASL bind request created using the provided information. 345 * 346 * @throws LDAPException If a problem is encountered while trying to create 347 * the SASL bind request. 348 */ 349 public static SASLBindRequest createBindRequest(final String bindDN, 350 final String password, 351 final String mechanism, 352 final String... options) 353 throws LDAPException 354 { 355 return createBindRequest(bindDN, 356 (password == null ? null : getBytes(password)), mechanism, 357 StaticUtils.toList(options)); 358 } 359 360 361 362 /** 363 * Creates a new SASL bind request using the provided information. 364 * 365 * @param bindDN The bind DN to use for the SASL bind request. For most 366 * SASL mechanisms, this should be {@code null}, since the 367 * identity of the target user should be specified in some 368 * other way (e.g., via an "authID" SASL option). 369 * @param password The password to use for the SASL bind request. It may 370 * be {@code null} if no password is required for the 371 * desired SASL mechanism. 372 * @param mechanism The name of the SASL mechanism to use. It may be 373 * {@code null} if the provided set of options contains a 374 * "mech" option to specify the desired SASL option. 375 * @param options The set of SASL options to use when creating the bind 376 * request, in the form "name=value". It may be 377 * {@code null} or empty if no SASL options are needed and 378 * a value was provided for the {@code mechanism} argument. 379 * If the set of SASL options includes a "mech" option, 380 * then the {@code mechanism} argument must be {@code null} 381 * or have a value that matches the value of the "mech" 382 * SASL option. 383 * @param controls The set of controls to include in the request. 384 * 385 * @return The SASL bind request created using the provided information. 386 * 387 * @throws LDAPException If a problem is encountered while trying to create 388 * the SASL bind request. 389 */ 390 public static SASLBindRequest createBindRequest(final String bindDN, 391 final String password, 392 final String mechanism, 393 final List<String> options, 394 final Control... controls) 395 throws LDAPException 396 { 397 return createBindRequest(bindDN, 398 (password == null ? null : getBytes(password)), mechanism, options, 399 controls); 400 } 401 402 403 404 /** 405 * Creates a new SASL bind request using the provided information. 406 * 407 * @param bindDN The bind DN to use for the SASL bind request. For most 408 * SASL mechanisms, this should be {@code null}, since the 409 * identity of the target user should be specified in some 410 * other way (e.g., via an "authID" SASL option). 411 * @param password The password to use for the SASL bind request. It may 412 * be {@code null} if no password is required for the 413 * desired SASL mechanism. 414 * @param mechanism The name of the SASL mechanism to use. It may be 415 * {@code null} if the provided set of options contains a 416 * "mech" option to specify the desired SASL option. 417 * @param options The set of SASL options to use when creating the bind 418 * request, in the form "name=value". It may be 419 * {@code null} or empty if no SASL options are needed and 420 * a value was provided for the {@code mechanism} argument. 421 * If the set of SASL options includes a "mech" option, 422 * then the {@code mechanism} argument must be {@code null} 423 * or have a value that matches the value of the "mech" 424 * SASL option. 425 * 426 * @return The SASL bind request created using the provided information. 427 * 428 * @throws LDAPException If a problem is encountered while trying to create 429 * the SASL bind request. 430 */ 431 public static SASLBindRequest createBindRequest(final String bindDN, 432 final byte[] password, 433 final String mechanism, 434 final String... options) 435 throws LDAPException 436 { 437 return createBindRequest(bindDN, password, mechanism, 438 StaticUtils.toList(options)); 439 } 440 441 442 443 /** 444 * Creates a new SASL bind request using the provided information. 445 * 446 * @param bindDN The bind DN to use for the SASL bind request. For most 447 * SASL mechanisms, this should be {@code null}, since the 448 * identity of the target user should be specified in some 449 * other way (e.g., via an "authID" SASL option). 450 * @param password The password to use for the SASL bind request. It may 451 * be {@code null} if no password is required for the 452 * desired SASL mechanism. 453 * @param mechanism The name of the SASL mechanism to use. It may be 454 * {@code null} if the provided set of options contains a 455 * "mech" option to specify the desired SASL option. 456 * @param options The set of SASL options to use when creating the bind 457 * request, in the form "name=value". It may be 458 * {@code null} or empty if no SASL options are needed and 459 * a value was provided for the {@code mechanism} argument. 460 * If the set of SASL options includes a "mech" option, 461 * then the {@code mechanism} argument must be {@code null} 462 * or have a value that matches the value of the "mech" 463 * SASL option. 464 * @param controls The set of controls to include in the request. 465 * 466 * @return The SASL bind request created using the provided information. 467 * 468 * @throws LDAPException If a problem is encountered while trying to create 469 * the SASL bind request. 470 */ 471 public static SASLBindRequest createBindRequest(final String bindDN, 472 final byte[] password, 473 final String mechanism, 474 final List<String> options, 475 final Control... controls) 476 throws LDAPException 477 { 478 // Parse the provided set of options to ensure that they are properly 479 // formatted in name-value form, and extract the SASL mechanism. 480 final String mech; 481 final Map<String,String> optionsMap = parseOptions(options); 482 final String mechOption = 483 optionsMap.remove(toLowerCase(SASL_OPTION_MECHANISM)); 484 if (mechOption != null) 485 { 486 mech = mechOption; 487 if ((mechanism != null) && (! mech.equalsIgnoreCase(mechanism))) 488 { 489 throw new LDAPException(ResultCode.PARAM_ERROR, 490 ERR_SASL_OPTION_MECH_CONFLICT.get(mechanism, mech)); 491 } 492 } 493 else 494 { 495 mech = mechanism; 496 } 497 498 if (mech == null) 499 { 500 throw new LDAPException(ResultCode.PARAM_ERROR, 501 ERR_SASL_OPTION_NO_MECH.get()); 502 } 503 504 if (mech.equalsIgnoreCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME)) 505 { 506 return createANONYMOUSBindRequest(password, optionsMap, controls); 507 } 508 else if (mech.equalsIgnoreCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)) 509 { 510 return createCRAMMD5BindRequest(password, optionsMap, controls); 511 } 512 else if (mech.equalsIgnoreCase( 513 DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME)) 514 { 515 return createDIGESTMD5BindRequest(password, optionsMap, controls); 516 } 517 else if (mech.equalsIgnoreCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME)) 518 { 519 return createEXTERNALBindRequest(password, optionsMap, controls); 520 } 521 else if (mech.equalsIgnoreCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME)) 522 { 523 return createGSSAPIBindRequest(password, optionsMap, controls); 524 } 525 else if (mech.equalsIgnoreCase(PLAINBindRequest.PLAIN_MECHANISM_NAME)) 526 { 527 return createPLAINBindRequest(password, optionsMap, controls); 528 } 529 else 530 { 531 // If Commercial Edition classes are available, then see if the 532 // authentication attempt is for one of the Commercial Edition mechanisms. 533 try 534 { 535 final Class<?> c = 536 Class.forName("com.unboundid.ldap.sdk.unboundidds.SASLHelper"); 537 final Method createBindRequestMethod = c.getMethod("createBindRequest", 538 String.class, StaticUtils.NO_BYTES.getClass(), String.class, 539 Map.class, StaticUtils.NO_CONTROLS.getClass()); 540 final Object bindRequestObject = createBindRequestMethod.invoke(null, 541 bindDN, password, mech, optionsMap, controls); 542 if (bindRequestObject != null) 543 { 544 return (SASLBindRequest) bindRequestObject; 545 } 546 } 547 catch (final Exception e) 548 { 549 Debug.debugException(e); 550 551 // This may mean that there was a problem with the provided arguments. 552 // If it's an InvocationTargetException that wraps an LDAPException, 553 // then throw that LDAPException. 554 if (e instanceof InvocationTargetException) 555 { 556 final InvocationTargetException ite = (InvocationTargetException) e; 557 final Throwable t = ite.getTargetException(); 558 if (t instanceof LDAPException) 559 { 560 throw (LDAPException) t; 561 } 562 } 563 } 564 565 throw new LDAPException(ResultCode.PARAM_ERROR, 566 ERR_SASL_OPTION_UNSUPPORTED_MECH.get(mech)); 567 } 568 } 569 570 571 572 /** 573 * Creates a SASL ANONYMOUS bind request using the provided set of options. 574 * 575 * @param password The password to use for the bind request. 576 * @param options The set of SASL options for the bind request. 577 * @param controls The set of controls to include in the request. 578 * 579 * @return The SASL ANONYMOUS bind request that was created. 580 * 581 * @throws LDAPException If a problem is encountered while trying to create 582 * the SASL bind request. 583 */ 584 private static ANONYMOUSBindRequest createANONYMOUSBindRequest( 585 final byte[] password, 586 final Map<String,String> options, 587 final Control[] controls) 588 throws LDAPException 589 { 590 if (password != null) 591 { 592 throw new LDAPException(ResultCode.PARAM_ERROR, 593 ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get( 594 ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME)); 595 } 596 597 598 // The trace option is optional. 599 final String trace = options.remove(toLowerCase(SASL_OPTION_TRACE)); 600 601 // Ensure no unsupported options were provided. 602 ensureNoUnsupportedOptions(options, 603 ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME); 604 605 return new ANONYMOUSBindRequest(trace, controls); 606 } 607 608 609 610 /** 611 * Creates a SASL CRAM-MD5 bind request using the provided password and set of 612 * options. 613 * 614 * @param password The password to use for the bind request. 615 * @param options The set of SASL options for the bind request. 616 * @param controls The set of controls to include in the request. 617 * 618 * @return The SASL CRAM-MD5 bind request that was created. 619 * 620 * @throws LDAPException If a problem is encountered while trying to create 621 * the SASL bind request. 622 */ 623 private static CRAMMD5BindRequest createCRAMMD5BindRequest( 624 final byte[] password, 625 final Map<String,String> options, 626 final Control[] controls) 627 throws LDAPException 628 { 629 if (password == null) 630 { 631 throw new LDAPException(ResultCode.PARAM_ERROR, 632 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get( 633 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)); 634 } 635 636 637 // The authID option is required. 638 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); 639 if (authID == null) 640 { 641 throw new LDAPException(ResultCode.PARAM_ERROR, 642 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, 643 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)); 644 } 645 646 647 // Ensure no unsupported options were provided. 648 ensureNoUnsupportedOptions(options, 649 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME); 650 651 return new CRAMMD5BindRequest(authID, password, controls); 652 } 653 654 655 656 /** 657 * Creates a SASL DIGEST-MD5 bind request using the provided password and set 658 * of options. 659 * 660 * @param password The password to use for the bind request. 661 * @param options The set of SASL options for the bind request. 662 * @param controls The set of controls to include in the request. 663 * 664 * @return The SASL DIGEST-MD5 bind request that was created. 665 * 666 * @throws LDAPException If a problem is encountered while trying to create 667 * the SASL bind request. 668 */ 669 private static DIGESTMD5BindRequest createDIGESTMD5BindRequest( 670 final byte[] password, 671 final Map<String,String> options, 672 final Control[] controls) 673 throws LDAPException 674 { 675 if (password == null) 676 { 677 throw new LDAPException(ResultCode.PARAM_ERROR, 678 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get( 679 DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME)); 680 } 681 682 // The authID option is required. 683 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); 684 if (authID == null) 685 { 686 throw new LDAPException(ResultCode.PARAM_ERROR, 687 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, 688 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)); 689 } 690 691 final DIGESTMD5BindRequestProperties properties = 692 new DIGESTMD5BindRequestProperties(authID, password); 693 694 // The authzID option is optional. 695 properties.setAuthorizationID( 696 options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID))); 697 698 // The realm option is optional. 699 properties.setRealm(options.remove(toLowerCase(SASL_OPTION_REALM))); 700 701 // The QoP option is optional, and may contain multiple values that need to 702 // be parsed. 703 final String qopString = options.remove(toLowerCase(SASL_OPTION_QOP)); 704 if (qopString != null) 705 { 706 properties.setAllowedQoP( 707 SASLQualityOfProtection.decodeQoPList(qopString)); 708 } 709 710 // Ensure no unsupported options were provided. 711 ensureNoUnsupportedOptions(options, 712 DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME); 713 714 return new DIGESTMD5BindRequest(properties, controls); 715 } 716 717 718 719 /** 720 * Creates a SASL EXTERNAL bind request using the provided set of options. 721 * 722 * @param password The password to use for the bind request. 723 * @param options The set of SASL options for the bind request. 724 * @param controls The set of controls to include in the request. 725 * 726 * @return The SASL EXTERNAL bind request that was created. 727 * 728 * @throws LDAPException If a problem is encountered while trying to create 729 * the SASL bind request. 730 */ 731 private static EXTERNALBindRequest createEXTERNALBindRequest( 732 final byte[] password, 733 final Map<String,String> options, 734 final Control[] controls) 735 throws LDAPException 736 { 737 if (password != null) 738 { 739 throw new LDAPException(ResultCode.PARAM_ERROR, 740 ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get( 741 EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME)); 742 } 743 744 // Ensure no unsupported options were provided. 745 ensureNoUnsupportedOptions(options, 746 EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME); 747 748 return new EXTERNALBindRequest(controls); 749 } 750 751 752 753 /** 754 * Creates a SASL GSSAPI bind request using the provided password and set of 755 * options. 756 * 757 * @param password The password to use for the bind request. 758 * @param options The set of SASL options for the bind request. 759 * @param controls The set of controls to include in the request. 760 * 761 * @return The SASL GSSAPI bind request that was created. 762 * 763 * @throws LDAPException If a problem is encountered while trying to create 764 * the SASL bind request. 765 */ 766 private static GSSAPIBindRequest createGSSAPIBindRequest( 767 final byte[] password, 768 final Map<String,String> options, 769 final Control[] controls) 770 throws LDAPException 771 { 772 // The authID option is required. 773 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); 774 if (authID == null) 775 { 776 throw new LDAPException(ResultCode.PARAM_ERROR, 777 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, 778 GSSAPIBindRequest.GSSAPI_MECHANISM_NAME)); 779 } 780 final GSSAPIBindRequestProperties gssapiProperties = 781 new GSSAPIBindRequestProperties(authID, password); 782 783 // The authzID option is optional. 784 gssapiProperties.setAuthorizationID( 785 options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID))); 786 787 // The configFile option is optional. 788 gssapiProperties.setConfigFilePath(options.remove(toLowerCase( 789 SASL_OPTION_CONFIG_FILE))); 790 791 // The debug option is optional. 792 gssapiProperties.setEnableGSSAPIDebugging(getBooleanValue(options, 793 SASL_OPTION_DEBUG, false)); 794 795 // The kdcAddress option is optional. 796 gssapiProperties.setKDCAddress(options.remove( 797 toLowerCase(SASL_OPTION_KDC_ADDRESS))); 798 799 // The protocol option is optional. 800 final String protocol = options.remove(toLowerCase(SASL_OPTION_PROTOCOL)); 801 if (protocol != null) 802 { 803 gssapiProperties.setServicePrincipalProtocol(protocol); 804 } 805 806 // The realm option is optional. 807 gssapiProperties.setRealm(options.remove(toLowerCase(SASL_OPTION_REALM))); 808 809 // The QoP option is optional, and may contain multiple values that need to 810 // be parsed. 811 final String qopString = options.remove(toLowerCase(SASL_OPTION_QOP)); 812 if (qopString != null) 813 { 814 gssapiProperties.setAllowedQoP( 815 SASLQualityOfProtection.decodeQoPList(qopString)); 816 } 817 818 // The renewTGT option is optional. 819 gssapiProperties.setRenewTGT(getBooleanValue(options, SASL_OPTION_RENEW_TGT, 820 false)); 821 822 // The requireCache option is optional. 823 gssapiProperties.setRequireCachedCredentials(getBooleanValue(options, 824 SASL_OPTION_REQUIRE_CACHE, false)); 825 826 // The ticketCache option is optional. 827 gssapiProperties.setTicketCachePath(options.remove(toLowerCase( 828 SASL_OPTION_TICKET_CACHE_PATH))); 829 830 // The useTicketCache option is optional. 831 gssapiProperties.setUseTicketCache(getBooleanValue(options, 832 SASL_OPTION_USE_TICKET_CACHE, true)); 833 834 // Ensure no unsupported options were provided. 835 ensureNoUnsupportedOptions(options, 836 GSSAPIBindRequest.GSSAPI_MECHANISM_NAME); 837 838 // A password must have been provided unless useTicketCache=true and 839 // requireTicketCache=true. 840 if (password == null) 841 { 842 if (! (gssapiProperties.useTicketCache() && 843 gssapiProperties.requireCachedCredentials())) 844 { 845 throw new LDAPException(ResultCode.PARAM_ERROR, 846 ERR_SASL_OPTION_GSSAPI_PASSWORD_REQUIRED.get()); 847 } 848 } 849 850 return new GSSAPIBindRequest(gssapiProperties, controls); 851 } 852 853 854 855 /** 856 * Creates a SASL PLAIN bind request using the provided password and set of 857 * options. 858 * 859 * @param password The password to use for the bind request. 860 * @param options The set of SASL options for the bind request. 861 * @param controls The set of controls to include in the request. 862 * 863 * @return The SASL PLAIN bind request that was created. 864 * 865 * @throws LDAPException If a problem is encountered while trying to create 866 * the SASL bind request. 867 */ 868 private static PLAINBindRequest createPLAINBindRequest( 869 final byte[] password, 870 final Map<String,String> options, 871 final Control[] controls) 872 throws LDAPException 873 { 874 if (password == null) 875 { 876 throw new LDAPException(ResultCode.PARAM_ERROR, 877 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get( 878 PLAINBindRequest.PLAIN_MECHANISM_NAME)); 879 } 880 881 // The authID option is required. 882 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); 883 if (authID == null) 884 { 885 throw new LDAPException(ResultCode.PARAM_ERROR, 886 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, 887 PLAINBindRequest.PLAIN_MECHANISM_NAME)); 888 } 889 890 // The authzID option is optional. 891 final String authzID = options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID)); 892 893 // Ensure no unsupported options were provided. 894 ensureNoUnsupportedOptions(options, 895 PLAINBindRequest.PLAIN_MECHANISM_NAME); 896 897 return new PLAINBindRequest(authID, authzID, password, controls); 898 } 899 900 901 902 /** 903 * Parses the provided list of SASL options. 904 * 905 * @param options The list of options to be parsed. 906 * 907 * @return A map with the parsed set of options. 908 * 909 * @throws LDAPException If a problem is encountered while parsing options. 910 */ 911 private static Map<String,String> 912 parseOptions(final List<String> options) 913 throws LDAPException 914 { 915 if (options == null) 916 { 917 return new HashMap<String,String>(0); 918 } 919 920 final HashMap<String,String> m = new HashMap<String,String>(options.size()); 921 for (final String s : options) 922 { 923 final int equalPos = s.indexOf('='); 924 if (equalPos < 0) 925 { 926 throw new LDAPException(ResultCode.PARAM_ERROR, 927 ERR_SASL_OPTION_MISSING_EQUAL.get(s)); 928 } 929 else if (equalPos == 0) 930 { 931 throw new LDAPException(ResultCode.PARAM_ERROR, 932 ERR_SASL_OPTION_STARTS_WITH_EQUAL.get(s)); 933 } 934 935 final String name = s.substring(0, equalPos); 936 final String value = s.substring(equalPos + 1); 937 if (m.put(toLowerCase(name), value) != null) 938 { 939 throw new LDAPException(ResultCode.PARAM_ERROR, 940 ERR_SASL_OPTION_NOT_MULTI_VALUED.get(name)); 941 } 942 } 943 944 return m; 945 } 946 947 948 949 /** 950 * Ensures that the provided map is empty, and will throw an exception if it 951 * isn't. This method is intended for internal use only. 952 * 953 * @param options The map of options to ensure is empty. 954 * @param mechanism The associated SASL mechanism. 955 * 956 * @throws LDAPException If the map of SASL options is not empty. 957 */ 958 @InternalUseOnly() 959 public static void ensureNoUnsupportedOptions( 960 final Map<String,String> options, 961 final String mechanism) 962 throws LDAPException 963 { 964 if (! options.isEmpty()) 965 { 966 for (final String s : options.keySet()) 967 { 968 throw new LDAPException(ResultCode.PARAM_ERROR, 969 ERR_SASL_OPTION_UNSUPPORTED_FOR_MECH.get(s,mechanism)); 970 } 971 } 972 } 973 974 975 976 /** 977 * Retrieves the value of the specified option and parses it as a boolean. 978 * Values of "true", "t", "yes", "y", "on", and "1" will be treated as 979 * {@code true}. Values of "false", "f", "no", "n", "off", and "0" will be 980 * treated as {@code false}. 981 * 982 * @param m The map from which to retrieve the option. It must not be 983 * {@code null}. 984 * @param o The name of the option to examine. 985 * @param d The default value to use if the given option was not provided. 986 * 987 * @return The parsed boolean value. 988 * 989 * @throws LDAPException If the option value cannot be parsed as a boolean. 990 */ 991 static boolean getBooleanValue(final Map<String,String> m, final String o, 992 final boolean d) 993 throws LDAPException 994 { 995 final String s = toLowerCase(m.remove(toLowerCase(o))); 996 if (s == null) 997 { 998 return d; 999 } 1000 else if (s.equals("true") || 1001 s.equals("t") || 1002 s.equals("yes") || 1003 s.equals("y") || 1004 s.equals("on") || 1005 s.equals("1")) 1006 { 1007 return true; 1008 } 1009 else if (s.equals("false") || 1010 s.equals("f") || 1011 s.equals("no") || 1012 s.equals("n") || 1013 s.equals("off") || 1014 s.equals("0")) 1015 { 1016 return false; 1017 } 1018 else 1019 { 1020 throw new LDAPException(ResultCode.PARAM_ERROR, 1021 ERR_SASL_OPTION_MALFORMED_BOOLEAN_VALUE.get(o)); 1022 } 1023 } 1024}