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