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 */
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 * 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 }