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