001/* 002 * Copyright 2009-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2009-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.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 * Retrieves a set of system properties that will not be altered by GSSAPI 804 * processing. 805 * 806 * @return A set of system properties that will not be altered by GSSAPI 807 * processing. 808 */ 809 public Set<String> getSuppressedSystemProperties() 810 { 811 return suppressedSystemProperties; 812 } 813 814 815 816 /** 817 * Indicates whether JVM-level debugging should be enabled for GSSAPI bind 818 * processing. 819 * 820 * @return {@code true} if JVM-level debugging should be enabled for GSSAPI 821 * bind processing, or {@code false} if not. 822 */ 823 public boolean enableGSSAPIDebugging() 824 { 825 return enableGSSAPIDebugging; 826 } 827 828 829 830 /** 831 * Retrieves the path to the default JAAS configuration file that will be used 832 * if no file was explicitly provided. A new file may be created if 833 * necessary. 834 * 835 * @param properties The GSSAPI properties that should be used for 836 * authentication. 837 * 838 * @return The path to the default JAAS configuration file that will be used 839 * if no file was explicitly provided. 840 * 841 * @throws LDAPException If an error occurs while attempting to create the 842 * configuration file. 843 */ 844 private static String getConfigFilePath( 845 final GSSAPIBindRequestProperties properties) 846 throws LDAPException 847 { 848 try 849 { 850 final File f = 851 File.createTempFile("GSSAPIBindRequest-JAAS-Config-", ".conf"); 852 f.deleteOnExit(); 853 final PrintWriter w = new PrintWriter(new FileWriter(f)); 854 855 try 856 { 857 // The JAAS configuration file may vary based on the JVM that we're 858 // using. For Sun-based JVMs, the module will be 859 // "com.sun.security.auth.module.Krb5LoginModule". 860 try 861 { 862 final Class<?> sunModuleClass = 863 Class.forName("com.sun.security.auth.module.Krb5LoginModule"); 864 if (sunModuleClass != null) 865 { 866 writeSunJAASConfig(w, properties); 867 return f.getAbsolutePath(); 868 } 869 } 870 catch (final ClassNotFoundException cnfe) 871 { 872 // This is fine. 873 debugException(cnfe); 874 } 875 876 877 // For the IBM JVMs, the module will be 878 // "com.ibm.security.auth.module.Krb5LoginModule". 879 try 880 { 881 final Class<?> ibmModuleClass = 882 Class.forName("com.ibm.security.auth.module.Krb5LoginModule"); 883 if (ibmModuleClass != null) 884 { 885 writeIBMJAASConfig(w, properties); 886 return f.getAbsolutePath(); 887 } 888 } 889 catch (final ClassNotFoundException cnfe) 890 { 891 // This is fine. 892 debugException(cnfe); 893 } 894 895 896 // If we've gotten here, then we can't generate an appropriate 897 // configuration. 898 throw new LDAPException(ResultCode.LOCAL_ERROR, 899 ERR_GSSAPI_CANNOT_CREATE_JAAS_CONFIG.get( 900 ERR_GSSAPI_NO_SUPPORTED_JAAS_MODULE.get())); 901 } 902 finally 903 { 904 w.close(); 905 } 906 } 907 catch (final LDAPException le) 908 { 909 debugException(le); 910 throw le; 911 } 912 catch (final Exception e) 913 { 914 debugException(e); 915 916 throw new LDAPException(ResultCode.LOCAL_ERROR, 917 ERR_GSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(getExceptionMessage(e)), e); 918 } 919 } 920 921 922 923 /** 924 * Writes a JAAS configuration file in a form appropriate for Sun VMs. 925 * 926 * @param w The writer to use to create the config file. 927 * @param p The properties to use for GSSAPI authentication. 928 */ 929 private static void writeSunJAASConfig(final PrintWriter w, 930 final GSSAPIBindRequestProperties p) 931 { 932 w.println(p.getJAASClientName() + " {"); 933 w.println(" com.sun.security.auth.module.Krb5LoginModule required"); 934 w.println(" client=true"); 935 936 if (p.useTicketCache()) 937 { 938 w.println(" useTicketCache=true"); 939 w.println(" renewTGT=" + p.renewTGT()); 940 w.println(" doNotPrompt=" + p.requireCachedCredentials()); 941 942 final String ticketCachePath = p.getTicketCachePath(); 943 if (ticketCachePath != null) 944 { 945 w.println(" ticketCache=\"" + ticketCachePath + '"'); 946 } 947 } 948 else 949 { 950 w.println(" useTicketCache=false"); 951 } 952 953 if (p.enableGSSAPIDebugging()) 954 { 955 w.println(" debug=true"); 956 } 957 958 w.println(" ;"); 959 w.println("};"); 960 } 961 962 963 964 /** 965 * Writes a JAAS configuration file in a form appropriate for IBM VMs. 966 * 967 * @param w The writer to use to create the config file. 968 * @param p The properties to use for GSSAPI authentication. 969 */ 970 private static void writeIBMJAASConfig(final PrintWriter w, 971 final GSSAPIBindRequestProperties p) 972 { 973 // NOTE: It does not appear that the IBM GSSAPI implementation has any 974 // analog for the renewTGT property, so it will be ignored. 975 w.println(p.getJAASClientName() + " {"); 976 w.println(" com.ibm.security.auth.module.Krb5LoginModule required"); 977 w.println(" credsType=initiator"); 978 979 if (p.useTicketCache()) 980 { 981 final String ticketCachePath = p.getTicketCachePath(); 982 if (ticketCachePath == null) 983 { 984 if (p.requireCachedCredentials()) 985 { 986 w.println(" useDefaultCcache=true"); 987 } 988 } 989 else 990 { 991 final File f = new File(ticketCachePath); 992 final String path = f.getAbsolutePath().replace('\\', '/'); 993 w.println(" useCcache=\"file://" + path + '"'); 994 } 995 } 996 else 997 { 998 w.println(" useDefaultCcache=false"); 999 } 1000 1001 if (p.enableGSSAPIDebugging()) 1002 { 1003 w.println(" debug=true"); 1004 } 1005 1006 w.println(" ;"); 1007 w.println("};"); 1008 } 1009 1010 1011 1012 /** 1013 * Sends this bind request to the target server over the provided connection 1014 * and returns the corresponding response. 1015 * 1016 * @param connection The connection to use to send this bind request to the 1017 * server and read the associated response. 1018 * @param depth The current referral depth for this request. It should 1019 * always be one for the initial request, and should only 1020 * be incremented when following referrals. 1021 * 1022 * @return The bind response read from the server. 1023 * 1024 * @throws LDAPException If a problem occurs while sending the request or 1025 * reading the response. 1026 */ 1027 @Override() 1028 protected BindResult process(final LDAPConnection connection, final int depth) 1029 throws LDAPException 1030 { 1031 if (! conn.compareAndSet(null, connection)) 1032 { 1033 throw new LDAPException(ResultCode.LOCAL_ERROR, 1034 ERR_GSSAPI_MULTIPLE_CONCURRENT_REQUESTS.get()); 1035 } 1036 1037 setProperty(PROPERTY_CONFIG_FILE, configFilePath); 1038 setProperty(PROPERTY_SUBJECT_CREDS_ONLY, 1039 String.valueOf(useSubjectCredentialsOnly)); 1040 if (debugEnabled(DebugType.LDAP)) 1041 { 1042 debug(Level.CONFIG, DebugType.LDAP, 1043 "Using config file property " + PROPERTY_CONFIG_FILE + " = '" + 1044 configFilePath + "'."); 1045 debug(Level.CONFIG, DebugType.LDAP, 1046 "Using subject creds only property " + PROPERTY_SUBJECT_CREDS_ONLY + 1047 " = '" + useSubjectCredentialsOnly + "'."); 1048 } 1049 1050 if (kdcAddress == null) 1051 { 1052 if (DEFAULT_KDC_ADDRESS == null) 1053 { 1054 clearProperty(PROPERTY_KDC_ADDRESS); 1055 if (debugEnabled(DebugType.LDAP)) 1056 { 1057 debug(Level.CONFIG, DebugType.LDAP, 1058 "Clearing kdcAddress property '" + PROPERTY_KDC_ADDRESS + "'."); 1059 } 1060 } 1061 else 1062 { 1063 setProperty(PROPERTY_KDC_ADDRESS, DEFAULT_KDC_ADDRESS); 1064 if (debugEnabled(DebugType.LDAP)) 1065 { 1066 debug(Level.CONFIG, DebugType.LDAP, 1067 "Using default kdcAddress property " + PROPERTY_KDC_ADDRESS + 1068 " = '" + DEFAULT_KDC_ADDRESS + "'."); 1069 } 1070 } 1071 } 1072 else 1073 { 1074 setProperty(PROPERTY_KDC_ADDRESS, kdcAddress); 1075 if (debugEnabled(DebugType.LDAP)) 1076 { 1077 debug(Level.CONFIG, DebugType.LDAP, 1078 "Using kdcAddress property " + PROPERTY_KDC_ADDRESS + " = '" + 1079 kdcAddress + "'."); 1080 } 1081 } 1082 1083 if (realm == null) 1084 { 1085 if (DEFAULT_REALM == null) 1086 { 1087 clearProperty(PROPERTY_REALM); 1088 if (debugEnabled(DebugType.LDAP)) 1089 { 1090 debug(Level.CONFIG, DebugType.LDAP, 1091 "Clearing realm property '" + PROPERTY_REALM + "'."); 1092 } 1093 } 1094 else 1095 { 1096 setProperty(PROPERTY_REALM, DEFAULT_REALM); 1097 if (debugEnabled(DebugType.LDAP)) 1098 { 1099 debug(Level.CONFIG, DebugType.LDAP, 1100 "Using default realm property " + PROPERTY_REALM + " = '" + 1101 DEFAULT_REALM + "'."); 1102 } 1103 } 1104 } 1105 else 1106 { 1107 setProperty(PROPERTY_REALM, realm); 1108 if (debugEnabled(DebugType.LDAP)) 1109 { 1110 debug(Level.CONFIG, DebugType.LDAP, 1111 "Using realm property " + PROPERTY_REALM + " = '" + realm + "'."); 1112 } 1113 } 1114 1115 try 1116 { 1117 final LoginContext context; 1118 try 1119 { 1120 context = new LoginContext(jaasClientName, this); 1121 context.login(); 1122 } 1123 catch (Exception e) 1124 { 1125 debugException(e); 1126 1127 throw new LDAPException(ResultCode.LOCAL_ERROR, 1128 ERR_GSSAPI_CANNOT_INITIALIZE_JAAS_CONTEXT.get( 1129 getExceptionMessage(e)), e); 1130 } 1131 1132 try 1133 { 1134 return (BindResult) Subject.doAs(context.getSubject(), this); 1135 } 1136 catch (Exception e) 1137 { 1138 debugException(e); 1139 if (e instanceof LDAPException) 1140 { 1141 throw (LDAPException) e; 1142 } 1143 else 1144 { 1145 throw new LDAPException(ResultCode.LOCAL_ERROR, 1146 ERR_GSSAPI_AUTHENTICATION_FAILED.get( 1147 getExceptionMessage(e)), e); 1148 } 1149 } 1150 } 1151 finally 1152 { 1153 conn.set(null); 1154 } 1155 } 1156 1157 1158 1159 /** 1160 * Perform the privileged portion of the authentication processing. 1161 * 1162 * @return {@code null}, since no return value is actually needed. 1163 * 1164 * @throws LDAPException If a problem occurs during processing. 1165 */ 1166 @InternalUseOnly() 1167 public Object run() 1168 throws LDAPException 1169 { 1170 unhandledCallbackMessages.clear(); 1171 1172 final LDAPConnection connection = conn.get(); 1173 1174 final String[] mechanisms = { GSSAPI_MECHANISM_NAME }; 1175 1176 final HashMap<String,Object> saslProperties = new HashMap<String,Object>(2); 1177 saslProperties.put(Sasl.QOP, SASLQualityOfProtection.toString(allowedQoP)); 1178 saslProperties.put(Sasl.SERVER_AUTH, "true"); 1179 1180 final SaslClient saslClient; 1181 try 1182 { 1183 String serverName = saslClientServerName; 1184 if (serverName == null) 1185 { 1186 serverName = connection.getConnectedAddress(); 1187 } 1188 1189 saslClient = Sasl.createSaslClient(mechanisms, authorizationID, 1190 servicePrincipalProtocol, serverName, saslProperties, this); 1191 } 1192 catch (Exception e) 1193 { 1194 debugException(e); 1195 throw new LDAPException(ResultCode.LOCAL_ERROR, 1196 ERR_GSSAPI_CANNOT_CREATE_SASL_CLIENT.get(getExceptionMessage(e)), e); 1197 } 1198 1199 final SASLHelper helper = new SASLHelper(this, connection, 1200 GSSAPI_MECHANISM_NAME, saslClient, getControls(), 1201 getResponseTimeoutMillis(connection), unhandledCallbackMessages); 1202 1203 try 1204 { 1205 return helper.processSASLBind(); 1206 } 1207 finally 1208 { 1209 messageID = helper.getMessageID(); 1210 } 1211 } 1212 1213 1214 1215 /** 1216 * {@inheritDoc} 1217 */ 1218 @Override() 1219 public GSSAPIBindRequest getRebindRequest(final String host, final int port) 1220 { 1221 try 1222 { 1223 final GSSAPIBindRequestProperties gssapiProperties = 1224 new GSSAPIBindRequestProperties(authenticationID, authorizationID, 1225 password, realm, kdcAddress, configFilePath); 1226 gssapiProperties.setAllowedQoP(allowedQoP); 1227 gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol); 1228 gssapiProperties.setUseTicketCache(useTicketCache); 1229 gssapiProperties.setRequireCachedCredentials(requireCachedCredentials); 1230 gssapiProperties.setRenewTGT(renewTGT); 1231 gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly); 1232 gssapiProperties.setTicketCachePath(ticketCachePath); 1233 gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging); 1234 gssapiProperties.setJAASClientName(jaasClientName); 1235 gssapiProperties.setSASLClientServerName(saslClientServerName); 1236 gssapiProperties.setSuppressedSystemProperties( 1237 suppressedSystemProperties); 1238 1239 return new GSSAPIBindRequest(gssapiProperties, getControls()); 1240 } 1241 catch (Exception e) 1242 { 1243 // This should never happen. 1244 debugException(e); 1245 return null; 1246 } 1247 } 1248 1249 1250 1251 /** 1252 * Handles any necessary callbacks required for SASL authentication. 1253 * 1254 * @param callbacks The set of callbacks to be handled. 1255 * 1256 * @throws UnsupportedCallbackException If an unsupported type of callback 1257 * was received. 1258 */ 1259 @InternalUseOnly() 1260 public void handle(final Callback[] callbacks) 1261 throws UnsupportedCallbackException 1262 { 1263 for (final Callback callback : callbacks) 1264 { 1265 if (callback instanceof NameCallback) 1266 { 1267 ((NameCallback) callback).setName(authenticationID); 1268 } 1269 else if (callback instanceof PasswordCallback) 1270 { 1271 if (password == null) 1272 { 1273 throw new UnsupportedCallbackException(callback, 1274 ERR_GSSAPI_NO_PASSWORD_AVAILABLE.get()); 1275 } 1276 else 1277 { 1278 ((PasswordCallback) callback).setPassword( 1279 password.stringValue().toCharArray()); 1280 } 1281 } 1282 else if (callback instanceof RealmCallback) 1283 { 1284 final RealmCallback rc = (RealmCallback) callback; 1285 if (realm == null) 1286 { 1287 unhandledCallbackMessages.add( 1288 ERR_GSSAPI_REALM_REQUIRED_BUT_NONE_PROVIDED.get(rc.getPrompt())); 1289 } 1290 else 1291 { 1292 rc.setText(realm); 1293 } 1294 } 1295 else 1296 { 1297 // This is an unexpected callback. 1298 if (debugEnabled(DebugType.LDAP)) 1299 { 1300 debug(Level.WARNING, DebugType.LDAP, 1301 "Unexpected GSSAPI SASL callback of type " + 1302 callback.getClass().getName()); 1303 } 1304 1305 unhandledCallbackMessages.add(ERR_GSSAPI_UNEXPECTED_CALLBACK.get( 1306 callback.getClass().getName())); 1307 } 1308 } 1309 } 1310 1311 1312 1313 /** 1314 * {@inheritDoc} 1315 */ 1316 @Override() 1317 public int getLastMessageID() 1318 { 1319 return messageID; 1320 } 1321 1322 1323 1324 /** 1325 * {@inheritDoc} 1326 */ 1327 @Override() 1328 public GSSAPIBindRequest duplicate() 1329 { 1330 return duplicate(getControls()); 1331 } 1332 1333 1334 1335 /** 1336 * {@inheritDoc} 1337 */ 1338 @Override() 1339 public GSSAPIBindRequest duplicate(final Control[] controls) 1340 { 1341 try 1342 { 1343 final GSSAPIBindRequestProperties gssapiProperties = 1344 new GSSAPIBindRequestProperties(authenticationID, authorizationID, 1345 password, realm, kdcAddress, configFilePath); 1346 gssapiProperties.setAllowedQoP(allowedQoP); 1347 gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol); 1348 gssapiProperties.setUseTicketCache(useTicketCache); 1349 gssapiProperties.setRequireCachedCredentials(requireCachedCredentials); 1350 gssapiProperties.setRenewTGT(renewTGT); 1351 gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly); 1352 gssapiProperties.setTicketCachePath(ticketCachePath); 1353 gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging); 1354 gssapiProperties.setJAASClientName(jaasClientName); 1355 gssapiProperties.setSASLClientServerName(saslClientServerName); 1356 gssapiProperties.setSuppressedSystemProperties( 1357 suppressedSystemProperties); 1358 1359 final GSSAPIBindRequest bindRequest = 1360 new GSSAPIBindRequest(gssapiProperties, controls); 1361 bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1362 return bindRequest; 1363 } 1364 catch (Exception e) 1365 { 1366 // This should never happen. 1367 debugException(e); 1368 return null; 1369 } 1370 } 1371 1372 1373 1374 /** 1375 * Clears the specified system property, unless it is one that is configured 1376 * to be suppressed. 1377 * 1378 * @param name The name of the property to be suppressed. 1379 */ 1380 private void clearProperty(final String name) 1381 { 1382 if (! suppressedSystemProperties.contains(name)) 1383 { 1384 System.clearProperty(name); 1385 } 1386 } 1387 1388 1389 1390 /** 1391 * Sets the specified system property, unless it is one that is configured to 1392 * be suppressed. 1393 * 1394 * @param name The name of the property to be suppressed. 1395 * @param value The value of the property to be suppressed. 1396 */ 1397 private void setProperty(final String name, final String value) 1398 { 1399 if (! suppressedSystemProperties.contains(name)) 1400 { 1401 System.setProperty(name, value); 1402 } 1403 } 1404 1405 1406 1407 /** 1408 * {@inheritDoc} 1409 */ 1410 @Override() 1411 public void toString(final StringBuilder buffer) 1412 { 1413 buffer.append("GSSAPIBindRequest(authenticationID='"); 1414 buffer.append(authenticationID); 1415 buffer.append('\''); 1416 1417 if (authorizationID != null) 1418 { 1419 buffer.append(", authorizationID='"); 1420 buffer.append(authorizationID); 1421 buffer.append('\''); 1422 } 1423 1424 if (realm != null) 1425 { 1426 buffer.append(", realm='"); 1427 buffer.append(realm); 1428 buffer.append('\''); 1429 } 1430 1431 buffer.append(", qop='"); 1432 buffer.append(SASLQualityOfProtection.toString(allowedQoP)); 1433 buffer.append('\''); 1434 1435 if (kdcAddress != null) 1436 { 1437 buffer.append(", kdcAddress='"); 1438 buffer.append(kdcAddress); 1439 buffer.append('\''); 1440 } 1441 1442 buffer.append(", jaasClientName='"); 1443 buffer.append(jaasClientName); 1444 buffer.append("', configFilePath='"); 1445 buffer.append(configFilePath); 1446 buffer.append("', servicePrincipalProtocol='"); 1447 buffer.append(servicePrincipalProtocol); 1448 buffer.append("', enableGSSAPIDebugging="); 1449 buffer.append(enableGSSAPIDebugging); 1450 1451 final Control[] controls = getControls(); 1452 if (controls.length > 0) 1453 { 1454 buffer.append(", controls={"); 1455 for (int i=0; i < controls.length; i++) 1456 { 1457 if (i > 0) 1458 { 1459 buffer.append(", "); 1460 } 1461 1462 buffer.append(controls[i]); 1463 } 1464 buffer.append('}'); 1465 } 1466 1467 buffer.append(')'); 1468 } 1469}