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