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