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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.io.File;
026import java.io.FileWriter;
027import java.io.PrintWriter;
028import java.security.PrivilegedExceptionAction;
029import java.util.ArrayList;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Set;
033import java.util.concurrent.atomic.AtomicReference;
034import java.util.logging.Level;
035import javax.security.auth.Subject;
036import javax.security.auth.callback.Callback;
037import javax.security.auth.callback.CallbackHandler;
038import javax.security.auth.callback.NameCallback;
039import javax.security.auth.callback.PasswordCallback;
040import javax.security.auth.callback.UnsupportedCallbackException;
041import javax.security.auth.login.LoginContext;
042import javax.security.sasl.RealmCallback;
043import javax.security.sasl.Sasl;
044import javax.security.sasl.SaslClient;
045
046import com.unboundid.asn1.ASN1OctetString;
047import com.unboundid.util.DebugType;
048import com.unboundid.util.InternalUseOnly;
049import com.unboundid.util.NotMutable;
050import com.unboundid.util.ThreadSafety;
051import com.unboundid.util.ThreadSafetyLevel;
052
053import static com.unboundid.ldap.sdk.LDAPMessages.*;
054import static com.unboundid.util.Debug.*;
055import static com.unboundid.util.StaticUtils.*;
056import 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)
125public 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}