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