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