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 */
021 package com.unboundid.ldap.sdk;
022
023
024
025 import java.io.File;
026 import java.io.FileWriter;
027 import java.io.PrintWriter;
028 import java.security.PrivilegedExceptionAction;
029 import java.util.ArrayList;
030 import java.util.HashMap;
031 import java.util.List;
032 import java.util.Set;
033 import java.util.concurrent.atomic.AtomicReference;
034 import java.util.logging.Level;
035 import javax.security.auth.Subject;
036 import javax.security.auth.callback.Callback;
037 import javax.security.auth.callback.CallbackHandler;
038 import javax.security.auth.callback.NameCallback;
039 import javax.security.auth.callback.PasswordCallback;
040 import javax.security.auth.callback.UnsupportedCallbackException;
041 import javax.security.auth.login.LoginContext;
042 import javax.security.sasl.RealmCallback;
043 import javax.security.sasl.Sasl;
044 import javax.security.sasl.SaslClient;
045
046 import com.unboundid.asn1.ASN1OctetString;
047 import com.unboundid.util.DebugType;
048 import com.unboundid.util.InternalUseOnly;
049 import com.unboundid.util.NotMutable;
050 import com.unboundid.util.ThreadSafety;
051 import com.unboundid.util.ThreadSafetyLevel;
052
053 import static com.unboundid.ldap.sdk.LDAPMessages.*;
054 import static com.unboundid.util.Debug.*;
055 import static com.unboundid.util.StaticUtils.*;
056 import 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)
125 public 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 }