001/* 002 * Copyright 2009-2015 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2009-2015 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.ldap.sdk; 022 023 024 025import java.io.File; 026import java.io.FileWriter; 027import java.io.PrintWriter; 028import java.security.PrivilegedExceptionAction; 029import java.util.ArrayList; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Set; 033import java.util.concurrent.atomic.AtomicReference; 034import java.util.logging.Level; 035import javax.security.auth.Subject; 036import javax.security.auth.callback.Callback; 037import javax.security.auth.callback.CallbackHandler; 038import javax.security.auth.callback.NameCallback; 039import javax.security.auth.callback.PasswordCallback; 040import javax.security.auth.callback.UnsupportedCallbackException; 041import javax.security.auth.login.LoginContext; 042import javax.security.sasl.RealmCallback; 043import javax.security.sasl.Sasl; 044import javax.security.sasl.SaslClient; 045 046import com.unboundid.asn1.ASN1OctetString; 047import com.unboundid.util.DebugType; 048import com.unboundid.util.InternalUseOnly; 049import com.unboundid.util.NotMutable; 050import com.unboundid.util.ThreadSafety; 051import com.unboundid.util.ThreadSafetyLevel; 052 053import static com.unboundid.ldap.sdk.LDAPMessages.*; 054import static com.unboundid.util.Debug.*; 055import static com.unboundid.util.StaticUtils.*; 056import static com.unboundid.util.Validator.*; 057 058 059 060/** 061 * This class provides a SASL GSSAPI bind request implementation as described in 062 * <A HREF="http://www.ietf.org/rfc/rfc4752.txt">RFC 4752</A>. It provides the 063 * ability to authenticate to a directory server using Kerberos V, which can 064 * serve as a kind of single sign-on mechanism that may be shared across 065 * client applications that support Kerberos. 066 * <BR><BR> 067 * This class uses the Java Authentication and Authorization Service (JAAS) 068 * behind the scenes to perform all Kerberos processing. This framework 069 * requires a configuration file to indicate the underlying mechanism to be 070 * used. It is possible for clients to explicitly specify the path to the 071 * configuration file that should be used, but if none is given then a default 072 * file will be created and used. This default file should be sufficient for 073 * Sun-provided JVMs, but a custom file may be required for JVMs provided by 074 * other vendors. 075 * <BR><BR> 076 * Elements included in a GSSAPI bind request include: 077 * <UL> 078 * <LI>Authentication ID -- A string which identifies the user that is 079 * attempting to authenticate. It should be the user's Kerberos 080 * principal.</LI> 081 * <LI>Authorization ID -- An optional string which specifies an alternate 082 * authorization identity that should be used for subsequent operations 083 * requested on the connection. Like the authentication ID, the 084 * authorization ID should be a Kerberos principal.</LI> 085 * <LI>KDC Address -- An optional string which specifies the IP address or 086 * resolvable name for the Kerberos key distribution center. If this is 087 * not provided, an attempt will be made to determine the appropriate 088 * value from the system configuration.</LI> 089 * <LI>Realm -- An optional string which specifies the realm into which the 090 * user should authenticate. If this is not provided, an attempt will be 091 * made to determine the appropriate value from the system 092 * configuration</LI> 093 * <LI>Password -- The clear-text password for the target user in the Kerberos 094 * realm.</LI> 095 * </UL> 096 * <H2>Example</H2> 097 * The following example demonstrates the process for performing a GSSAPI bind 098 * against a directory server with a username of "john.doe" and a password 099 * of "password": 100 * <PRE> 101 * GSSAPIBindRequestProperties gssapiProperties = 102 * new GSSAPIBindRequestProperties("john.doe@EXAMPLE.COM", "password"); 103 * gssapiProperties.setKDCAddress("kdc.example.com"); 104 * gssapiProperties.setRealm("EXAMPLE.COM"); 105 * 106 * GSSAPIBindRequest bindRequest = 107 * new GSSAPIBindRequest(gssapiProperties); 108 * BindResult bindResult; 109 * try 110 * { 111 * bindResult = connection.bind(bindRequest); 112 * // If we get here, then the bind was successful. 113 * } 114 * catch (LDAPException le) 115 * { 116 * // The bind failed for some reason. 117 * bindResult = new BindResult(le.toLDAPResult()); 118 * ResultCode resultCode = le.getResultCode(); 119 * String errorMessageFromServer = le.getDiagnosticMessage(); 120 * } 121 * </PRE> 122 */ 123@NotMutable() 124@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 125public final class GSSAPIBindRequest 126 extends SASLBindRequest 127 implements CallbackHandler, PrivilegedExceptionAction<Object> 128{ 129 /** 130 * The name for the GSSAPI SASL mechanism. 131 */ 132 public static final String GSSAPI_MECHANISM_NAME = "GSSAPI"; 133 134 135 136 /** 137 * The name of the configuration property used to specify the address of the 138 * Kerberos key distribution center. 139 */ 140 private static final String PROPERTY_KDC_ADDRESS = "java.security.krb5.kdc"; 141 142 143 144 /** 145 * The name of the configuration property used to specify the Kerberos realm. 146 */ 147 private static final String PROPERTY_REALM = "java.security.krb5.realm"; 148 149 150 151 /** 152 * The name of the configuration property used to specify the path to the JAAS 153 * configuration file. 154 */ 155 private static final String PROPERTY_CONFIG_FILE = 156 "java.security.auth.login.config"; 157 158 159 160 /** 161 * The name of the configuration property used to indicate whether credentials 162 * can come from somewhere other than the location specified in the JAAS 163 * configuration file. 164 */ 165 private static final String PROPERTY_SUBJECT_CREDS_ONLY = 166 "javax.security.auth.useSubjectCredsOnly"; 167 168 169 170 /** 171 * The value for the java.security.auth.login.config property at the time that 172 * this class was loaded. If this is set, then it will be used in place of 173 * an automatically-generated config file. 174 */ 175 private static final String DEFAULT_CONFIG_FILE = 176 System.getProperty(PROPERTY_CONFIG_FILE); 177 178 179 180 /** 181 * The default KDC address that will be used if none is explicitly configured. 182 */ 183 private static final String DEFAULT_KDC_ADDRESS = 184 System.getProperty(PROPERTY_KDC_ADDRESS); 185 186 187 188 /** 189 * The default realm that will be used if none is explicitly configured. 190 */ 191 private static final String DEFAULT_REALM = 192 System.getProperty(PROPERTY_REALM); 193 194 195 196 /** 197 * The serial version UID for this serializable class. 198 */ 199 private static final long serialVersionUID = 2511890818146955112L; 200 201 202 203 // The password for the GSSAPI bind request. 204 private final ASN1OctetString password; 205 206 // A reference to the connection to use for bind processing. 207 private final AtomicReference<LDAPConnection> conn; 208 209 // Indicates whether to enable JVM-level debugging for GSSAPI processing. 210 private final boolean enableGSSAPIDebugging; 211 212 // Indicates whether to attempt to renew the client's existing ticket-granting 213 // ticket if authentication uses an existing Kerberos session. 214 private final boolean renewTGT; 215 216 // Indicates whether to require that the credentials be obtained from the 217 // ticket cache such that authentication will fail if the client does not have 218 // an existing Kerberos session. 219 private final boolean requireCachedCredentials; 220 221 // Indicates whether to allow the client to use credentials that are outside 222 // of the current subject. 223 private final boolean useSubjectCredentialsOnly; 224 225 // Indicates whether to enable the use pf a ticket cache. 226 private final boolean useTicketCache; 227 228 // The message ID from the last LDAP message sent from this request. 229 private int messageID; 230 231 // The SASL quality of protection value(s) allowed for the DIGEST-MD5 bind 232 // request. 233 private final List<SASLQualityOfProtection> allowedQoP; 234 235 // A list that will be updated with messages about any unhandled callbacks 236 // encountered during processing. 237 private final List<String> unhandledCallbackMessages; 238 239 // The names of any system properties that should not be altered by GSSAPI 240 // processing. 241 private Set<String> suppressedSystemProperties; 242 243 // The authentication ID string for the GSSAPI bind request. 244 private final String authenticationID; 245 246 // The authorization ID string for the GSSAPI bind request, if available. 247 private final String authorizationID; 248 249 // The path to the JAAS configuration file to use for bind processing. 250 private final String configFilePath; 251 252 // The name that will be used to identify this client in the JAAS framework. 253 private final String jaasClientName; 254 255 // The KDC address for the GSSAPI bind request, if available. 256 private final String kdcAddress; 257 258 // The realm for the GSSAPI bind request, if available. 259 private final String realm; 260 261 // The server name that should be used when creating the Java SaslClient, if 262 // defined. 263 private final String saslClientServerName; 264 265 // The protocol that should be used in the Kerberos service principal for 266 // the server system. 267 private final String servicePrincipalProtocol; 268 269 // The path to the Kerberos ticket cache to use. 270 private final String ticketCachePath; 271 272 273 274 /** 275 * Creates a new SASL GSSAPI bind request with the provided authentication ID 276 * and password. 277 * 278 * @param authenticationID The authentication ID for this bind request. It 279 * must not be {@code null}. 280 * @param password The password for this bind request. It must not 281 * be {@code null}. 282 * 283 * @throws LDAPException If a problem occurs while creating the JAAS 284 * configuration file to use during authentication 285 * processing. 286 */ 287 public GSSAPIBindRequest(final String authenticationID, final String password) 288 throws LDAPException 289 { 290 this(new GSSAPIBindRequestProperties(authenticationID, password)); 291 } 292 293 294 295 /** 296 * Creates a new SASL GSSAPI bind request with the provided authentication ID 297 * and password. 298 * 299 * @param authenticationID The authentication ID for this bind request. It 300 * must not be {@code null}. 301 * @param password The password for this bind request. It must not 302 * be {@code null}. 303 * 304 * @throws LDAPException If a problem occurs while creating the JAAS 305 * configuration file to use during authentication 306 * processing. 307 */ 308 public GSSAPIBindRequest(final String authenticationID, final byte[] password) 309 throws LDAPException 310 { 311 this(new GSSAPIBindRequestProperties(authenticationID, password)); 312 } 313 314 315 316 /** 317 * Creates a new SASL GSSAPI bind request with the provided authentication ID 318 * and password. 319 * 320 * @param authenticationID The authentication ID for this bind request. It 321 * must not be {@code null}. 322 * @param password The password for this bind request. It must not 323 * be {@code null}. 324 * @param controls The set of controls to include in the request. 325 * 326 * @throws LDAPException If a problem occurs while creating the JAAS 327 * configuration file to use during authentication 328 * processing. 329 */ 330 public GSSAPIBindRequest(final String authenticationID, final String password, 331 final Control[] controls) 332 throws LDAPException 333 { 334 this(new GSSAPIBindRequestProperties(authenticationID, password), controls); 335 } 336 337 338 339 /** 340 * Creates a new SASL GSSAPI bind request with the provided authentication ID 341 * and password. 342 * 343 * @param authenticationID The authentication ID for this bind request. It 344 * must not be {@code null}. 345 * @param password The password for this bind request. It must not 346 * be {@code null}. 347 * @param controls The set of controls to include in the request. 348 * 349 * @throws LDAPException If a problem occurs while creating the JAAS 350 * configuration file to use during authentication 351 * processing. 352 */ 353 public GSSAPIBindRequest(final String authenticationID, final byte[] password, 354 final Control[] controls) 355 throws LDAPException 356 { 357 this(new GSSAPIBindRequestProperties(authenticationID, password), controls); 358 } 359 360 361 362 /** 363 * Creates a new SASL GSSAPI bind request with the provided information. 364 * 365 * @param authenticationID The authentication ID for this bind request. It 366 * must not be {@code null}. 367 * @param authorizationID The authorization ID for this bind request. It 368 * may be {@code null} if no alternate authorization 369 * ID should be used. 370 * @param password The password for this bind request. It must not 371 * be {@code null}. 372 * @param realm The realm to use for the authentication. It may 373 * be {@code null} to attempt to use the default 374 * realm from the system configuration. 375 * @param kdcAddress The address of the Kerberos key distribution 376 * center. It may be {@code null} to attempt to use 377 * the default KDC from the system configuration. 378 * @param configFilePath The path to the JAAS configuration file to use 379 * for the authentication processing. It may be 380 * {@code null} to use the default JAAS 381 * configuration. 382 * 383 * @throws LDAPException If a problem occurs while creating the JAAS 384 * configuration file to use during authentication 385 * processing. 386 */ 387 public GSSAPIBindRequest(final String authenticationID, 388 final String authorizationID, final String password, 389 final String realm, final String kdcAddress, 390 final String configFilePath) 391 throws LDAPException 392 { 393 this(new GSSAPIBindRequestProperties(authenticationID, authorizationID, 394 new ASN1OctetString(password), realm, kdcAddress, configFilePath)); 395 } 396 397 398 399 /** 400 * Creates a new SASL GSSAPI bind request with the provided information. 401 * 402 * @param authenticationID The authentication ID for this bind request. It 403 * must not be {@code null}. 404 * @param authorizationID The authorization ID for this bind request. It 405 * may be {@code null} if no alternate authorization 406 * ID should be used. 407 * @param password The password for this bind request. It must not 408 * be {@code null}. 409 * @param realm The realm to use for the authentication. It may 410 * be {@code null} to attempt to use the default 411 * realm from the system configuration. 412 * @param kdcAddress The address of the Kerberos key distribution 413 * center. It may be {@code null} to attempt to use 414 * the default KDC from the system configuration. 415 * @param configFilePath The path to the JAAS configuration file to use 416 * for the authentication processing. It may be 417 * {@code null} to use the default JAAS 418 * configuration. 419 * 420 * @throws LDAPException If a problem occurs while creating the JAAS 421 * configuration file to use during authentication 422 * processing. 423 */ 424 public GSSAPIBindRequest(final String authenticationID, 425 final String authorizationID, final byte[] password, 426 final String realm, final String kdcAddress, 427 final String configFilePath) 428 throws LDAPException 429 { 430 this(new GSSAPIBindRequestProperties(authenticationID, authorizationID, 431 new ASN1OctetString(password), realm, kdcAddress, configFilePath)); 432 } 433 434 435 436 /** 437 * Creates a new SASL GSSAPI bind request with the provided information. 438 * 439 * @param authenticationID The authentication ID for this bind request. It 440 * must not be {@code null}. 441 * @param authorizationID The authorization ID for this bind request. It 442 * may be {@code null} if no alternate authorization 443 * ID should be used. 444 * @param password The password for this bind request. It must not 445 * be {@code null}. 446 * @param realm The realm to use for the authentication. It may 447 * be {@code null} to attempt to use the default 448 * realm from the system configuration. 449 * @param kdcAddress The address of the Kerberos key distribution 450 * center. It may be {@code null} to attempt to use 451 * the default KDC from the system configuration. 452 * @param configFilePath The path to the JAAS configuration file to use 453 * for the authentication processing. It may be 454 * {@code null} to use the default JAAS 455 * configuration. 456 * @param controls The set of controls to include in the request. 457 * 458 * @throws LDAPException If a problem occurs while creating the JAAS 459 * configuration file to use during authentication 460 * processing. 461 */ 462 public GSSAPIBindRequest(final String authenticationID, 463 final String authorizationID, final String password, 464 final String realm, final String kdcAddress, 465 final String configFilePath, 466 final Control[] controls) 467 throws LDAPException 468 { 469 this(new GSSAPIBindRequestProperties(authenticationID, authorizationID, 470 new ASN1OctetString(password), realm, kdcAddress, configFilePath), 471 controls); 472 } 473 474 475 476 /** 477 * Creates a new SASL GSSAPI bind request with the provided information. 478 * 479 * @param authenticationID The authentication ID for this bind request. It 480 * must not be {@code null}. 481 * @param authorizationID The authorization ID for this bind request. It 482 * may be {@code null} if no alternate authorization 483 * ID should be used. 484 * @param password The password for this bind request. It must not 485 * be {@code null}. 486 * @param realm The realm to use for the authentication. It may 487 * be {@code null} to attempt to use the default 488 * realm from the system configuration. 489 * @param kdcAddress The address of the Kerberos key distribution 490 * center. It may be {@code null} to attempt to use 491 * the default KDC from the system configuration. 492 * @param configFilePath The path to the JAAS configuration file to use 493 * for the authentication processing. It may be 494 * {@code null} to use the default JAAS 495 * configuration. 496 * @param controls The set of controls to include in the request. 497 * 498 * @throws LDAPException If a problem occurs while creating the JAAS 499 * configuration file to use during authentication 500 * processing. 501 */ 502 public GSSAPIBindRequest(final String authenticationID, 503 final String authorizationID, final byte[] password, 504 final String realm, final String kdcAddress, 505 final String configFilePath, 506 final Control[] controls) 507 throws LDAPException 508 { 509 this(new GSSAPIBindRequestProperties(authenticationID, authorizationID, 510 new ASN1OctetString(password), realm, kdcAddress, configFilePath), 511 controls); 512 } 513 514 515 516 /** 517 * Creates a new SASL GSSAPI bind request with the provided set of properties. 518 * 519 * @param gssapiProperties The set of properties that should be used for 520 * the GSSAPI bind request. It must not be 521 * {@code null}. 522 * @param controls The set of controls to include in the request. 523 * 524 * @throws LDAPException If a problem occurs while creating the JAAS 525 * configuration file to use during authentication 526 * processing. 527 */ 528 public GSSAPIBindRequest(final GSSAPIBindRequestProperties gssapiProperties, 529 final Control... controls) 530 throws LDAPException 531 { 532 super(controls); 533 534 ensureNotNull(gssapiProperties); 535 536 authenticationID = gssapiProperties.getAuthenticationID(); 537 password = gssapiProperties.getPassword(); 538 realm = gssapiProperties.getRealm(); 539 allowedQoP = gssapiProperties.getAllowedQoP(); 540 kdcAddress = gssapiProperties.getKDCAddress(); 541 jaasClientName = gssapiProperties.getJAASClientName(); 542 saslClientServerName = gssapiProperties.getSASLClientServerName(); 543 servicePrincipalProtocol = gssapiProperties.getServicePrincipalProtocol(); 544 enableGSSAPIDebugging = gssapiProperties.enableGSSAPIDebugging(); 545 useSubjectCredentialsOnly = gssapiProperties.useSubjectCredentialsOnly(); 546 useTicketCache = gssapiProperties.useTicketCache(); 547 requireCachedCredentials = gssapiProperties.requireCachedCredentials(); 548 renewTGT = gssapiProperties.renewTGT(); 549 ticketCachePath = gssapiProperties.getTicketCachePath(); 550 suppressedSystemProperties = 551 gssapiProperties.getSuppressedSystemProperties(); 552 553 unhandledCallbackMessages = new ArrayList<String>(5); 554 555 conn = new AtomicReference<LDAPConnection>(); 556 messageID = -1; 557 558 final String authzID = gssapiProperties.getAuthorizationID(); 559 if (authzID == null) 560 { 561 authorizationID = null; 562 } 563 else 564 { 565 authorizationID = authzID; 566 } 567 568 final String cfgPath = gssapiProperties.getConfigFilePath(); 569 if (cfgPath == null) 570 { 571 if (DEFAULT_CONFIG_FILE == null) 572 { 573 configFilePath = getConfigFilePath(gssapiProperties); 574 } 575 else 576 { 577 configFilePath = DEFAULT_CONFIG_FILE; 578 } 579 } 580 else 581 { 582 configFilePath = cfgPath; 583 } 584 } 585 586 587 588 /** 589 * {@inheritDoc} 590 */ 591 @Override() 592 public String getSASLMechanismName() 593 { 594 return GSSAPI_MECHANISM_NAME; 595 } 596 597 598 599 /** 600 * Retrieves the authentication ID for the GSSAPI bind request, if defined. 601 * 602 * @return The authentication ID for the GSSAPI bind request, or {@code null} 603 * if an existing Kerberos session should be used. 604 */ 605 public String getAuthenticationID() 606 { 607 return authenticationID; 608 } 609 610 611 612 /** 613 * Retrieves the authorization ID for this bind request, if any. 614 * 615 * @return The authorization ID for this bind request, or {@code null} if 616 * there should not be a separate authorization identity. 617 */ 618 public String getAuthorizationID() 619 { 620 return authorizationID; 621 } 622 623 624 625 /** 626 * Retrieves the string representation of the password for this bind request, 627 * if defined. 628 * 629 * @return The string representation of the password for this bind request, 630 * or {@code null} if an existing Kerberos session should be used. 631 */ 632 public String getPasswordString() 633 { 634 if (password == null) 635 { 636 return null; 637 } 638 else 639 { 640 return password.stringValue(); 641 } 642 } 643 644 645 646 /** 647 * Retrieves the bytes that comprise the the password for this bind request, 648 * if defined. 649 * 650 * @return The bytes that comprise the password for this bind request, or 651 * {@code null} if an existing Kerberos session should be used. 652 */ 653 public byte[] getPasswordBytes() 654 { 655 if (password == null) 656 { 657 return null; 658 } 659 else 660 { 661 return password.getValue(); 662 } 663 } 664 665 666 667 /** 668 * Retrieves the realm for this bind request, if any. 669 * 670 * @return The realm for this bind request, or {@code null} if none was 671 * defined and the client should attempt to determine the realm from 672 * the system configuration. 673 */ 674 public String getRealm() 675 { 676 return realm; 677 } 678 679 680 681 /** 682 * Retrieves the list of allowed qualities of protection that may be used for 683 * communication that occurs on the connection after the authentication has 684 * completed, in order from most preferred to least preferred. 685 * 686 * @return The list of allowed qualities of protection that may be used for 687 * communication that occurs on the connection after the 688 * authentication has completed, in order from most preferred to 689 * least preferred. 690 */ 691 public List<SASLQualityOfProtection> getAllowedQoP() 692 { 693 return allowedQoP; 694 } 695 696 697 698 /** 699 * Retrieves the address of the Kerberos key distribution center. 700 * 701 * @return The address of the Kerberos key distribution center, or 702 * {@code null} if none was defined and the client should attempt to 703 * determine the KDC address from the system configuration. 704 */ 705 public String getKDCAddress() 706 { 707 return kdcAddress; 708 } 709 710 711 712 /** 713 * Retrieves the path to the JAAS configuration file that will be used during 714 * authentication processing. 715 * 716 * @return The path to the JAAS configuration file that will be used during 717 * authentication processing. 718 */ 719 public String getConfigFilePath() 720 { 721 return configFilePath; 722 } 723 724 725 726 /** 727 * Retrieves the protocol specified in the service principal that the 728 * directory server uses for its communication with the KDC. 729 * 730 * @return The protocol specified in the service principal that the directory 731 * server uses for its communication with the KDC. 732 */ 733 public String getServicePrincipalProtocol() 734 { 735 return servicePrincipalProtocol; 736 } 737 738 739 740 /** 741 * Indicates whether to enable the use of a ticket cache to to avoid the need 742 * to supply credentials if the client already has an existing Kerberos 743 * session. 744 * 745 * @return {@code true} if a ticket cache may be used to take advantage of an 746 * existing Kerberos session, or {@code false} if Kerberos 747 * credentials should always be provided. 748 */ 749 public boolean useTicketCache() 750 { 751 return useTicketCache; 752 } 753 754 755 756 /** 757 * Indicates whether GSSAPI authentication should only occur using an existing 758 * Kerberos session. 759 * 760 * @return {@code true} if GSSAPI authentication should only use an existing 761 * Kerberos session and should fail if the client does not have an 762 * existing session, or {@code false} if the client will be allowed 763 * to create a new session if one does not already exist. 764 */ 765 public boolean requireCachedCredentials() 766 { 767 return requireCachedCredentials; 768 } 769 770 771 772 /** 773 * Retrieves the path to the Kerberos ticket cache file that should be used 774 * during authentication, if defined. 775 * 776 * @return The path to the Kerberos ticket cache file that should be used 777 * during authentication, or {@code null} if the default ticket cache 778 * file should be used. 779 */ 780 public String getTicketCachePath() 781 { 782 return ticketCachePath; 783 } 784 785 786 787 /** 788 * Indicates whether to attempt to renew the client's ticket-granting ticket 789 * (TGT) if an existing Kerberos session is used to authenticate. 790 * 791 * @return {@code true} if the client should attempt to renew its 792 * ticket-granting ticket if the authentication is processed using an 793 * existing Kerberos session, or {@code false} if not. 794 */ 795 public boolean renewTGT() 796 { 797 return renewTGT; 798 } 799 800 801 802 /** 803 * Indicates whether to allow the client to use credentials that are outside 804 * of the current subject, obtained via some system-specific mechanism. 805 * 806 * @return {@code true} if the client will only be allowed to use credentials 807 * that are within the current subject, or {@code false} if the 808 * client will be allowed to use credentials outside the current 809 * subject. 810 */ 811 public boolean useSubjectCredentialsOnly() 812 { 813 return useSubjectCredentialsOnly; 814 } 815 816 817 818 /** 819 * Retrieves a set of system properties that will not be altered by GSSAPI 820 * processing. 821 * 822 * @return A set of system properties that will not be altered by GSSAPI 823 * processing. 824 */ 825 public Set<String> getSuppressedSystemProperties() 826 { 827 return suppressedSystemProperties; 828 } 829 830 831 832 /** 833 * Indicates whether JVM-level debugging should be enabled for GSSAPI bind 834 * processing. 835 * 836 * @return {@code true} if JVM-level debugging should be enabled for GSSAPI 837 * bind processing, or {@code false} if not. 838 */ 839 public boolean enableGSSAPIDebugging() 840 { 841 return enableGSSAPIDebugging; 842 } 843 844 845 846 /** 847 * Retrieves the path to the default JAAS configuration file that will be used 848 * if no file was explicitly provided. A new file may be created if 849 * necessary. 850 * 851 * @param properties The GSSAPI properties that should be used for 852 * authentication. 853 * 854 * @return The path to the default JAAS configuration file that will be used 855 * if no file was explicitly provided. 856 * 857 * @throws LDAPException If an error occurs while attempting to create the 858 * configuration file. 859 */ 860 private static String getConfigFilePath( 861 final GSSAPIBindRequestProperties properties) 862 throws LDAPException 863 { 864 try 865 { 866 final File f = 867 File.createTempFile("GSSAPIBindRequest-JAAS-Config-", ".conf"); 868 f.deleteOnExit(); 869 final PrintWriter w = new PrintWriter(new FileWriter(f)); 870 871 try 872 { 873 // The JAAS configuration file may vary based on the JVM that we're 874 // using. For Sun-based JVMs, the module will be 875 // "com.sun.security.auth.module.Krb5LoginModule". 876 try 877 { 878 final Class<?> sunModuleClass = 879 Class.forName("com.sun.security.auth.module.Krb5LoginModule"); 880 if (sunModuleClass != null) 881 { 882 writeSunJAASConfig(w, properties); 883 return f.getAbsolutePath(); 884 } 885 } 886 catch (final ClassNotFoundException cnfe) 887 { 888 // This is fine. 889 debugException(cnfe); 890 } 891 892 893 // For the IBM JVMs, the module will be 894 // "com.ibm.security.auth.module.Krb5LoginModule". 895 try 896 { 897 final Class<?> ibmModuleClass = 898 Class.forName("com.ibm.security.auth.module.Krb5LoginModule"); 899 if (ibmModuleClass != null) 900 { 901 writeIBMJAASConfig(w, properties); 902 return f.getAbsolutePath(); 903 } 904 } 905 catch (final ClassNotFoundException cnfe) 906 { 907 // This is fine. 908 debugException(cnfe); 909 } 910 911 912 // If we've gotten here, then we can't generate an appropriate 913 // configuration. 914 throw new LDAPException(ResultCode.LOCAL_ERROR, 915 ERR_GSSAPI_CANNOT_CREATE_JAAS_CONFIG.get( 916 ERR_GSSAPI_NO_SUPPORTED_JAAS_MODULE.get())); 917 } 918 finally 919 { 920 w.close(); 921 } 922 } 923 catch (final LDAPException le) 924 { 925 debugException(le); 926 throw le; 927 } 928 catch (final Exception e) 929 { 930 debugException(e); 931 932 throw new LDAPException(ResultCode.LOCAL_ERROR, 933 ERR_GSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(getExceptionMessage(e)), e); 934 } 935 } 936 937 938 939 /** 940 * Writes a JAAS configuration file in a form appropriate for Sun VMs. 941 * 942 * @param w The writer to use to create the config file. 943 * @param p The properties to use for GSSAPI authentication. 944 */ 945 private static void writeSunJAASConfig(final PrintWriter w, 946 final GSSAPIBindRequestProperties p) 947 { 948 w.println(p.getJAASClientName() + " {"); 949 w.println(" com.sun.security.auth.module.Krb5LoginModule required"); 950 w.println(" client=true"); 951 952 if (p.useTicketCache()) 953 { 954 w.println(" useTicketCache=true"); 955 w.println(" renewTGT=" + p.renewTGT()); 956 w.println(" doNotPrompt=" + p.requireCachedCredentials()); 957 958 final String ticketCachePath = p.getTicketCachePath(); 959 if (ticketCachePath != null) 960 { 961 w.println(" ticketCache=\"" + ticketCachePath + '"'); 962 } 963 } 964 else 965 { 966 w.println(" useTicketCache=false"); 967 } 968 969 if (p.enableGSSAPIDebugging()) 970 { 971 w.println(" debug=true"); 972 } 973 974 w.println(" ;"); 975 w.println("};"); 976 } 977 978 979 980 /** 981 * Writes a JAAS configuration file in a form appropriate for IBM VMs. 982 * 983 * @param w The writer to use to create the config file. 984 * @param p The properties to use for GSSAPI authentication. 985 */ 986 private static void writeIBMJAASConfig(final PrintWriter w, 987 final GSSAPIBindRequestProperties p) 988 { 989 // NOTE: It does not appear that the IBM GSSAPI implementation has any 990 // analog for the renewTGT property, so it will be ignored. 991 w.println(p.getJAASClientName() + " {"); 992 w.println(" com.ibm.security.auth.module.Krb5LoginModule required"); 993 w.println(" credsType=initiator"); 994 995 if (p.useTicketCache()) 996 { 997 final String ticketCachePath = p.getTicketCachePath(); 998 if (ticketCachePath == null) 999 { 1000 if (p.requireCachedCredentials()) 1001 { 1002 w.println(" useDefaultCcache=true"); 1003 } 1004 } 1005 else 1006 { 1007 final File f = new File(ticketCachePath); 1008 final String path = f.getAbsolutePath().replace('\\', '/'); 1009 w.println(" useCcache=\"file://" + path + '"'); 1010 } 1011 } 1012 else 1013 { 1014 w.println(" useDefaultCcache=false"); 1015 } 1016 1017 if (p.enableGSSAPIDebugging()) 1018 { 1019 w.println(" debug=true"); 1020 } 1021 1022 w.println(" ;"); 1023 w.println("};"); 1024 } 1025 1026 1027 1028 /** 1029 * Sends this bind request to the target server over the provided connection 1030 * and returns the corresponding response. 1031 * 1032 * @param connection The connection to use to send this bind request to the 1033 * server and read the associated response. 1034 * @param depth The current referral depth for this request. It should 1035 * always be one for the initial request, and should only 1036 * be incremented when following referrals. 1037 * 1038 * @return The bind response read from the server. 1039 * 1040 * @throws LDAPException If a problem occurs while sending the request or 1041 * reading the response. 1042 */ 1043 @Override() 1044 protected BindResult process(final LDAPConnection connection, final int depth) 1045 throws LDAPException 1046 { 1047 if (! conn.compareAndSet(null, connection)) 1048 { 1049 throw new LDAPException(ResultCode.LOCAL_ERROR, 1050 ERR_GSSAPI_MULTIPLE_CONCURRENT_REQUESTS.get()); 1051 } 1052 1053 setProperty(PROPERTY_CONFIG_FILE, configFilePath); 1054 setProperty(PROPERTY_SUBJECT_CREDS_ONLY, 1055 String.valueOf(useSubjectCredentialsOnly)); 1056 if (debugEnabled(DebugType.LDAP)) 1057 { 1058 debug(Level.CONFIG, DebugType.LDAP, 1059 "Using config file property " + PROPERTY_CONFIG_FILE + " = '" + 1060 configFilePath + "'."); 1061 debug(Level.CONFIG, DebugType.LDAP, 1062 "Using subject creds only property " + PROPERTY_SUBJECT_CREDS_ONLY + 1063 " = '" + useSubjectCredentialsOnly + "'."); 1064 } 1065 1066 if (kdcAddress == null) 1067 { 1068 if (DEFAULT_KDC_ADDRESS == null) 1069 { 1070 clearProperty(PROPERTY_KDC_ADDRESS); 1071 if (debugEnabled(DebugType.LDAP)) 1072 { 1073 debug(Level.CONFIG, DebugType.LDAP, 1074 "Clearing kdcAddress property '" + PROPERTY_KDC_ADDRESS + "'."); 1075 } 1076 } 1077 else 1078 { 1079 setProperty(PROPERTY_KDC_ADDRESS, DEFAULT_KDC_ADDRESS); 1080 if (debugEnabled(DebugType.LDAP)) 1081 { 1082 debug(Level.CONFIG, DebugType.LDAP, 1083 "Using default kdcAddress property " + PROPERTY_KDC_ADDRESS + 1084 " = '" + DEFAULT_KDC_ADDRESS + "'."); 1085 } 1086 } 1087 } 1088 else 1089 { 1090 setProperty(PROPERTY_KDC_ADDRESS, kdcAddress); 1091 if (debugEnabled(DebugType.LDAP)) 1092 { 1093 debug(Level.CONFIG, DebugType.LDAP, 1094 "Using kdcAddress property " + PROPERTY_KDC_ADDRESS + " = '" + 1095 kdcAddress + "'."); 1096 } 1097 } 1098 1099 if (realm == null) 1100 { 1101 if (DEFAULT_REALM == null) 1102 { 1103 clearProperty(PROPERTY_REALM); 1104 if (debugEnabled(DebugType.LDAP)) 1105 { 1106 debug(Level.CONFIG, DebugType.LDAP, 1107 "Clearing realm property '" + PROPERTY_REALM + "'."); 1108 } 1109 } 1110 else 1111 { 1112 setProperty(PROPERTY_REALM, DEFAULT_REALM); 1113 if (debugEnabled(DebugType.LDAP)) 1114 { 1115 debug(Level.CONFIG, DebugType.LDAP, 1116 "Using default realm property " + PROPERTY_REALM + " = '" + 1117 DEFAULT_REALM + "'."); 1118 } 1119 } 1120 } 1121 else 1122 { 1123 setProperty(PROPERTY_REALM, realm); 1124 if (debugEnabled(DebugType.LDAP)) 1125 { 1126 debug(Level.CONFIG, DebugType.LDAP, 1127 "Using realm property " + PROPERTY_REALM + " = '" + realm + "'."); 1128 } 1129 } 1130 1131 try 1132 { 1133 final LoginContext context; 1134 try 1135 { 1136 context = new LoginContext(jaasClientName, this); 1137 context.login(); 1138 } 1139 catch (Exception e) 1140 { 1141 debugException(e); 1142 1143 throw new LDAPException(ResultCode.LOCAL_ERROR, 1144 ERR_GSSAPI_CANNOT_INITIALIZE_JAAS_CONTEXT.get( 1145 getExceptionMessage(e)), e); 1146 } 1147 1148 try 1149 { 1150 return (BindResult) Subject.doAs(context.getSubject(), this); 1151 } 1152 catch (Exception e) 1153 { 1154 debugException(e); 1155 if (e instanceof LDAPException) 1156 { 1157 throw (LDAPException) e; 1158 } 1159 else 1160 { 1161 throw new LDAPException(ResultCode.LOCAL_ERROR, 1162 ERR_GSSAPI_AUTHENTICATION_FAILED.get( 1163 getExceptionMessage(e)), e); 1164 } 1165 } 1166 } 1167 finally 1168 { 1169 conn.set(null); 1170 } 1171 } 1172 1173 1174 1175 /** 1176 * Perform the privileged portion of the authentication processing. 1177 * 1178 * @return {@code null}, since no return value is actually needed. 1179 * 1180 * @throws LDAPException If a problem occurs during processing. 1181 */ 1182 @InternalUseOnly() 1183 public Object run() 1184 throws LDAPException 1185 { 1186 unhandledCallbackMessages.clear(); 1187 1188 final LDAPConnection connection = conn.get(); 1189 1190 final String[] mechanisms = { GSSAPI_MECHANISM_NAME }; 1191 1192 final HashMap<String,Object> saslProperties = new HashMap<String,Object>(2); 1193 saslProperties.put(Sasl.QOP, SASLQualityOfProtection.toString(allowedQoP)); 1194 saslProperties.put(Sasl.SERVER_AUTH, "true"); 1195 1196 final SaslClient saslClient; 1197 try 1198 { 1199 String serverName = saslClientServerName; 1200 if (serverName == null) 1201 { 1202 serverName = connection.getConnectedAddress(); 1203 } 1204 1205 saslClient = Sasl.createSaslClient(mechanisms, authorizationID, 1206 servicePrincipalProtocol, serverName, saslProperties, this); 1207 } 1208 catch (Exception e) 1209 { 1210 debugException(e); 1211 throw new LDAPException(ResultCode.LOCAL_ERROR, 1212 ERR_GSSAPI_CANNOT_CREATE_SASL_CLIENT.get(getExceptionMessage(e)), e); 1213 } 1214 1215 final SASLHelper helper = new SASLHelper(this, connection, 1216 GSSAPI_MECHANISM_NAME, saslClient, getControls(), 1217 getResponseTimeoutMillis(connection), unhandledCallbackMessages); 1218 1219 try 1220 { 1221 return helper.processSASLBind(); 1222 } 1223 finally 1224 { 1225 messageID = helper.getMessageID(); 1226 } 1227 } 1228 1229 1230 1231 /** 1232 * {@inheritDoc} 1233 */ 1234 @Override() 1235 public GSSAPIBindRequest getRebindRequest(final String host, final int port) 1236 { 1237 try 1238 { 1239 final GSSAPIBindRequestProperties gssapiProperties = 1240 new GSSAPIBindRequestProperties(authenticationID, authorizationID, 1241 password, realm, kdcAddress, configFilePath); 1242 gssapiProperties.setAllowedQoP(allowedQoP); 1243 gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol); 1244 gssapiProperties.setUseTicketCache(useTicketCache); 1245 gssapiProperties.setRequireCachedCredentials(requireCachedCredentials); 1246 gssapiProperties.setRenewTGT(renewTGT); 1247 gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly); 1248 gssapiProperties.setTicketCachePath(ticketCachePath); 1249 gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging); 1250 gssapiProperties.setJAASClientName(jaasClientName); 1251 gssapiProperties.setSASLClientServerName(saslClientServerName); 1252 gssapiProperties.setSuppressedSystemProperties( 1253 suppressedSystemProperties); 1254 1255 return new GSSAPIBindRequest(gssapiProperties, getControls()); 1256 } 1257 catch (Exception e) 1258 { 1259 // This should never happen. 1260 debugException(e); 1261 return null; 1262 } 1263 } 1264 1265 1266 1267 /** 1268 * Handles any necessary callbacks required for SASL authentication. 1269 * 1270 * @param callbacks The set of callbacks to be handled. 1271 * 1272 * @throws UnsupportedCallbackException If an unsupported type of callback 1273 * was received. 1274 */ 1275 @InternalUseOnly() 1276 public void handle(final Callback[] callbacks) 1277 throws UnsupportedCallbackException 1278 { 1279 for (final Callback callback : callbacks) 1280 { 1281 if (callback instanceof NameCallback) 1282 { 1283 ((NameCallback) callback).setName(authenticationID); 1284 } 1285 else if (callback instanceof PasswordCallback) 1286 { 1287 if (password == null) 1288 { 1289 throw new UnsupportedCallbackException(callback, 1290 ERR_GSSAPI_NO_PASSWORD_AVAILABLE.get()); 1291 } 1292 else 1293 { 1294 ((PasswordCallback) callback).setPassword( 1295 password.stringValue().toCharArray()); 1296 } 1297 } 1298 else if (callback instanceof RealmCallback) 1299 { 1300 final RealmCallback rc = (RealmCallback) callback; 1301 if (realm == null) 1302 { 1303 unhandledCallbackMessages.add( 1304 ERR_GSSAPI_REALM_REQUIRED_BUT_NONE_PROVIDED.get(rc.getPrompt())); 1305 } 1306 else 1307 { 1308 rc.setText(realm); 1309 } 1310 } 1311 else 1312 { 1313 // This is an unexpected callback. 1314 if (debugEnabled(DebugType.LDAP)) 1315 { 1316 debug(Level.WARNING, DebugType.LDAP, 1317 "Unexpected GSSAPI SASL callback of type " + 1318 callback.getClass().getName()); 1319 } 1320 1321 unhandledCallbackMessages.add(ERR_GSSAPI_UNEXPECTED_CALLBACK.get( 1322 callback.getClass().getName())); 1323 } 1324 } 1325 } 1326 1327 1328 1329 /** 1330 * {@inheritDoc} 1331 */ 1332 @Override() 1333 public int getLastMessageID() 1334 { 1335 return messageID; 1336 } 1337 1338 1339 1340 /** 1341 * {@inheritDoc} 1342 */ 1343 @Override() 1344 public GSSAPIBindRequest duplicate() 1345 { 1346 return duplicate(getControls()); 1347 } 1348 1349 1350 1351 /** 1352 * {@inheritDoc} 1353 */ 1354 @Override() 1355 public GSSAPIBindRequest duplicate(final Control[] controls) 1356 { 1357 try 1358 { 1359 final GSSAPIBindRequestProperties gssapiProperties = 1360 new GSSAPIBindRequestProperties(authenticationID, authorizationID, 1361 password, realm, kdcAddress, configFilePath); 1362 gssapiProperties.setAllowedQoP(allowedQoP); 1363 gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol); 1364 gssapiProperties.setUseTicketCache(useTicketCache); 1365 gssapiProperties.setRequireCachedCredentials(requireCachedCredentials); 1366 gssapiProperties.setRenewTGT(renewTGT); 1367 gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly); 1368 gssapiProperties.setTicketCachePath(ticketCachePath); 1369 gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging); 1370 gssapiProperties.setJAASClientName(jaasClientName); 1371 gssapiProperties.setSASLClientServerName(saslClientServerName); 1372 gssapiProperties.setSuppressedSystemProperties( 1373 suppressedSystemProperties); 1374 1375 final GSSAPIBindRequest bindRequest = 1376 new GSSAPIBindRequest(gssapiProperties, controls); 1377 bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1378 return bindRequest; 1379 } 1380 catch (Exception e) 1381 { 1382 // This should never happen. 1383 debugException(e); 1384 return null; 1385 } 1386 } 1387 1388 1389 1390 /** 1391 * Clears the specified system property, unless it is one that is configured 1392 * to be suppressed. 1393 * 1394 * @param name The name of the property to be suppressed. 1395 */ 1396 private void clearProperty(final String name) 1397 { 1398 if (! suppressedSystemProperties.contains(name)) 1399 { 1400 System.clearProperty(name); 1401 } 1402 } 1403 1404 1405 1406 /** 1407 * Sets the specified system property, unless it is one that is configured to 1408 * be suppressed. 1409 * 1410 * @param name The name of the property to be suppressed. 1411 * @param value The value of the property to be suppressed. 1412 */ 1413 private void setProperty(final String name, final String value) 1414 { 1415 if (! suppressedSystemProperties.contains(name)) 1416 { 1417 System.setProperty(name, value); 1418 } 1419 } 1420 1421 1422 1423 /** 1424 * {@inheritDoc} 1425 */ 1426 @Override() 1427 public void toString(final StringBuilder buffer) 1428 { 1429 buffer.append("GSSAPIBindRequest(authenticationID='"); 1430 buffer.append(authenticationID); 1431 buffer.append('\''); 1432 1433 if (authorizationID != null) 1434 { 1435 buffer.append(", authorizationID='"); 1436 buffer.append(authorizationID); 1437 buffer.append('\''); 1438 } 1439 1440 if (realm != null) 1441 { 1442 buffer.append(", realm='"); 1443 buffer.append(realm); 1444 buffer.append('\''); 1445 } 1446 1447 buffer.append(", qop='"); 1448 buffer.append(SASLQualityOfProtection.toString(allowedQoP)); 1449 buffer.append('\''); 1450 1451 if (kdcAddress != null) 1452 { 1453 buffer.append(", kdcAddress='"); 1454 buffer.append(kdcAddress); 1455 buffer.append('\''); 1456 } 1457 1458 buffer.append(", jaasClientName='"); 1459 buffer.append(jaasClientName); 1460 buffer.append("', configFilePath='"); 1461 buffer.append(configFilePath); 1462 buffer.append("', servicePrincipalProtocol='"); 1463 buffer.append(servicePrincipalProtocol); 1464 buffer.append("', enableGSSAPIDebugging="); 1465 buffer.append(enableGSSAPIDebugging); 1466 1467 final Control[] controls = getControls(); 1468 if (controls.length > 0) 1469 { 1470 buffer.append(", controls={"); 1471 for (int i=0; i < controls.length; i++) 1472 { 1473 if (i > 0) 1474 { 1475 buffer.append(", "); 1476 } 1477 1478 buffer.append(controls[i]); 1479 } 1480 buffer.append('}'); 1481 } 1482 1483 buffer.append(')'); 1484 } 1485}