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