001/* 002 * Copyright 2011-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2011-2017 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 return createBindRequest(bindDN, password, false, null, mechanism, options, 479 controls); 480 } 481 482 483 484 /** 485 * Creates a new SASL bind request using the provided information. 486 * 487 * @param bindDN The bind DN to use for the SASL bind request. 488 * For most SASL mechanisms, this should be 489 * {@code null}, since the identity of the target 490 * user should be specified in some other way 491 * (e.g., via an "authID" SASL option). 492 * @param password The password to use for the SASL bind request. 493 * It may be {@code null} if no password is 494 * required for the desired SASL mechanism. 495 * @param promptForPassword Indicates whether to interactively prompt for 496 * the password if one is needed but none was 497 * provided. 498 * @param tool The command-line tool whose input and output 499 * streams should be used when prompting for the 500 * bind password. It may be {@code null} if 501 * {@code promptForPassword} is {@code false}. 502 * @param mechanism The name of the SASL mechanism to use. It may 503 * be {@code null} if the provided set of options 504 * contains a "mech" option to specify the desired 505 * SASL option. 506 * @param options The set of SASL options to use when creating the 507 * bind request, in the form "name=value". It may 508 * be {@code null} or empty if no SASL options are 509 * needed and a value was provided for the 510 * {@code mechanism} argument. If the set of SASL 511 * options includes a "mech" option, then the 512 * {@code mechanism} argument must be {@code null} 513 * or have a value that matches the value of the 514 * "mech" SASL option. 515 * @param controls The set of controls to include in the request. 516 * 517 * @return The SASL bind request created using the provided information. 518 * 519 * @throws LDAPException If a problem is encountered while trying to create 520 * the SASL bind request. 521 */ 522 public static SASLBindRequest createBindRequest(final String bindDN, 523 final byte[] password, 524 final boolean promptForPassword, 525 final CommandLineTool tool, 526 final String mechanism, 527 final List<String> options, 528 final Control... controls) 529 throws LDAPException 530 { 531 if (promptForPassword) 532 { 533 Validator.ensureNotNull(tool); 534 } 535 536 // Parse the provided set of options to ensure that they are properly 537 // formatted in name-value form, and extract the SASL mechanism. 538 final String mech; 539 final Map<String,String> optionsMap = parseOptions(options); 540 final String mechOption = 541 optionsMap.remove(toLowerCase(SASL_OPTION_MECHANISM)); 542 if (mechOption != null) 543 { 544 mech = mechOption; 545 if ((mechanism != null) && (! mech.equalsIgnoreCase(mechanism))) 546 { 547 throw new LDAPException(ResultCode.PARAM_ERROR, 548 ERR_SASL_OPTION_MECH_CONFLICT.get(mechanism, mech)); 549 } 550 } 551 else 552 { 553 mech = mechanism; 554 } 555 556 if (mech == null) 557 { 558 throw new LDAPException(ResultCode.PARAM_ERROR, 559 ERR_SASL_OPTION_NO_MECH.get()); 560 } 561 562 if (mech.equalsIgnoreCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME)) 563 { 564 return createANONYMOUSBindRequest(password, optionsMap, controls); 565 } 566 else if (mech.equalsIgnoreCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)) 567 { 568 return createCRAMMD5BindRequest(password, promptForPassword, tool, 569 optionsMap, controls); 570 } 571 else if (mech.equalsIgnoreCase( 572 DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME)) 573 { 574 return createDIGESTMD5BindRequest(password, promptForPassword, tool, 575 optionsMap, controls); 576 } 577 else if (mech.equalsIgnoreCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME)) 578 { 579 return createEXTERNALBindRequest(password, optionsMap, controls); 580 } 581 else if (mech.equalsIgnoreCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME)) 582 { 583 return createGSSAPIBindRequest(password, promptForPassword, tool, 584 optionsMap, controls); 585 } 586 else if (mech.equalsIgnoreCase(PLAINBindRequest.PLAIN_MECHANISM_NAME)) 587 { 588 return createPLAINBindRequest(password, promptForPassword, tool, 589 optionsMap, controls); 590 } 591 else 592 { 593 // If Commercial Edition classes are available, then see if the 594 // authentication attempt is for one of the Commercial Edition mechanisms. 595 try 596 { 597 final Class<?> c = 598 Class.forName("com.unboundid.ldap.sdk.unboundidds.SASLHelper"); 599 final Method createBindRequestMethod = c.getMethod("createBindRequest", 600 String.class, StaticUtils.NO_BYTES.getClass(), String.class, 601 CommandLineTool.class, Map.class, 602 StaticUtils.NO_CONTROLS.getClass()); 603 final Object bindRequestObject = createBindRequestMethod.invoke(null, 604 bindDN, password, mech, tool, optionsMap, controls); 605 if (bindRequestObject != null) 606 { 607 return (SASLBindRequest) bindRequestObject; 608 } 609 } 610 catch (final Exception e) 611 { 612 Debug.debugException(e); 613 614 // This may mean that there was a problem with the provided arguments. 615 // If it's an InvocationTargetException that wraps an LDAPException, 616 // then throw that LDAPException. 617 if (e instanceof InvocationTargetException) 618 { 619 final InvocationTargetException ite = (InvocationTargetException) e; 620 final Throwable t = ite.getTargetException(); 621 if (t instanceof LDAPException) 622 { 623 throw (LDAPException) t; 624 } 625 } 626 } 627 628 throw new LDAPException(ResultCode.PARAM_ERROR, 629 ERR_SASL_OPTION_UNSUPPORTED_MECH.get(mech)); 630 } 631 } 632 633 634 635 /** 636 * Creates a SASL ANONYMOUS bind request using the provided set of options. 637 * 638 * @param password The password to use for the bind request. 639 * @param options The set of SASL options for the bind request. 640 * @param controls The set of controls to include in the request. 641 * 642 * @return The SASL ANONYMOUS bind request that was created. 643 * 644 * @throws LDAPException If a problem is encountered while trying to create 645 * the SASL bind request. 646 */ 647 private static ANONYMOUSBindRequest createANONYMOUSBindRequest( 648 final byte[] password, 649 final Map<String,String> options, 650 final Control[] controls) 651 throws LDAPException 652 { 653 if (password != null) 654 { 655 throw new LDAPException(ResultCode.PARAM_ERROR, 656 ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get( 657 ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME)); 658 } 659 660 661 // The trace option is optional. 662 final String trace = options.remove(toLowerCase(SASL_OPTION_TRACE)); 663 664 // Ensure no unsupported options were provided. 665 ensureNoUnsupportedOptions(options, 666 ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME); 667 668 return new ANONYMOUSBindRequest(trace, controls); 669 } 670 671 672 673 /** 674 * Creates a SASL CRAM-MD5 bind request using the provided password and set of 675 * options. 676 * 677 * @param password The password to use for the bind request. 678 * @param promptForPassword Indicates whether to interactively prompt for 679 * the password if one is needed but none was 680 * provided. 681 * @param tool The command-line tool whose input and output 682 * streams should be used when prompting for the 683 * bind password. It may be {@code null} if 684 * {@code promptForPassword} is {@code false}. 685 * @param options The set of SASL options for the bind request. 686 * @param controls The set of controls to include in the request. 687 * 688 * @return The SASL CRAM-MD5 bind request that was created. 689 * 690 * @throws LDAPException If a problem is encountered while trying to create 691 * the SASL bind request. 692 */ 693 private static CRAMMD5BindRequest createCRAMMD5BindRequest( 694 final byte[] password, 695 final boolean promptForPassword, 696 final CommandLineTool tool, 697 final Map<String,String> options, 698 final Control[] controls) 699 throws LDAPException 700 { 701 final byte[] pw; 702 if (password == null) 703 { 704 if (promptForPassword) 705 { 706 tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get()); 707 pw = PasswordReader.readPassword(); 708 tool.getOriginalOut().println(); 709 } 710 else 711 { 712 throw new LDAPException(ResultCode.PARAM_ERROR, 713 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get( 714 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)); 715 } 716 } 717 else 718 { 719 pw = password; 720 } 721 722 723 // The authID option is required. 724 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); 725 if (authID == null) 726 { 727 throw new LDAPException(ResultCode.PARAM_ERROR, 728 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, 729 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)); 730 } 731 732 733 // Ensure no unsupported options were provided. 734 ensureNoUnsupportedOptions(options, 735 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME); 736 737 return new CRAMMD5BindRequest(authID, pw, controls); 738 } 739 740 741 742 /** 743 * Creates a SASL DIGEST-MD5 bind request using the provided password and set 744 * of options. 745 * 746 * @param password The password to use for the bind request. 747 * @param promptForPassword Indicates whether to interactively prompt for 748 * the password if one is needed but none was 749 * provided. 750 * @param tool The command-line tool whose input and output 751 * streams should be used when prompting for the 752 * bind password. It may be {@code null} if 753 * {@code promptForPassword} is {@code false}. 754 * @param options The set of SASL options for the bind request. 755 * @param controls The set of controls to include in the request. 756 * 757 * @return The SASL DIGEST-MD5 bind request that was created. 758 * 759 * @throws LDAPException If a problem is encountered while trying to create 760 * the SASL bind request. 761 */ 762 private static DIGESTMD5BindRequest createDIGESTMD5BindRequest( 763 final byte[] password, 764 final boolean promptForPassword, 765 final CommandLineTool tool, 766 final Map<String,String> options, 767 final Control[] controls) 768 throws LDAPException 769 { 770 final byte[] pw; 771 if (password == null) 772 { 773 if (promptForPassword) 774 { 775 tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get()); 776 pw = PasswordReader.readPassword(); 777 tool.getOriginalOut().println(); 778 } 779 else 780 { 781 throw new LDAPException(ResultCode.PARAM_ERROR, 782 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get( 783 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)); 784 } 785 } 786 else 787 { 788 pw = password; 789 } 790 791 // The authID option is required. 792 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); 793 if (authID == null) 794 { 795 throw new LDAPException(ResultCode.PARAM_ERROR, 796 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, 797 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)); 798 } 799 800 final DIGESTMD5BindRequestProperties properties = 801 new DIGESTMD5BindRequestProperties(authID, pw); 802 803 // The authzID option is optional. 804 properties.setAuthorizationID( 805 options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID))); 806 807 // The realm option is optional. 808 properties.setRealm(options.remove(toLowerCase(SASL_OPTION_REALM))); 809 810 // The QoP option is optional, and may contain multiple values that need to 811 // be parsed. 812 final String qopString = options.remove(toLowerCase(SASL_OPTION_QOP)); 813 if (qopString != null) 814 { 815 properties.setAllowedQoP( 816 SASLQualityOfProtection.decodeQoPList(qopString)); 817 } 818 819 // Ensure no unsupported options were provided. 820 ensureNoUnsupportedOptions(options, 821 DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME); 822 823 return new DIGESTMD5BindRequest(properties, controls); 824 } 825 826 827 828 /** 829 * Creates a SASL EXTERNAL bind request using the provided set of options. 830 * 831 * @param password The password to use for the bind request. 832 * @param options The set of SASL options for the bind request. 833 * @param controls The set of controls to include in the request. 834 * 835 * @return The SASL EXTERNAL bind request that was created. 836 * 837 * @throws LDAPException If a problem is encountered while trying to create 838 * the SASL bind request. 839 */ 840 private static EXTERNALBindRequest createEXTERNALBindRequest( 841 final byte[] password, 842 final Map<String,String> options, 843 final Control[] controls) 844 throws LDAPException 845 { 846 if (password != null) 847 { 848 throw new LDAPException(ResultCode.PARAM_ERROR, 849 ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get( 850 EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME)); 851 } 852 853 // Ensure no unsupported options were provided. 854 ensureNoUnsupportedOptions(options, 855 EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME); 856 857 return new EXTERNALBindRequest(controls); 858 } 859 860 861 862 /** 863 * Creates a SASL GSSAPI bind request using the provided password and set of 864 * options. 865 * 866 * @param password The password to use for the bind request. 867 * @param promptForPassword Indicates whether to interactively prompt for 868 * the password if one is needed but none was 869 * provided. 870 * @param tool The command-line tool whose input and output 871 * streams should be used when prompting for the 872 * bind password. It may be {@code null} if 873 * {@code promptForPassword} is {@code false}. 874 * @param options The set of SASL options for the bind request. 875 * @param controls The set of controls to include in the request. 876 * 877 * @return The SASL GSSAPI bind request that was created. 878 * 879 * @throws LDAPException If a problem is encountered while trying to create 880 * the SASL bind request. 881 */ 882 private static GSSAPIBindRequest createGSSAPIBindRequest( 883 final byte[] password, 884 final boolean promptForPassword, 885 final CommandLineTool tool, 886 final Map<String,String> options, 887 final Control[] controls) 888 throws LDAPException 889 { 890 // The authID option is required. 891 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); 892 if (authID == null) 893 { 894 throw new LDAPException(ResultCode.PARAM_ERROR, 895 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, 896 GSSAPIBindRequest.GSSAPI_MECHANISM_NAME)); 897 } 898 final GSSAPIBindRequestProperties gssapiProperties = 899 new GSSAPIBindRequestProperties(authID, password); 900 901 // The authzID option is optional. 902 gssapiProperties.setAuthorizationID( 903 options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID))); 904 905 // The configFile option is optional. 906 gssapiProperties.setConfigFilePath(options.remove(toLowerCase( 907 SASL_OPTION_CONFIG_FILE))); 908 909 // The debug option is optional. 910 gssapiProperties.setEnableGSSAPIDebugging(getBooleanValue(options, 911 SASL_OPTION_DEBUG, false)); 912 913 // The kdcAddress option is optional. 914 gssapiProperties.setKDCAddress(options.remove( 915 toLowerCase(SASL_OPTION_KDC_ADDRESS))); 916 917 // The protocol option is optional. 918 final String protocol = options.remove(toLowerCase(SASL_OPTION_PROTOCOL)); 919 if (protocol != null) 920 { 921 gssapiProperties.setServicePrincipalProtocol(protocol); 922 } 923 924 // The realm option is optional. 925 gssapiProperties.setRealm(options.remove(toLowerCase(SASL_OPTION_REALM))); 926 927 // The QoP option is optional, and may contain multiple values that need to 928 // be parsed. 929 final String qopString = options.remove(toLowerCase(SASL_OPTION_QOP)); 930 if (qopString != null) 931 { 932 gssapiProperties.setAllowedQoP( 933 SASLQualityOfProtection.decodeQoPList(qopString)); 934 } 935 936 // The renewTGT option is optional. 937 gssapiProperties.setRenewTGT(getBooleanValue(options, SASL_OPTION_RENEW_TGT, 938 false)); 939 940 // The requireCache option is optional. 941 gssapiProperties.setRequireCachedCredentials(getBooleanValue(options, 942 SASL_OPTION_REQUIRE_CACHE, false)); 943 944 // The ticketCache option is optional. 945 gssapiProperties.setTicketCachePath(options.remove(toLowerCase( 946 SASL_OPTION_TICKET_CACHE_PATH))); 947 948 // The useTicketCache option is optional. 949 gssapiProperties.setUseTicketCache(getBooleanValue(options, 950 SASL_OPTION_USE_TICKET_CACHE, true)); 951 952 // Ensure no unsupported options were provided. 953 ensureNoUnsupportedOptions(options, 954 GSSAPIBindRequest.GSSAPI_MECHANISM_NAME); 955 956 // A password must have been provided unless useTicketCache=true and 957 // requireTicketCache=true. 958 if (password == null) 959 { 960 if (! (gssapiProperties.useTicketCache() && 961 gssapiProperties.requireCachedCredentials())) 962 { 963 if (promptForPassword) 964 { 965 tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get()); 966 gssapiProperties.setPassword(PasswordReader.readPassword()); 967 tool.getOriginalOut().println(); 968 } 969 else 970 { 971 throw new LDAPException(ResultCode.PARAM_ERROR, 972 ERR_SASL_OPTION_GSSAPI_PASSWORD_REQUIRED.get()); 973 } 974 } 975 } 976 977 return new GSSAPIBindRequest(gssapiProperties, controls); 978 } 979 980 981 982 /** 983 * Creates a SASL PLAIN bind request using the provided password and set of 984 * options. 985 * 986 * @param password The password to use for the bind request. 987 * @param promptForPassword Indicates whether to interactively prompt for 988 * the password if one is needed but none was 989 * provided. 990 * @param tool The command-line tool whose input and output 991 * streams should be used when prompting for the 992 * bind password. It may be {@code null} if 993 * {@code promptForPassword} is {@code false}. 994 * @param options The set of SASL options for the bind request. 995 * @param controls The set of controls to include in the request. 996 * 997 * @return The SASL PLAIN bind request that was created. 998 * 999 * @throws LDAPException If a problem is encountered while trying to create 1000 * the SASL bind request. 1001 */ 1002 private static PLAINBindRequest createPLAINBindRequest( 1003 final byte[] password, 1004 final boolean promptForPassword, 1005 final CommandLineTool tool, 1006 final Map<String,String> options, 1007 final Control[] controls) 1008 throws LDAPException 1009 { 1010 final byte[] pw; 1011 if (password == null) 1012 { 1013 if (promptForPassword) 1014 { 1015 tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get()); 1016 pw = PasswordReader.readPassword(); 1017 tool.getOriginalOut().println(); 1018 } 1019 else 1020 { 1021 throw new LDAPException(ResultCode.PARAM_ERROR, 1022 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get( 1023 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)); 1024 } 1025 } 1026 else 1027 { 1028 pw = password; 1029 } 1030 1031 // The authID option is required. 1032 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); 1033 if (authID == null) 1034 { 1035 throw new LDAPException(ResultCode.PARAM_ERROR, 1036 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, 1037 PLAINBindRequest.PLAIN_MECHANISM_NAME)); 1038 } 1039 1040 // The authzID option is optional. 1041 final String authzID = options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID)); 1042 1043 // Ensure no unsupported options were provided. 1044 ensureNoUnsupportedOptions(options, 1045 PLAINBindRequest.PLAIN_MECHANISM_NAME); 1046 1047 return new PLAINBindRequest(authID, authzID, pw, controls); 1048 } 1049 1050 1051 1052 /** 1053 * Parses the provided list of SASL options. 1054 * 1055 * @param options The list of options to be parsed. 1056 * 1057 * @return A map with the parsed set of options. 1058 * 1059 * @throws LDAPException If a problem is encountered while parsing options. 1060 */ 1061 private static Map<String,String> 1062 parseOptions(final List<String> options) 1063 throws LDAPException 1064 { 1065 if (options == null) 1066 { 1067 return new HashMap<String,String>(0); 1068 } 1069 1070 final HashMap<String,String> m = new HashMap<String,String>(options.size()); 1071 for (final String s : options) 1072 { 1073 final int equalPos = s.indexOf('='); 1074 if (equalPos < 0) 1075 { 1076 throw new LDAPException(ResultCode.PARAM_ERROR, 1077 ERR_SASL_OPTION_MISSING_EQUAL.get(s)); 1078 } 1079 else if (equalPos == 0) 1080 { 1081 throw new LDAPException(ResultCode.PARAM_ERROR, 1082 ERR_SASL_OPTION_STARTS_WITH_EQUAL.get(s)); 1083 } 1084 1085 final String name = s.substring(0, equalPos); 1086 final String value = s.substring(equalPos + 1); 1087 if (m.put(toLowerCase(name), value) != null) 1088 { 1089 throw new LDAPException(ResultCode.PARAM_ERROR, 1090 ERR_SASL_OPTION_NOT_MULTI_VALUED.get(name)); 1091 } 1092 } 1093 1094 return m; 1095 } 1096 1097 1098 1099 /** 1100 * Ensures that the provided map is empty, and will throw an exception if it 1101 * isn't. This method is intended for internal use only. 1102 * 1103 * @param options The map of options to ensure is empty. 1104 * @param mechanism The associated SASL mechanism. 1105 * 1106 * @throws LDAPException If the map of SASL options is not empty. 1107 */ 1108 @InternalUseOnly() 1109 public static void ensureNoUnsupportedOptions( 1110 final Map<String,String> options, 1111 final String mechanism) 1112 throws LDAPException 1113 { 1114 if (! options.isEmpty()) 1115 { 1116 for (final String s : options.keySet()) 1117 { 1118 throw new LDAPException(ResultCode.PARAM_ERROR, 1119 ERR_SASL_OPTION_UNSUPPORTED_FOR_MECH.get(s,mechanism)); 1120 } 1121 } 1122 } 1123 1124 1125 1126 /** 1127 * Retrieves the value of the specified option and parses it as a boolean. 1128 * Values of "true", "t", "yes", "y", "on", and "1" will be treated as 1129 * {@code true}. Values of "false", "f", "no", "n", "off", and "0" will be 1130 * treated as {@code false}. 1131 * 1132 * @param m The map from which to retrieve the option. It must not be 1133 * {@code null}. 1134 * @param o The name of the option to examine. 1135 * @param d The default value to use if the given option was not provided. 1136 * 1137 * @return The parsed boolean value. 1138 * 1139 * @throws LDAPException If the option value cannot be parsed as a boolean. 1140 */ 1141 static boolean getBooleanValue(final Map<String,String> m, final String o, 1142 final boolean d) 1143 throws LDAPException 1144 { 1145 final String s = toLowerCase(m.remove(toLowerCase(o))); 1146 if (s == null) 1147 { 1148 return d; 1149 } 1150 else if (s.equals("true") || 1151 s.equals("t") || 1152 s.equals("yes") || 1153 s.equals("y") || 1154 s.equals("on") || 1155 s.equals("1")) 1156 { 1157 return true; 1158 } 1159 else if (s.equals("false") || 1160 s.equals("f") || 1161 s.equals("no") || 1162 s.equals("n") || 1163 s.equals("off") || 1164 s.equals("0")) 1165 { 1166 return false; 1167 } 1168 else 1169 { 1170 throw new LDAPException(ResultCode.PARAM_ERROR, 1171 ERR_SASL_OPTION_MALFORMED_BOOLEAN_VALUE.get(o)); 1172 } 1173 } 1174 1175 1176 1177 /** 1178 * Retrieves a string representation of the SASL usage information. This will 1179 * include the supported SASL mechanisms and the properties that may be used 1180 * with each. 1181 * 1182 * @param maxWidth The maximum line width to use for the output. If this is 1183 * less than or equal to zero, then no wrapping will be 1184 * performed. 1185 * 1186 * @return A string representation of the usage information 1187 */ 1188 public static String getUsageString(final int maxWidth) 1189 { 1190 final StringBuilder buffer = new StringBuilder(); 1191 1192 for (final String line : getUsage(maxWidth)) 1193 { 1194 buffer.append(line); 1195 buffer.append(EOL); 1196 } 1197 1198 return buffer.toString(); 1199 } 1200 1201 1202 1203 /** 1204 * Retrieves lines that make up the SASL usage information, optionally 1205 * wrapping long lines. 1206 * 1207 * @param maxWidth The maximum line width to use for the output. If this is 1208 * less than or equal to zero, then no wrapping will be 1209 * performed. 1210 * 1211 * @return The lines that make up the SASL usage information. 1212 */ 1213 public static List<String> getUsage(final int maxWidth) 1214 { 1215 final ArrayList<String> lines = new ArrayList<String>(100); 1216 1217 boolean first = true; 1218 for (final SASLMechanismInfo i : getSupportedSASLMechanisms()) 1219 { 1220 if (first) 1221 { 1222 first = false; 1223 } 1224 else 1225 { 1226 lines.add(""); 1227 lines.add(""); 1228 } 1229 1230 lines.addAll( 1231 wrapLine(INFO_SASL_HELP_MECHANISM.get(i.getName()), maxWidth)); 1232 lines.add(""); 1233 1234 for (final String line : wrapLine(i.getDescription(), maxWidth - 4)) 1235 { 1236 lines.add(" " + line); 1237 } 1238 lines.add(""); 1239 1240 for (final String line : 1241 wrapLine(INFO_SASL_HELP_MECHANISM_OPTIONS.get(i.getName()), 1242 maxWidth - 4)) 1243 { 1244 lines.add(" " + line); 1245 } 1246 1247 if (i.acceptsPassword()) 1248 { 1249 lines.add(""); 1250 if (i.requiresPassword()) 1251 { 1252 for (final String line : 1253 wrapLine(INFO_SASL_HELP_PASSWORD_REQUIRED.get(i.getName()), 1254 maxWidth - 4)) 1255 { 1256 lines.add(" " + line); 1257 } 1258 } 1259 else 1260 { 1261 for (final String line : 1262 wrapLine(INFO_SASL_HELP_PASSWORD_OPTIONAL.get(i.getName()), 1263 maxWidth - 4)) 1264 { 1265 lines.add(" " + line); 1266 } 1267 } 1268 } 1269 1270 for (final SASLOption o : i.getOptions()) 1271 { 1272 lines.add(""); 1273 lines.add(" * " + o.getName()); 1274 for (final String line : wrapLine(o.getDescription(), maxWidth - 14)) 1275 { 1276 lines.add(" " + line); 1277 } 1278 } 1279 } 1280 1281 return lines; 1282 } 1283}