001/*
002 * Copyright 2009-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2009-2019 Ping Identity Corporation
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.Debug;
048import com.unboundid.util.DebugType;
049import com.unboundid.util.InternalUseOnly;
050import com.unboundid.util.NotMutable;
051import com.unboundid.util.StaticUtils;
052import com.unboundid.util.ThreadSafety;
053import com.unboundid.util.ThreadSafetyLevel;
054import com.unboundid.util.Validator;
055
056import static com.unboundid.ldap.sdk.LDAPMessages.*;
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 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    Validator.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<>(5);
573
574    conn      = new AtomicReference<>();
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          Debug.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          Debug.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      Debug.debugException(le);
1008      throw le;
1009    }
1010    catch (final Exception e)
1011    {
1012      Debug.debugException(e);
1013
1014      throw new LDAPException(ResultCode.LOCAL_ERROR,
1015           ERR_GSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(
1016                StaticUtils.getExceptionMessage(e)),
1017           e);
1018    }
1019  }
1020
1021
1022
1023  /**
1024   * Writes a JAAS configuration file in a form appropriate for Sun VMs.
1025   *
1026   * @param  w  The writer to use to create the config file.
1027   * @param  p  The properties to use for GSSAPI authentication.
1028   */
1029  private static void writeSunJAASConfig(final PrintWriter w,
1030                                         final GSSAPIBindRequestProperties p)
1031  {
1032    w.println(p.getJAASClientName() + " {");
1033    w.println("  com.sun.security.auth.module.Krb5LoginModule required");
1034    w.println("  client=true");
1035
1036    if (p.getIsInitiator() != null)
1037    {
1038      w.println("  isInitiator=" + p.getIsInitiator());
1039    }
1040
1041    if (p.refreshKrb5Config())
1042    {
1043      w.println("  refreshKrb5Config=true");
1044    }
1045
1046    if (p.useKeyTab())
1047    {
1048      w.println("  useKeyTab=true");
1049      if (p.getKeyTabPath() != null)
1050      {
1051        w.println("  keyTab=\"" + p.getKeyTabPath() + '"');
1052      }
1053    }
1054
1055    if (p.useTicketCache())
1056    {
1057      w.println("  useTicketCache=true");
1058      w.println("  renewTGT=" + p.renewTGT());
1059      w.println("  doNotPrompt=" + p.requireCachedCredentials());
1060
1061      final String ticketCachePath = p.getTicketCachePath();
1062      if (ticketCachePath != null)
1063      {
1064        w.println("  ticketCache=\"" + ticketCachePath + '"');
1065      }
1066    }
1067    else
1068    {
1069      w.println("  useTicketCache=false");
1070    }
1071
1072    if (p.enableGSSAPIDebugging())
1073    {
1074      w.println(" debug=true");
1075    }
1076
1077    w.println("  ;");
1078    w.println("};");
1079  }
1080
1081
1082
1083  /**
1084   * Writes a JAAS configuration file in a form appropriate for IBM VMs.
1085   *
1086   * @param  w  The writer to use to create the config file.
1087   * @param  p  The properties to use for GSSAPI authentication.
1088   */
1089  private static void writeIBMJAASConfig(final PrintWriter w,
1090                                         final GSSAPIBindRequestProperties p)
1091  {
1092    // NOTE:  It does not appear that the IBM GSSAPI implementation has any
1093    // analog for the renewTGT property, so it will be ignored.
1094    w.println(p.getJAASClientName() + " {");
1095    w.println("  com.ibm.security.auth.module.Krb5LoginModule required");
1096    if ((p.getIsInitiator() == null) || p.getIsInitiator().booleanValue())
1097    {
1098      w.println("  credsType=initiator");
1099    }
1100    else
1101    {
1102      w.println("  credsType=acceptor");
1103    }
1104
1105    if (p.refreshKrb5Config())
1106    {
1107      w.println("  refreshKrb5Config=true");
1108    }
1109
1110    if (p.useKeyTab())
1111    {
1112      w.println("  useKeyTab=true");
1113      if (p.getKeyTabPath() != null)
1114      {
1115        w.println("  keyTab=\"" + p.getKeyTabPath() + '"');
1116      }
1117    }
1118
1119    if (p.useTicketCache())
1120    {
1121      final String ticketCachePath = p.getTicketCachePath();
1122      if (ticketCachePath == null)
1123      {
1124        if (p.requireCachedCredentials())
1125        {
1126          w.println("  useDefaultCcache=true");
1127        }
1128      }
1129      else
1130      {
1131        final File f = new File(ticketCachePath);
1132        final String path = f.getAbsolutePath().replace('\\', '/');
1133        w.println("  useCcache=\"file://" + path + '"');
1134      }
1135    }
1136    else
1137    {
1138      w.println("  useDefaultCcache=false");
1139    }
1140
1141    if (p.enableGSSAPIDebugging())
1142    {
1143      w.println(" debug=true");
1144    }
1145
1146    w.println("  ;");
1147    w.println("};");
1148  }
1149
1150
1151
1152  /**
1153   * Sends this bind request to the target server over the provided connection
1154   * and returns the corresponding response.
1155   *
1156   * @param  connection  The connection to use to send this bind request to the
1157   *                     server and read the associated response.
1158   * @param  depth       The current referral depth for this request.  It should
1159   *                     always be one for the initial request, and should only
1160   *                     be incremented when following referrals.
1161   *
1162   * @return  The bind response read from the server.
1163   *
1164   * @throws  LDAPException  If a problem occurs while sending the request or
1165   *                         reading the response.
1166   */
1167  @Override()
1168  protected BindResult process(final LDAPConnection connection, final int depth)
1169            throws LDAPException
1170  {
1171    if (! conn.compareAndSet(null, connection))
1172    {
1173      throw new LDAPException(ResultCode.LOCAL_ERROR,
1174                     ERR_GSSAPI_MULTIPLE_CONCURRENT_REQUESTS.get());
1175    }
1176
1177    setProperty(PROPERTY_CONFIG_FILE, configFilePath);
1178    setProperty(PROPERTY_SUBJECT_CREDS_ONLY,
1179         String.valueOf(useSubjectCredentialsOnly));
1180    if (Debug.debugEnabled(DebugType.LDAP))
1181    {
1182      Debug.debug(Level.CONFIG, DebugType.LDAP,
1183           "Using config file property " + PROPERTY_CONFIG_FILE + " = '" +
1184                configFilePath + "'.");
1185      Debug.debug(Level.CONFIG, DebugType.LDAP,
1186           "Using subject creds only property " + PROPERTY_SUBJECT_CREDS_ONLY +
1187                " = '" + useSubjectCredentialsOnly + "'.");
1188    }
1189
1190    if (kdcAddress == null)
1191    {
1192      if (DEFAULT_KDC_ADDRESS == null)
1193      {
1194        clearProperty(PROPERTY_KDC_ADDRESS);
1195        if (Debug.debugEnabled(DebugType.LDAP))
1196        {
1197          Debug.debug(Level.CONFIG, DebugType.LDAP,
1198               "Clearing kdcAddress property '" + PROPERTY_KDC_ADDRESS + "'.");
1199        }
1200      }
1201      else
1202      {
1203        setProperty(PROPERTY_KDC_ADDRESS, DEFAULT_KDC_ADDRESS);
1204        if (Debug.debugEnabled(DebugType.LDAP))
1205        {
1206          Debug.debug(Level.CONFIG, DebugType.LDAP,
1207               "Using default kdcAddress property " + PROPERTY_KDC_ADDRESS +
1208                    " = '" + DEFAULT_KDC_ADDRESS + "'.");
1209        }
1210      }
1211    }
1212    else
1213    {
1214      setProperty(PROPERTY_KDC_ADDRESS, kdcAddress);
1215      if (Debug.debugEnabled(DebugType.LDAP))
1216      {
1217        Debug.debug(Level.CONFIG, DebugType.LDAP,
1218             "Using kdcAddress property " + PROPERTY_KDC_ADDRESS + " = '" +
1219                  kdcAddress + "'.");
1220      }
1221    }
1222
1223    if (realm == null)
1224    {
1225      if (DEFAULT_REALM == null)
1226      {
1227        clearProperty(PROPERTY_REALM);
1228        if (Debug.debugEnabled(DebugType.LDAP))
1229        {
1230          Debug.debug(Level.CONFIG, DebugType.LDAP,
1231               "Clearing realm property '" + PROPERTY_REALM + "'.");
1232        }
1233      }
1234      else
1235      {
1236        setProperty(PROPERTY_REALM, DEFAULT_REALM);
1237        if (Debug.debugEnabled(DebugType.LDAP))
1238        {
1239          Debug.debug(Level.CONFIG, DebugType.LDAP,
1240               "Using default realm property " + PROPERTY_REALM + " = '" +
1241                    DEFAULT_REALM + "'.");
1242        }
1243      }
1244    }
1245    else
1246    {
1247      setProperty(PROPERTY_REALM, realm);
1248      if (Debug.debugEnabled(DebugType.LDAP))
1249      {
1250        Debug.debug(Level.CONFIG, DebugType.LDAP,
1251             "Using realm property " + PROPERTY_REALM + " = '" + realm + "'.");
1252      }
1253    }
1254
1255    try
1256    {
1257      final LoginContext context;
1258      try
1259      {
1260        context = new LoginContext(jaasClientName, this);
1261        context.login();
1262      }
1263      catch (final Exception e)
1264      {
1265        Debug.debugException(e);
1266
1267        throw new LDAPException(ResultCode.LOCAL_ERROR,
1268             ERR_GSSAPI_CANNOT_INITIALIZE_JAAS_CONTEXT.get(
1269                  StaticUtils.getExceptionMessage(e)),
1270             e);
1271      }
1272
1273      try
1274      {
1275        return (BindResult) Subject.doAs(context.getSubject(), this);
1276      }
1277      catch (final Exception e)
1278      {
1279        Debug.debugException(e);
1280        if (e instanceof LDAPException)
1281        {
1282          throw (LDAPException) e;
1283        }
1284        else
1285        {
1286          throw new LDAPException(ResultCode.LOCAL_ERROR,
1287               ERR_GSSAPI_AUTHENTICATION_FAILED.get(
1288                    StaticUtils.getExceptionMessage(e)),
1289               e);
1290        }
1291      }
1292    }
1293    finally
1294    {
1295      conn.set(null);
1296    }
1297  }
1298
1299
1300
1301  /**
1302   * Perform the privileged portion of the authentication processing.
1303   *
1304   * @return  {@code null}, since no return value is actually needed.
1305   *
1306   * @throws  LDAPException  If a problem occurs during processing.
1307   */
1308  @InternalUseOnly()
1309  @Override()
1310  public Object run()
1311         throws LDAPException
1312  {
1313    unhandledCallbackMessages.clear();
1314
1315    final LDAPConnection connection = conn.get();
1316
1317
1318    final HashMap<String,Object> saslProperties =
1319         new HashMap<>(StaticUtils.computeMapCapacity(2));
1320    saslProperties.put(Sasl.QOP, SASLQualityOfProtection.toString(allowedQoP));
1321    saslProperties.put(Sasl.SERVER_AUTH, "true");
1322
1323    final SaslClient saslClient;
1324    try
1325    {
1326      String serverName = saslClientServerName;
1327      if (serverName == null)
1328      {
1329        serverName = connection.getConnectedAddress();
1330      }
1331
1332      final String[] mechanisms = { GSSAPI_MECHANISM_NAME };
1333      saslClient = Sasl.createSaslClient(mechanisms, authorizationID,
1334           servicePrincipalProtocol, serverName, saslProperties, this);
1335    }
1336    catch (final Exception e)
1337    {
1338      Debug.debugException(e);
1339      throw new LDAPException(ResultCode.LOCAL_ERROR,
1340           ERR_GSSAPI_CANNOT_CREATE_SASL_CLIENT.get(
1341                StaticUtils.getExceptionMessage(e)),
1342           e);
1343    }
1344
1345    final SASLHelper helper = new SASLHelper(this, connection,
1346         GSSAPI_MECHANISM_NAME, saslClient, getControls(),
1347         getResponseTimeoutMillis(connection), unhandledCallbackMessages);
1348
1349    try
1350    {
1351      return helper.processSASLBind();
1352    }
1353    finally
1354    {
1355      messageID = helper.getMessageID();
1356    }
1357  }
1358
1359
1360
1361  /**
1362   * {@inheritDoc}
1363   */
1364  @Override()
1365  public GSSAPIBindRequest getRebindRequest(final String host, final int port)
1366  {
1367    try
1368    {
1369      final GSSAPIBindRequestProperties gssapiProperties =
1370           new GSSAPIBindRequestProperties(authenticationID, authorizationID,
1371                password, realm, kdcAddress, configFilePath);
1372      gssapiProperties.setAllowedQoP(allowedQoP);
1373      gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol);
1374      gssapiProperties.setUseTicketCache(useTicketCache);
1375      gssapiProperties.setRequireCachedCredentials(requireCachedCredentials);
1376      gssapiProperties.setRenewTGT(renewTGT);
1377      gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly);
1378      gssapiProperties.setTicketCachePath(ticketCachePath);
1379      gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging);
1380      gssapiProperties.setJAASClientName(jaasClientName);
1381      gssapiProperties.setSASLClientServerName(saslClientServerName);
1382      gssapiProperties.setSuppressedSystemProperties(
1383           suppressedSystemProperties);
1384
1385      return new GSSAPIBindRequest(gssapiProperties, getControls());
1386    }
1387    catch (final Exception e)
1388    {
1389      // This should never happen.
1390      Debug.debugException(e);
1391      return null;
1392    }
1393  }
1394
1395
1396
1397  /**
1398   * Handles any necessary callbacks required for SASL authentication.
1399   *
1400   * @param  callbacks  The set of callbacks to be handled.
1401   *
1402   * @throws  UnsupportedCallbackException  If an unsupported type of callback
1403   *                                        was received.
1404   */
1405  @InternalUseOnly()
1406  @Override()
1407  public void handle(final Callback[] callbacks)
1408         throws UnsupportedCallbackException
1409  {
1410    for (final Callback callback : callbacks)
1411    {
1412      if (callback instanceof NameCallback)
1413      {
1414        ((NameCallback) callback).setName(authenticationID);
1415      }
1416      else if (callback instanceof PasswordCallback)
1417      {
1418        if (password == null)
1419        {
1420          throw new UnsupportedCallbackException(callback,
1421               ERR_GSSAPI_NO_PASSWORD_AVAILABLE.get());
1422        }
1423        else
1424        {
1425          ((PasswordCallback) callback).setPassword(
1426               password.stringValue().toCharArray());
1427        }
1428      }
1429      else if (callback instanceof RealmCallback)
1430      {
1431        final RealmCallback rc = (RealmCallback) callback;
1432        if (realm == null)
1433        {
1434          unhandledCallbackMessages.add(
1435               ERR_GSSAPI_REALM_REQUIRED_BUT_NONE_PROVIDED.get(rc.getPrompt()));
1436        }
1437        else
1438        {
1439          rc.setText(realm);
1440        }
1441      }
1442      else
1443      {
1444        // This is an unexpected callback.
1445        if (Debug.debugEnabled(DebugType.LDAP))
1446        {
1447          Debug.debug(Level.WARNING, DebugType.LDAP,
1448                "Unexpected GSSAPI SASL callback of type " +
1449                callback.getClass().getName());
1450        }
1451
1452        unhandledCallbackMessages.add(ERR_GSSAPI_UNEXPECTED_CALLBACK.get(
1453             callback.getClass().getName()));
1454      }
1455    }
1456  }
1457
1458
1459
1460  /**
1461   * {@inheritDoc}
1462   */
1463  @Override()
1464  public int getLastMessageID()
1465  {
1466    return messageID;
1467  }
1468
1469
1470
1471  /**
1472   * {@inheritDoc}
1473   */
1474  @Override()
1475  public GSSAPIBindRequest duplicate()
1476  {
1477    return duplicate(getControls());
1478  }
1479
1480
1481
1482  /**
1483   * {@inheritDoc}
1484   */
1485  @Override()
1486  public GSSAPIBindRequest duplicate(final Control[] controls)
1487  {
1488    try
1489    {
1490      final GSSAPIBindRequestProperties gssapiProperties =
1491           new GSSAPIBindRequestProperties(authenticationID, authorizationID,
1492                password, realm, kdcAddress, configFilePath);
1493      gssapiProperties.setAllowedQoP(allowedQoP);
1494      gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol);
1495      gssapiProperties.setUseTicketCache(useTicketCache);
1496      gssapiProperties.setRequireCachedCredentials(requireCachedCredentials);
1497      gssapiProperties.setRenewTGT(renewTGT);
1498      gssapiProperties.setRefreshKrb5Config(refreshKrb5Config);
1499      gssapiProperties.setUseKeyTab(useKeyTab);
1500      gssapiProperties.setKeyTabPath(keyTabPath);
1501      gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly);
1502      gssapiProperties.setTicketCachePath(ticketCachePath);
1503      gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging);
1504      gssapiProperties.setJAASClientName(jaasClientName);
1505      gssapiProperties.setSASLClientServerName(saslClientServerName);
1506      gssapiProperties.setIsInitiator(isInitiator);
1507      gssapiProperties.setSuppressedSystemProperties(
1508           suppressedSystemProperties);
1509
1510      final GSSAPIBindRequest bindRequest =
1511           new GSSAPIBindRequest(gssapiProperties, controls);
1512      bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
1513      return bindRequest;
1514    }
1515    catch (final Exception e)
1516    {
1517      // This should never happen.
1518      Debug.debugException(e);
1519      return null;
1520    }
1521  }
1522
1523
1524
1525  /**
1526   * Clears the specified system property, unless it is one that is configured
1527   * to be suppressed.
1528   *
1529   * @param  name  The name of the property to be suppressed.
1530   */
1531  private void clearProperty(final String name)
1532  {
1533    if (! suppressedSystemProperties.contains(name))
1534    {
1535      System.clearProperty(name);
1536    }
1537  }
1538
1539
1540
1541  /**
1542   * Sets the specified system property, unless it is one that is configured to
1543   * be suppressed.
1544   *
1545   * @param  name   The name of the property to be suppressed.
1546   * @param  value  The value of the property to be suppressed.
1547   */
1548  private void setProperty(final String name, final String value)
1549  {
1550    if (! suppressedSystemProperties.contains(name))
1551    {
1552      System.setProperty(name, value);
1553    }
1554  }
1555
1556
1557
1558  /**
1559   * {@inheritDoc}
1560   */
1561  @Override()
1562  public void toString(final StringBuilder buffer)
1563  {
1564    buffer.append("GSSAPIBindRequest(authenticationID='");
1565    buffer.append(authenticationID);
1566    buffer.append('\'');
1567
1568    if (authorizationID != null)
1569    {
1570      buffer.append(", authorizationID='");
1571      buffer.append(authorizationID);
1572      buffer.append('\'');
1573    }
1574
1575    if (realm != null)
1576    {
1577      buffer.append(", realm='");
1578      buffer.append(realm);
1579      buffer.append('\'');
1580    }
1581
1582    buffer.append(", qop='");
1583    buffer.append(SASLQualityOfProtection.toString(allowedQoP));
1584    buffer.append('\'');
1585
1586    if (kdcAddress != null)
1587    {
1588      buffer.append(", kdcAddress='");
1589      buffer.append(kdcAddress);
1590      buffer.append('\'');
1591    }
1592
1593    if (isInitiator != null)
1594    {
1595      buffer.append(", isInitiator=");
1596      buffer.append(isInitiator);
1597    }
1598
1599    buffer.append(", jaasClientName='");
1600    buffer.append(jaasClientName);
1601    buffer.append("', configFilePath='");
1602    buffer.append(configFilePath);
1603    buffer.append("', servicePrincipalProtocol='");
1604    buffer.append(servicePrincipalProtocol);
1605    buffer.append("', enableGSSAPIDebugging=");
1606    buffer.append(enableGSSAPIDebugging);
1607
1608    final Control[] controls = getControls();
1609    if (controls.length > 0)
1610    {
1611      buffer.append(", controls={");
1612      for (int i=0; i < controls.length; i++)
1613      {
1614        if (i > 0)
1615        {
1616          buffer.append(", ");
1617        }
1618
1619        buffer.append(controls[i]);
1620      }
1621      buffer.append('}');
1622    }
1623
1624    buffer.append(')');
1625  }
1626
1627
1628
1629  /**
1630   * {@inheritDoc}
1631   */
1632  @Override()
1633  public void toCode(final List<String> lineList, final String requestID,
1634                     final int indentSpaces, final boolean includeProcessing)
1635  {
1636    // Create and update the bind request properties object.
1637    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
1638         "GSSAPIBindRequestProperties", requestID + "RequestProperties",
1639         "new GSSAPIBindRequestProperties",
1640         ToCodeArgHelper.createString(authenticationID, "Authentication ID"),
1641         ToCodeArgHelper.createString("---redacted-password---", "Password"));
1642
1643    if (authorizationID != null)
1644    {
1645      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1646           requestID + "RequestProperties.setAuthorizationID",
1647           ToCodeArgHelper.createString(authorizationID, null));
1648    }
1649
1650    if (realm != null)
1651    {
1652      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1653           requestID + "RequestProperties.setRealm",
1654           ToCodeArgHelper.createString(realm, null));
1655    }
1656
1657    final ArrayList<String> qopValues = new ArrayList<>(3);
1658    for (final SASLQualityOfProtection qop : allowedQoP)
1659    {
1660      qopValues.add("SASLQualityOfProtection." + qop.name());
1661    }
1662    ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1663         requestID + "RequestProperties.setAllowedQoP",
1664         ToCodeArgHelper.createRaw(qopValues, null));
1665
1666    if (kdcAddress != null)
1667    {
1668      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1669           requestID + "RequestProperties.setKDCAddress",
1670           ToCodeArgHelper.createString(kdcAddress, null));
1671    }
1672
1673    if (jaasClientName != null)
1674    {
1675      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1676           requestID + "RequestProperties.setJAASClientName",
1677           ToCodeArgHelper.createString(jaasClientName, null));
1678    }
1679
1680    if (configFilePath != null)
1681    {
1682      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1683           requestID + "RequestProperties.setConfigFilePath",
1684           ToCodeArgHelper.createString(configFilePath, null));
1685    }
1686
1687    if (saslClientServerName != null)
1688    {
1689      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1690           requestID + "RequestProperties.setSASLClientServerName",
1691           ToCodeArgHelper.createString(saslClientServerName, null));
1692    }
1693
1694    if (servicePrincipalProtocol != null)
1695    {
1696      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1697           requestID + "RequestProperties.setServicePrincipalProtocol",
1698           ToCodeArgHelper.createString(servicePrincipalProtocol, null));
1699    }
1700
1701    ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1702         requestID + "RequestProperties.setRefreshKrb5Config",
1703         ToCodeArgHelper.createBoolean(refreshKrb5Config, null));
1704
1705    ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1706         requestID + "RequestProperties.setUseKeyTab",
1707         ToCodeArgHelper.createBoolean(useKeyTab, null));
1708
1709    if (keyTabPath != null)
1710    {
1711      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1712           requestID + "RequestProperties.setKeyTabPath",
1713           ToCodeArgHelper.createString(keyTabPath, null));
1714    }
1715
1716    ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1717         requestID + "RequestProperties.setUseSubjectCredentialsOnly",
1718         ToCodeArgHelper.createBoolean(useSubjectCredentialsOnly, null));
1719
1720    ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1721         requestID + "RequestProperties.setUseTicketCache",
1722         ToCodeArgHelper.createBoolean(useTicketCache, null));
1723
1724    ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1725         requestID + "RequestProperties.setRequireCachedCredentials",
1726         ToCodeArgHelper.createBoolean(requireCachedCredentials, null));
1727
1728    if (ticketCachePath != null)
1729    {
1730      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1731           requestID + "RequestProperties.setTicketCachePath",
1732           ToCodeArgHelper.createString(ticketCachePath, null));
1733    }
1734
1735    ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1736         requestID + "RequestProperties.setRenewTGT",
1737         ToCodeArgHelper.createBoolean(renewTGT, null));
1738
1739    if (isInitiator != null)
1740    {
1741      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1742           requestID + "RequestProperties.setIsInitiator",
1743           ToCodeArgHelper.createBoolean(isInitiator, null));
1744    }
1745
1746    if ((suppressedSystemProperties != null) &&
1747        (! suppressedSystemProperties.isEmpty()))
1748    {
1749      final ArrayList<ToCodeArgHelper> suppressedArgs =
1750           new ArrayList<>(suppressedSystemProperties.size());
1751      for (final String s : suppressedSystemProperties)
1752      {
1753        suppressedArgs.add(ToCodeArgHelper.createString(s, null));
1754      }
1755
1756      ToCodeHelper.generateMethodCall(lineList, indentSpaces, "List<String>",
1757           requestID + "SuppressedProperties", "Arrays.asList", suppressedArgs);
1758
1759      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1760           requestID + "RequestProperties.setSuppressedSystemProperties",
1761           ToCodeArgHelper.createRaw(requestID + "SuppressedProperties", null));
1762    }
1763
1764    ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
1765         requestID + "RequestProperties.setEnableGSSAPIDebugging",
1766         ToCodeArgHelper.createBoolean(enableGSSAPIDebugging, null));
1767
1768
1769    // Create the request variable.
1770    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(2);
1771    constructorArgs.add(
1772         ToCodeArgHelper.createRaw(requestID + "RequestProperties", null));
1773
1774    final Control[] controls = getControls();
1775    if (controls.length > 0)
1776    {
1777      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
1778           "Bind Controls"));
1779    }
1780
1781    ToCodeHelper.generateMethodCall(lineList, indentSpaces, "GSSAPIBindRequest",
1782         requestID + "Request", "new GSSAPIBindRequest", constructorArgs);
1783
1784
1785    // Add lines for processing the request and obtaining the result.
1786    if (includeProcessing)
1787    {
1788      // Generate a string with the appropriate indent.
1789      final StringBuilder buffer = new StringBuilder();
1790      for (int i=0; i < indentSpaces; i++)
1791      {
1792        buffer.append(' ');
1793      }
1794      final String indent = buffer.toString();
1795
1796      lineList.add("");
1797      lineList.add(indent + "try");
1798      lineList.add(indent + '{');
1799      lineList.add(indent + "  BindResult " + requestID +
1800           "Result = connection.bind(" + requestID + "Request);");
1801      lineList.add(indent + "  // The bind was processed successfully.");
1802      lineList.add(indent + '}');
1803      lineList.add(indent + "catch (LDAPException e)");
1804      lineList.add(indent + '{');
1805      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
1806           "help explain why.");
1807      lineList.add(indent + "  // Note that the connection is now likely in " +
1808           "an unauthenticated state.");
1809      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
1810      lineList.add(indent + "  String message = e.getMessage();");
1811      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
1812      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
1813      lineList.add(indent + "  Control[] responseControls = " +
1814           "e.getResponseControls();");
1815      lineList.add(indent + '}');
1816    }
1817  }
1818}