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