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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.io.File;
026import java.io.FileWriter;
027import java.io.PrintWriter;
028import java.security.PrivilegedExceptionAction;
029import java.util.ArrayList;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Set;
033import java.util.concurrent.atomic.AtomicReference;
034import java.util.logging.Level;
035import javax.security.auth.Subject;
036import javax.security.auth.callback.Callback;
037import javax.security.auth.callback.CallbackHandler;
038import javax.security.auth.callback.NameCallback;
039import javax.security.auth.callback.PasswordCallback;
040import javax.security.auth.callback.UnsupportedCallbackException;
041import javax.security.auth.login.LoginContext;
042import javax.security.sasl.RealmCallback;
043import javax.security.sasl.Sasl;
044import javax.security.sasl.SaslClient;
045
046import com.unboundid.asn1.ASN1OctetString;
047import com.unboundid.util.DebugType;
048import com.unboundid.util.InternalUseOnly;
049import com.unboundid.util.NotMutable;
050import com.unboundid.util.ThreadSafety;
051import com.unboundid.util.ThreadSafetyLevel;
052
053import static com.unboundid.ldap.sdk.LDAPMessages.*;
054import static com.unboundid.util.Debug.*;
055import static com.unboundid.util.StaticUtils.*;
056import static com.unboundid.util.Validator.*;
057
058
059
060/**
061 * This class provides a SASL GSSAPI bind request implementation as described in
062 * <A HREF="http://www.ietf.org/rfc/rfc4752.txt">RFC 4752</A>.  It provides the
063 * ability to authenticate to a directory server using Kerberos V, which can
064 * serve as a kind of single sign-on mechanism that may be shared across
065 * client applications that support Kerberos.
066 * <BR><BR>
067 * This class uses the Java Authentication and Authorization Service (JAAS)
068 * behind the scenes to perform all Kerberos processing.  This framework
069 * requires a configuration file to indicate the underlying mechanism to be
070 * used.  It is possible for clients to explicitly specify the path to the
071 * configuration file that should be used, but if none is given then a default
072 * file will be created and used.  This default file should be sufficient for
073 * Sun-provided JVMs, but a custom file may be required for JVMs provided by
074 * other vendors.
075 * <BR><BR>
076 * Elements included in a GSSAPI bind request include:
077 * <UL>
078 *   <LI>Authentication ID -- A string which identifies the user that is
079 *       attempting to authenticate.  It should be the user's Kerberos
080 *       principal.</LI>
081 *   <LI>Authorization ID -- An optional string which specifies an alternate
082 *       authorization identity that should be used for subsequent operations
083 *       requested on the connection.  Like the authentication ID, the
084 *       authorization ID should be a Kerberos principal.</LI>
085 *   <LI>KDC Address -- An optional string which specifies the IP address or
086 *       resolvable name for the Kerberos key distribution center.  If this is
087 *       not provided, an attempt will be made to determine the appropriate
088 *       value from the system configuration.</LI>
089 *   <LI>Realm -- An optional string which specifies the realm into which the
090 *       user should authenticate.  If this is not provided, an attempt will be
091 *       made to determine the appropriate value from the system
092 *       configuration</LI>
093 *   <LI>Password -- The clear-text password for the target user in the Kerberos
094 *       realm.</LI>
095 * </UL>
096 * <H2>Example</H2>
097 * The following example demonstrates the process for performing a GSSAPI bind
098 * against a directory server with a username of "john.doe" and a password
099 * of "password":
100 * <PRE>
101 * GSSAPIBindRequestProperties gssapiProperties =
102 *      new GSSAPIBindRequestProperties("john.doe@EXAMPLE.COM", "password");
103 * gssapiProperties.setKDCAddress("kdc.example.com");
104 * gssapiProperties.setRealm("EXAMPLE.COM");
105 *
106 * GSSAPIBindRequest bindRequest =
107 *      new GSSAPIBindRequest(gssapiProperties);
108 * BindResult bindResult;
109 * try
110 * {
111 *   bindResult = connection.bind(bindRequest);
112 *   // If we get here, then the bind was successful.
113 * }
114 * catch (LDAPException le)
115 * {
116 *   // The bind failed for some reason.
117 *   bindResult = new BindResult(le.toLDAPResult());
118 *   ResultCode resultCode = le.getResultCode();
119 *   String errorMessageFromServer = le.getDiagnosticMessage();
120 * }
121 * </PRE>
122 */
123@NotMutable()
124@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
125public final class GSSAPIBindRequest
126       extends SASLBindRequest
127       implements CallbackHandler, PrivilegedExceptionAction<Object>
128{
129  /**
130   * The name for the GSSAPI SASL mechanism.
131   */
132  public static final String GSSAPI_MECHANISM_NAME = "GSSAPI";
133
134
135
136  /**
137   * The name of the configuration property used to specify the address of the
138   * Kerberos key distribution center.
139   */
140  private static final String PROPERTY_KDC_ADDRESS = "java.security.krb5.kdc";
141
142
143
144  /**
145   * The name of the configuration property used to specify the Kerberos realm.
146   */
147  private static final String PROPERTY_REALM = "java.security.krb5.realm";
148
149
150
151  /**
152   * The name of the configuration property used to specify the path to the JAAS
153   * configuration file.
154   */
155  private static final String PROPERTY_CONFIG_FILE =
156       "java.security.auth.login.config";
157
158
159
160  /**
161   * The name of the configuration property used to indicate whether credentials
162   * can come from somewhere other than the location specified in the JAAS
163   * configuration file.
164   */
165  private static final String PROPERTY_SUBJECT_CREDS_ONLY =
166       "javax.security.auth.useSubjectCredsOnly";
167
168
169
170  /**
171   * The value for the java.security.auth.login.config property at the time that
172   * this class was loaded.  If this is set, then it will be used in place of
173   * an automatically-generated config file.
174   */
175  private static final String DEFAULT_CONFIG_FILE =
176       System.getProperty(PROPERTY_CONFIG_FILE);
177
178
179
180  /**
181   * The default KDC address that will be used if none is explicitly configured.
182   */
183  private static final String DEFAULT_KDC_ADDRESS =
184       System.getProperty(PROPERTY_KDC_ADDRESS);
185
186
187
188  /**
189   * The default realm that will be used if none is explicitly configured.
190   */
191  private static final String DEFAULT_REALM =
192       System.getProperty(PROPERTY_REALM);
193
194
195
196  /**
197   * The serial version UID for this serializable class.
198   */
199  private static final long serialVersionUID = 2511890818146955112L;
200
201
202
203  // The password for the GSSAPI bind request.
204  private final ASN1OctetString password;
205
206  // A reference to the connection to use for bind processing.
207  private final AtomicReference<LDAPConnection> conn;
208
209  // Indicates whether to enable JVM-level debugging for GSSAPI processing.
210  private final boolean enableGSSAPIDebugging;
211
212  // Indicates whether to attempt to renew the client's existing ticket-granting
213  // ticket if authentication uses an existing Kerberos session.
214  private final boolean renewTGT;
215
216  // Indicates whether to require that the credentials be obtained from the
217  // ticket cache such that authentication will fail if the client does not have
218  // an existing Kerberos session.
219  private final boolean requireCachedCredentials;
220
221  // Indicates whether to allow the client to use credentials that are outside
222  // of the current subject.
223  private final boolean useSubjectCredentialsOnly;
224
225  // Indicates whether to enable the use pf a ticket cache.
226  private final boolean useTicketCache;
227
228  // The message ID from the last LDAP message sent from this request.
229  private int messageID;
230
231  // The SASL quality of protection value(s) allowed for the DIGEST-MD5 bind
232  // request.
233  private final List<SASLQualityOfProtection> allowedQoP;
234
235  // A list that will be updated with messages about any unhandled callbacks
236  // encountered during processing.
237  private final List<String> unhandledCallbackMessages;
238
239  // The names of any system properties that should not be altered by GSSAPI
240  // processing.
241  private Set<String> suppressedSystemProperties;
242
243  // The authentication ID string for the GSSAPI bind request.
244  private final String authenticationID;
245
246  // The authorization ID string for the GSSAPI bind request, if available.
247  private final String authorizationID;
248
249  // The path to the JAAS configuration file to use for bind processing.
250  private final String configFilePath;
251
252  // The name that will be used to identify this client in the JAAS framework.
253  private final String jaasClientName;
254
255  // The KDC address for the GSSAPI bind request, if available.
256  private final String kdcAddress;
257
258  // The realm for the GSSAPI bind request, if available.
259  private final String realm;
260
261  // The server name that should be used when creating the Java SaslClient, if
262  // defined.
263  private final String saslClientServerName;
264
265  // The protocol that should be used in the Kerberos service principal for
266  // the server system.
267  private final String servicePrincipalProtocol;
268
269  // The path to the Kerberos ticket cache to use.
270  private final String ticketCachePath;
271
272
273
274  /**
275   * Creates a new SASL GSSAPI bind request with the provided authentication ID
276   * and password.
277   *
278   * @param  authenticationID  The authentication ID for this bind request.  It
279   *                           must not be {@code null}.
280   * @param  password          The password for this bind request.  It must not
281   *                           be {@code null}.
282   *
283   * @throws  LDAPException  If a problem occurs while creating the JAAS
284   *                         configuration file to use during authentication
285   *                         processing.
286   */
287  public GSSAPIBindRequest(final String authenticationID, final String password)
288         throws LDAPException
289  {
290    this(new GSSAPIBindRequestProperties(authenticationID, password));
291  }
292
293
294
295  /**
296   * Creates a new SASL GSSAPI bind request with the provided authentication ID
297   * and password.
298   *
299   * @param  authenticationID  The authentication ID for this bind request.  It
300   *                           must not be {@code null}.
301   * @param  password          The password for this bind request.  It must not
302   *                           be {@code null}.
303   *
304   * @throws  LDAPException  If a problem occurs while creating the JAAS
305   *                         configuration file to use during authentication
306   *                         processing.
307   */
308  public GSSAPIBindRequest(final String authenticationID, final byte[] password)
309         throws LDAPException
310  {
311    this(new GSSAPIBindRequestProperties(authenticationID, password));
312  }
313
314
315
316  /**
317   * Creates a new SASL GSSAPI bind request with the provided authentication ID
318   * and password.
319   *
320   * @param  authenticationID  The authentication ID for this bind request.  It
321   *                           must not be {@code null}.
322   * @param  password          The password for this bind request.  It must not
323   *                           be {@code null}.
324   * @param  controls          The set of controls to include in the request.
325   *
326   * @throws  LDAPException  If a problem occurs while creating the JAAS
327   *                         configuration file to use during authentication
328   *                         processing.
329   */
330  public GSSAPIBindRequest(final String authenticationID, final String password,
331                           final Control[] controls)
332         throws LDAPException
333  {
334    this(new GSSAPIBindRequestProperties(authenticationID, password), controls);
335  }
336
337
338
339  /**
340   * Creates a new SASL GSSAPI bind request with the provided authentication ID
341   * and password.
342   *
343   * @param  authenticationID  The authentication ID for this bind request.  It
344   *                           must not be {@code null}.
345   * @param  password          The password for this bind request.  It must not
346   *                           be {@code null}.
347   * @param  controls          The set of controls to include in the request.
348   *
349   * @throws  LDAPException  If a problem occurs while creating the JAAS
350   *                         configuration file to use during authentication
351   *                         processing.
352   */
353  public GSSAPIBindRequest(final String authenticationID, final byte[] password,
354                           final Control[] controls)
355         throws LDAPException
356  {
357    this(new GSSAPIBindRequestProperties(authenticationID, password), controls);
358  }
359
360
361
362  /**
363   * Creates a new SASL GSSAPI bind request with the provided information.
364   *
365   * @param  authenticationID  The authentication ID for this bind request.  It
366   *                           must not be {@code null}.
367   * @param  authorizationID   The authorization ID for this bind request.  It
368   *                           may be {@code null} if no alternate authorization
369   *                           ID should be used.
370   * @param  password          The password for this bind request.  It must not
371   *                           be {@code null}.
372   * @param  realm             The realm to use for the authentication.  It may
373   *                           be {@code null} to attempt to use the default
374   *                           realm from the system configuration.
375   * @param  kdcAddress        The address of the Kerberos key distribution
376   *                           center.  It may be {@code null} to attempt to use
377   *                           the default KDC from the system configuration.
378   * @param  configFilePath    The path to the JAAS configuration file to use
379   *                           for the authentication processing.  It may be
380   *                           {@code null} to use the default JAAS
381   *                           configuration.
382   *
383   * @throws  LDAPException  If a problem occurs while creating the JAAS
384   *                         configuration file to use during authentication
385   *                         processing.
386   */
387  public GSSAPIBindRequest(final String authenticationID,
388                           final String authorizationID, final String password,
389                           final String realm, final String kdcAddress,
390                           final String configFilePath)
391         throws LDAPException
392  {
393    this(new GSSAPIBindRequestProperties(authenticationID, authorizationID,
394         new ASN1OctetString(password), realm, kdcAddress, configFilePath));
395  }
396
397
398
399  /**
400   * Creates a new SASL GSSAPI bind request with the provided information.
401   *
402   * @param  authenticationID  The authentication ID for this bind request.  It
403   *                           must not be {@code null}.
404   * @param  authorizationID   The authorization ID for this bind request.  It
405   *                           may be {@code null} if no alternate authorization
406   *                           ID should be used.
407   * @param  password          The password for this bind request.  It must not
408   *                           be {@code null}.
409   * @param  realm             The realm to use for the authentication.  It may
410   *                           be {@code null} to attempt to use the default
411   *                           realm from the system configuration.
412   * @param  kdcAddress        The address of the Kerberos key distribution
413   *                           center.  It may be {@code null} to attempt to use
414   *                           the default KDC from the system configuration.
415   * @param  configFilePath    The path to the JAAS configuration file to use
416   *                           for the authentication processing.  It may be
417   *                           {@code null} to use the default JAAS
418   *                           configuration.
419   *
420   * @throws  LDAPException  If a problem occurs while creating the JAAS
421   *                         configuration file to use during authentication
422   *                         processing.
423   */
424  public GSSAPIBindRequest(final String authenticationID,
425                           final String authorizationID, final byte[] password,
426                           final String realm, final String kdcAddress,
427                           final String configFilePath)
428         throws LDAPException
429  {
430    this(new GSSAPIBindRequestProperties(authenticationID, authorizationID,
431         new ASN1OctetString(password), realm, kdcAddress, configFilePath));
432  }
433
434
435
436  /**
437   * Creates a new SASL GSSAPI bind request with the provided information.
438   *
439   * @param  authenticationID  The authentication ID for this bind request.  It
440   *                           must not be {@code null}.
441   * @param  authorizationID   The authorization ID for this bind request.  It
442   *                           may be {@code null} if no alternate authorization
443   *                           ID should be used.
444   * @param  password          The password for this bind request.  It must not
445   *                           be {@code null}.
446   * @param  realm             The realm to use for the authentication.  It may
447   *                           be {@code null} to attempt to use the default
448   *                           realm from the system configuration.
449   * @param  kdcAddress        The address of the Kerberos key distribution
450   *                           center.  It may be {@code null} to attempt to use
451   *                           the default KDC from the system configuration.
452   * @param  configFilePath    The path to the JAAS configuration file to use
453   *                           for the authentication processing.  It may be
454   *                           {@code null} to use the default JAAS
455   *                           configuration.
456   * @param  controls          The set of controls to include in the request.
457   *
458   * @throws  LDAPException  If a problem occurs while creating the JAAS
459   *                         configuration file to use during authentication
460   *                         processing.
461   */
462  public GSSAPIBindRequest(final String authenticationID,
463                           final String authorizationID, final String password,
464                           final String realm, final String kdcAddress,
465                           final String configFilePath,
466                           final Control[] controls)
467         throws LDAPException
468  {
469    this(new GSSAPIBindRequestProperties(authenticationID, authorizationID,
470         new ASN1OctetString(password), realm, kdcAddress, configFilePath),
471         controls);
472  }
473
474
475
476  /**
477   * Creates a new SASL GSSAPI bind request with the provided information.
478   *
479   * @param  authenticationID  The authentication ID for this bind request.  It
480   *                           must not be {@code null}.
481   * @param  authorizationID   The authorization ID for this bind request.  It
482   *                           may be {@code null} if no alternate authorization
483   *                           ID should be used.
484   * @param  password          The password for this bind request.  It must not
485   *                           be {@code null}.
486   * @param  realm             The realm to use for the authentication.  It may
487   *                           be {@code null} to attempt to use the default
488   *                           realm from the system configuration.
489   * @param  kdcAddress        The address of the Kerberos key distribution
490   *                           center.  It may be {@code null} to attempt to use
491   *                           the default KDC from the system configuration.
492   * @param  configFilePath    The path to the JAAS configuration file to use
493   *                           for the authentication processing.  It may be
494   *                           {@code null} to use the default JAAS
495   *                           configuration.
496   * @param  controls          The set of controls to include in the request.
497   *
498   * @throws  LDAPException  If a problem occurs while creating the JAAS
499   *                         configuration file to use during authentication
500   *                         processing.
501   */
502  public GSSAPIBindRequest(final String authenticationID,
503                           final String authorizationID, final byte[] password,
504                           final String realm, final String kdcAddress,
505                           final String configFilePath,
506                           final Control[] controls)
507         throws LDAPException
508  {
509    this(new GSSAPIBindRequestProperties(authenticationID, authorizationID,
510         new ASN1OctetString(password), realm, kdcAddress, configFilePath),
511         controls);
512  }
513
514
515
516  /**
517   * Creates a new SASL GSSAPI bind request with the provided set of properties.
518   *
519   * @param  gssapiProperties  The set of properties that should be used for
520   *                           the GSSAPI bind request.  It must not be
521   *                           {@code null}.
522   * @param  controls          The set of controls to include in the request.
523   *
524   * @throws  LDAPException  If a problem occurs while creating the JAAS
525   *                         configuration file to use during authentication
526   *                         processing.
527   */
528  public GSSAPIBindRequest(final GSSAPIBindRequestProperties gssapiProperties,
529                           final Control... controls)
530          throws LDAPException
531  {
532    super(controls);
533
534    ensureNotNull(gssapiProperties);
535
536    authenticationID           = gssapiProperties.getAuthenticationID();
537    password                   = gssapiProperties.getPassword();
538    realm                      = gssapiProperties.getRealm();
539    allowedQoP                 = gssapiProperties.getAllowedQoP();
540    kdcAddress                 = gssapiProperties.getKDCAddress();
541    jaasClientName             = gssapiProperties.getJAASClientName();
542    saslClientServerName       = gssapiProperties.getSASLClientServerName();
543    servicePrincipalProtocol   = gssapiProperties.getServicePrincipalProtocol();
544    enableGSSAPIDebugging      = gssapiProperties.enableGSSAPIDebugging();
545    useSubjectCredentialsOnly  = gssapiProperties.useSubjectCredentialsOnly();
546    useTicketCache             = gssapiProperties.useTicketCache();
547    requireCachedCredentials   = gssapiProperties.requireCachedCredentials();
548    renewTGT                   = gssapiProperties.renewTGT();
549    ticketCachePath            = gssapiProperties.getTicketCachePath();
550    suppressedSystemProperties =
551         gssapiProperties.getSuppressedSystemProperties();
552
553    unhandledCallbackMessages = new ArrayList<String>(5);
554
555    conn      = new AtomicReference<LDAPConnection>();
556    messageID = -1;
557
558    final String authzID = gssapiProperties.getAuthorizationID();
559    if (authzID == null)
560    {
561      authorizationID = null;
562    }
563    else
564    {
565      authorizationID = authzID;
566    }
567
568    final String cfgPath = gssapiProperties.getConfigFilePath();
569    if (cfgPath == null)
570    {
571      if (DEFAULT_CONFIG_FILE == null)
572      {
573        configFilePath = getConfigFilePath(gssapiProperties);
574      }
575      else
576      {
577        configFilePath = DEFAULT_CONFIG_FILE;
578      }
579    }
580    else
581    {
582      configFilePath = cfgPath;
583    }
584  }
585
586
587
588  /**
589   * {@inheritDoc}
590   */
591  @Override()
592  public String getSASLMechanismName()
593  {
594    return GSSAPI_MECHANISM_NAME;
595  }
596
597
598
599  /**
600   * Retrieves the authentication ID for the GSSAPI bind request, if defined.
601   *
602   * @return  The authentication ID for the GSSAPI bind request, or {@code null}
603   *          if an existing Kerberos session should be used.
604   */
605  public String getAuthenticationID()
606  {
607    return authenticationID;
608  }
609
610
611
612  /**
613   * Retrieves the authorization ID for this bind request, if any.
614   *
615   * @return  The authorization ID for this bind request, or {@code null} if
616   *          there should not be a separate authorization identity.
617   */
618  public String getAuthorizationID()
619  {
620    return authorizationID;
621  }
622
623
624
625  /**
626   * Retrieves the string representation of the password for this bind request,
627   * if defined.
628   *
629   * @return  The string representation of the password for this bind request,
630   *          or {@code null} if an existing Kerberos session should be used.
631   */
632  public String getPasswordString()
633  {
634    if (password == null)
635    {
636      return null;
637    }
638    else
639    {
640      return password.stringValue();
641    }
642  }
643
644
645
646  /**
647   * Retrieves the bytes that comprise the the password for this bind request,
648   * if defined.
649   *
650   * @return  The bytes that comprise the password for this bind request, or
651   *          {@code null} if an existing Kerberos session should be used.
652   */
653  public byte[] getPasswordBytes()
654  {
655    if (password == null)
656    {
657      return null;
658    }
659    else
660    {
661      return password.getValue();
662    }
663  }
664
665
666
667  /**
668   * Retrieves the realm for this bind request, if any.
669   *
670   * @return  The realm for this bind request, or {@code null} if none was
671   *          defined and the client should attempt to determine the realm from
672   *          the system configuration.
673   */
674  public String getRealm()
675  {
676    return realm;
677  }
678
679
680
681  /**
682   * Retrieves the list of allowed qualities of protection that may be used for
683   * communication that occurs on the connection after the authentication has
684   * completed, in order from most preferred to least preferred.
685   *
686   * @return  The list of allowed qualities of protection that may be used for
687   *          communication that occurs on the connection after the
688   *          authentication has completed, in order from most preferred to
689   *          least preferred.
690   */
691  public List<SASLQualityOfProtection> getAllowedQoP()
692  {
693    return allowedQoP;
694  }
695
696
697
698  /**
699   * Retrieves the address of the Kerberos key distribution center.
700   *
701   * @return  The address of the Kerberos key distribution center, or
702   *          {@code null} if none was defined and the client should attempt to
703   *          determine the KDC address from the system configuration.
704   */
705  public String getKDCAddress()
706  {
707    return kdcAddress;
708  }
709
710
711
712  /**
713   * Retrieves the path to the JAAS configuration file that will be used during
714   * authentication processing.
715   *
716   * @return  The path to the JAAS configuration file that will be used during
717   *          authentication processing.
718   */
719  public String getConfigFilePath()
720  {
721    return configFilePath;
722  }
723
724
725
726  /**
727   * Retrieves the protocol specified in the service principal that the
728   * directory server uses for its communication with the KDC.
729   *
730   * @return  The protocol specified in the service principal that the directory
731   *          server uses for its communication with the KDC.
732   */
733  public String getServicePrincipalProtocol()
734  {
735    return servicePrincipalProtocol;
736  }
737
738
739
740  /**
741   * Indicates whether to enable the use of a ticket cache to to avoid the need
742   * to supply credentials if the client already has an existing Kerberos
743   * session.
744   *
745   * @return  {@code true} if a ticket cache may be used to take advantage of an
746   *          existing Kerberos session, or {@code false} if Kerberos
747   *          credentials should always be provided.
748   */
749  public boolean useTicketCache()
750  {
751    return useTicketCache;
752  }
753
754
755
756  /**
757   * Indicates whether GSSAPI authentication should only occur using an existing
758   * Kerberos session.
759   *
760   * @return  {@code true} if GSSAPI authentication should only use an existing
761   *          Kerberos session and should fail if the client does not have an
762   *          existing session, or {@code false} if the client will be allowed
763   *          to create a new session if one does not already exist.
764   */
765  public boolean requireCachedCredentials()
766  {
767    return requireCachedCredentials;
768  }
769
770
771
772  /**
773   * Retrieves the path to the Kerberos ticket cache file that should be used
774   * during authentication, if defined.
775   *
776   * @return  The path to the Kerberos ticket cache file that should be used
777   *          during authentication, or {@code null} if the default ticket cache
778   *          file should be used.
779   */
780  public String getTicketCachePath()
781  {
782    return ticketCachePath;
783  }
784
785
786
787  /**
788   * Indicates whether to attempt to renew the client's ticket-granting ticket
789   * (TGT) if an existing Kerberos session is used to authenticate.
790   *
791   * @return  {@code true} if the client should attempt to renew its
792   *          ticket-granting ticket if the authentication is processed using an
793   *          existing Kerberos session, or {@code false} if not.
794   */
795  public boolean renewTGT()
796  {
797    return renewTGT;
798  }
799
800
801
802  /**
803   * 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}