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