001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2024 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) 2007-2024 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.util.ArrayList;
041import java.util.Collections;
042import java.util.HashMap;
043import java.util.List;
044import java.util.logging.Level;
045import javax.security.auth.callback.Callback;
046import javax.security.auth.callback.CallbackHandler;
047import javax.security.auth.callback.NameCallback;
048import javax.security.auth.callback.PasswordCallback;
049import javax.security.sasl.RealmCallback;
050import javax.security.sasl.RealmChoiceCallback;
051import javax.security.sasl.Sasl;
052import javax.security.sasl.SaslClient;
053
054import com.unboundid.asn1.ASN1OctetString;
055import com.unboundid.util.Debug;
056import com.unboundid.util.DebugType;
057import com.unboundid.util.InternalUseOnly;
058import com.unboundid.util.NotMutable;
059import com.unboundid.util.NotNull;
060import com.unboundid.util.Nullable;
061import com.unboundid.util.StaticUtils;
062import com.unboundid.util.ThreadSafety;
063import com.unboundid.util.ThreadSafetyLevel;
064import com.unboundid.util.Validator;
065
066import static com.unboundid.ldap.sdk.LDAPMessages.*;
067
068
069
070/**
071 * This class provides a SASL DIGEST-MD5 bind request implementation as
072 * described in <A HREF="http://www.ietf.org/rfc/rfc2831.txt">RFC 2831</A>.  The
073 * DIGEST-MD5 mechanism can be used to authenticate over an insecure channel
074 * without exposing the credentials (although it requires that the server have
075 * access to the clear-text password).  It is similar to CRAM-MD5, but provides
076 * better security by combining random data from both the client and the server,
077 * and allows for greater security and functionality, including the ability to
078 * specify an alternate authorization identity and the ability to use data
079 * integrity or confidentiality protection.
080 * <BR><BR>
081 * Elements included in a DIGEST-MD5 bind request include:
082 * <UL>
083 *   <LI>Authentication ID -- A string which identifies the user that is
084 *       attempting to authenticate.  It should be an "authzId" value as
085 *       described in section 5.2.1.8 of
086 *       <A HREF="http://www.ietf.org/rfc/rfc4513.txt">RFC 4513</A>.  That is,
087 *       it should be either "dn:" followed by the distinguished name of the
088 *       target user, or "u:" followed by the username.  If the "u:" form is
089 *       used, then the mechanism used to resolve the provided username to an
090 *       entry may vary from server to server.</LI>
091 *   <LI>Authorization ID -- An optional string which specifies an alternate
092 *       authorization identity that should be used for subsequent operations
093 *       requested on the connection.  Like the authentication ID, the
094 *       authorization ID should use the "authzId" syntax.</LI>
095 *   <LI>Realm -- An optional string which specifies the realm into which the
096 *       user should authenticate.</LI>
097 *   <LI>Password -- The clear-text password for the target user.</LI>
098 * </UL>
099 * <H2>Example</H2>
100 * The following example demonstrates the process for performing a DIGEST-MD5
101 * bind against a directory server with a username of "john.doe" and a password
102 * of "password":
103 * <PRE>
104 * DIGESTMD5BindRequest bindRequest =
105 *      new DIGESTMD5BindRequest("u:john.doe", "password");
106 * BindResult bindResult;
107 * try
108 * {
109 *   bindResult = connection.bind(bindRequest);
110 *   // If we get here, then the bind was successful.
111 * }
112 * catch (LDAPException le)
113 * {
114 *   // The bind failed for some reason.
115 *   bindResult = new BindResult(le.toLDAPResult());
116 *   ResultCode resultCode = le.getResultCode();
117 *   String errorMessageFromServer = le.getDiagnosticMessage();
118 * }
119 * </PRE>
120 */
121@NotMutable()
122@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
123public final class DIGESTMD5BindRequest
124       extends SASLBindRequest
125       implements CallbackHandler
126{
127  /**
128   * The name for the DIGEST-MD5 SASL mechanism.
129   */
130  @NotNull public static final String DIGESTMD5_MECHANISM_NAME = "DIGEST-MD5";
131
132
133
134  /**
135   * The serial version UID for this serializable class.
136   */
137  private static final long serialVersionUID = 867592367640540593L;
138
139
140
141  // The password for this bind request.
142  @NotNull private final ASN1OctetString password;
143
144  // The message ID from the last LDAP message sent from this request.
145  private int messageID = -1;
146
147  // The SASL quality of protection value(s) allowed for the DIGEST-MD5 bind
148  // request.
149  @NotNull private final List<SASLQualityOfProtection> allowedQoP;
150
151  // A list that will be updated with messages about any unhandled callbacks
152  // encountered during processing.
153  @NotNull private final List<String> unhandledCallbackMessages;
154
155  // The authentication ID string for this bind request.
156  @NotNull private final String authenticationID;
157
158  // The authorization ID string for this bind request, if available.
159  @Nullable private final String authorizationID;
160
161  // The realm form this bind request, if available.
162  @Nullable private final String realm;
163
164
165
166  /**
167   * Creates a new SASL DIGEST-MD5 bind request with the provided authentication
168   * ID and password.  It will not include an authorization ID, a realm, or any
169   * controls.
170   *
171   * @param  authenticationID  The authentication ID for this bind request.  It
172   *                           must not be {@code null}.
173   * @param  password          The password for this bind request.  It must not
174   *                           be {@code null}.
175   */
176  public DIGESTMD5BindRequest(@NotNull final String authenticationID,
177                              @NotNull final String password)
178  {
179    this(authenticationID, null, new ASN1OctetString(password), null,
180         NO_CONTROLS);
181
182    Validator.ensureNotNull(password);
183  }
184
185
186
187  /**
188   * Creates a new SASL DIGEST-MD5 bind request with the provided authentication
189   * ID and password.  It will not include an authorization ID, a realm, or any
190   * controls.
191   *
192   * @param  authenticationID  The authentication ID for this bind request.  It
193   *                           must not be {@code null}.
194   * @param  password          The password for this bind request.  It must not
195   *                           be {@code null}.
196   */
197  public DIGESTMD5BindRequest(@NotNull final String authenticationID,
198                              @NotNull final byte[] password)
199  {
200    this(authenticationID, null, new ASN1OctetString(password), null,
201         NO_CONTROLS);
202
203    Validator.ensureNotNull(password);
204  }
205
206
207
208  /**
209   * Creates a new SASL DIGEST-MD5 bind request with the provided authentication
210   * ID and password.  It will not include an authorization ID, a realm, or any
211   * controls.
212   *
213   * @param  authenticationID  The authentication ID for this bind request.  It
214   *                           must not be {@code null}.
215   * @param  password          The password for this bind request.  It must not
216   *                           be {@code null}.
217   */
218  public DIGESTMD5BindRequest(@NotNull final String authenticationID,
219                              @NotNull final ASN1OctetString password)
220  {
221    this(authenticationID, null, password, null, NO_CONTROLS);
222  }
223
224
225
226  /**
227   * Creates a new SASL DIGEST-MD5 bind request with the provided information.
228   *
229   * @param  authenticationID  The authentication ID for this bind request.  It
230   *                           must not be {@code null}.
231   * @param  authorizationID   The authorization ID for this bind request.  It
232   *                           may be {@code null} if there will not be an
233   *                           alternate authorization identity.
234   * @param  password          The password for this bind request.  It must not
235   *                           be {@code null}.
236   * @param  realm             The realm to use for the authentication.  It may
237   *                           be {@code null} if the server supports a default
238   *                           realm.
239   * @param  controls          The set of controls to include in the request.
240   */
241  public DIGESTMD5BindRequest(@NotNull final String authenticationID,
242                              @Nullable final String authorizationID,
243                              @NotNull final String password,
244                              @Nullable final String realm,
245                              @Nullable final Control... controls)
246  {
247    this(authenticationID, authorizationID, new ASN1OctetString(password),
248         realm, controls);
249
250    Validator.ensureNotNull(password);
251  }
252
253
254
255  /**
256   * Creates a new SASL DIGEST-MD5 bind request with the provided information.
257   *
258   * @param  authenticationID  The authentication ID for this bind request.  It
259   *                           must not be {@code null}.
260   * @param  authorizationID   The authorization ID for this bind request.  It
261   *                           may be {@code null} if there will not be an
262   *                           alternate authorization identity.
263   * @param  password          The password for this bind request.  It must not
264   *                           be {@code null}.
265   * @param  realm             The realm to use for the authentication.  It may
266   *                           be {@code null} if the server supports a default
267   *                           realm.
268   * @param  controls          The set of controls to include in the request.
269   */
270  public DIGESTMD5BindRequest(@NotNull final String authenticationID,
271                              @Nullable final String authorizationID,
272                              @NotNull final byte[] password,
273                              @Nullable final String realm,
274                              @Nullable final Control... controls)
275  {
276    this(authenticationID, authorizationID, new ASN1OctetString(password),
277         realm, controls);
278
279    Validator.ensureNotNull(password);
280  }
281
282
283
284  /**
285   * Creates a new SASL DIGEST-MD5 bind request with the provided information.
286   *
287   * @param  authenticationID  The authentication ID for this bind request.  It
288   *                           must not be {@code null}.
289   * @param  authorizationID   The authorization ID for this bind request.  It
290   *                           may be {@code null} if there will not be an
291   *                           alternate authorization identity.
292   * @param  password          The password for this bind request.  It must not
293   *                           be {@code null}.
294   * @param  realm             The realm to use for the authentication.  It may
295   *                           be {@code null} if the server supports a default
296   *                           realm.
297   * @param  controls          The set of controls to include in the request.
298   */
299  public DIGESTMD5BindRequest(@NotNull final String authenticationID,
300                              @Nullable final String authorizationID,
301                              @NotNull final ASN1OctetString password,
302                              @Nullable final String realm, final
303                              @Nullable Control... controls)
304  {
305    super(controls);
306
307    Validator.ensureNotNull(authenticationID, password);
308
309    this.authenticationID = authenticationID;
310    this.authorizationID  = authorizationID;
311    this.password         = password;
312    this.realm            = realm;
313
314    allowedQoP = Collections.singletonList(SASLQualityOfProtection.AUTH);
315
316    unhandledCallbackMessages = new ArrayList<>(5);
317  }
318
319
320
321  /**
322   * Creates a new SASL DIGEST-MD5 bind request with the provided set of
323   * properties.
324   *
325   * @param  properties  The properties to use for this
326   * @param  controls    The set of controls to include in the request.
327   */
328  public DIGESTMD5BindRequest(
329              @NotNull final DIGESTMD5BindRequestProperties properties,
330              @Nullable final Control... controls)
331  {
332    super(controls);
333
334    Validator.ensureNotNull(properties);
335
336    authenticationID = properties.getAuthenticationID();
337    authorizationID  = properties.getAuthorizationID();
338    password         = properties.getPassword();
339    realm            = properties.getRealm();
340    allowedQoP       = properties.getAllowedQoP();
341
342    unhandledCallbackMessages = new ArrayList<>(5);
343  }
344
345
346
347  /**
348   * {@inheritDoc}
349   */
350  @Override()
351  @NotNull()
352  public String getSASLMechanismName()
353  {
354    return DIGESTMD5_MECHANISM_NAME;
355  }
356
357
358
359  /**
360   * Retrieves the authentication ID for this bind request.
361   *
362   * @return  The authentication ID for this bind request.
363   */
364  @NotNull()
365  public String getAuthenticationID()
366  {
367    return authenticationID;
368  }
369
370
371
372  /**
373   * Retrieves the authorization ID for this bind request, if any.
374   *
375   * @return  The authorization ID for this bind request, or {@code null} if
376   *          there should not be a separate authorization identity.
377   */
378  @Nullable()
379  public String getAuthorizationID()
380  {
381    return authorizationID;
382  }
383
384
385
386  /**
387   * Retrieves the string representation of the password for this bind request.
388   *
389   * @return  The string representation of the password for this bind request.
390   */
391  @NotNull()
392  public String getPasswordString()
393  {
394    return password.stringValue();
395  }
396
397
398
399  /**
400   * Retrieves the bytes that comprise the the password for this bind request.
401   *
402   * @return  The bytes that comprise the password for this bind request.
403   */
404  @NotNull()
405  public byte[] getPasswordBytes()
406  {
407    return password.getValue();
408  }
409
410
411
412  /**
413   * Retrieves the realm for this bind request, if any.
414   *
415   * @return  The realm for this bind request, or {@code null} if none was
416   *          defined and the server should use the default realm.
417   */
418  @Nullable()
419  public String getRealm()
420  {
421    return realm;
422  }
423
424
425
426  /**
427   * Retrieves the list of allowed qualities of protection that may be used for
428   * communication that occurs on the connection after the authentication has
429   * completed, in order from most preferred to least preferred.
430   *
431   * @return  The list of allowed qualities of protection that may be used for
432   *          communication that occurs on the connection after the
433   *          authentication has completed, in order from most preferred to
434   *          least preferred.
435   */
436  @NotNull()
437  public List<SASLQualityOfProtection> getAllowedQoP()
438  {
439    return allowedQoP;
440  }
441
442
443
444  /**
445   * Sends this bind request to the target server over the provided connection
446   * and returns the corresponding response.
447   *
448   * @param  connection  The connection to use to send this bind request to the
449   *                     server and read the associated response.
450   * @param  depth       The current referral depth for this request.  It should
451   *                     always be one for the initial request, and should only
452   *                     be incremented when following referrals.
453   *
454   * @return  The bind response read from the server.
455   *
456   * @throws  LDAPException  If a problem occurs while sending the request or
457   *                         reading the response.
458   */
459  @Override()
460  @NotNull()
461  protected BindResult process(@NotNull final LDAPConnection connection,
462                               final int depth)
463            throws LDAPException
464  {
465    setReferralDepth(depth);
466    unhandledCallbackMessages.clear();
467
468
469    final HashMap<String,Object> saslProperties =
470         new HashMap<>(StaticUtils.computeMapCapacity(20));
471    saslProperties.put(Sasl.QOP, SASLQualityOfProtection.toString(allowedQoP));
472    saslProperties.put(Sasl.SERVER_AUTH, "false");
473
474    final SaslClient saslClient;
475    try
476    {
477      final String[] mechanisms = { DIGESTMD5_MECHANISM_NAME };
478      saslClient = Sasl.createSaslClient(mechanisms, authorizationID, "ldap",
479                                         connection.getConnectedAddress(),
480                                         saslProperties, this);
481    }
482    catch (final Exception e)
483    {
484      Debug.debugException(e);
485      throw new LDAPException(ResultCode.LOCAL_ERROR,
486           ERR_DIGESTMD5_CANNOT_CREATE_SASL_CLIENT.get(
487                StaticUtils.getExceptionMessage(e)),
488           e);
489    }
490
491    final SASLClientBindHandler bindHandler = new SASLClientBindHandler(this,
492         connection, DIGESTMD5_MECHANISM_NAME, saslClient, getControls(),
493         getResponseTimeoutMillis(connection), unhandledCallbackMessages);
494
495    try
496    {
497      return bindHandler.processSASLBind();
498    }
499    finally
500    {
501      messageID = bindHandler.getMessageID();
502    }
503  }
504
505
506
507  /**
508   * {@inheritDoc}
509   */
510  @Override()
511  @NotNull()
512  public DIGESTMD5BindRequest getRebindRequest(@NotNull final String host,
513                                               final int port)
514  {
515    final DIGESTMD5BindRequestProperties properties =
516         new DIGESTMD5BindRequestProperties(authenticationID, password);
517    properties.setAuthorizationID(authorizationID);
518    properties.setRealm(realm);
519    properties.setAllowedQoP(allowedQoP);
520
521    return new DIGESTMD5BindRequest(properties, getControls());
522  }
523
524
525
526  /**
527   * Handles any necessary callbacks required for SASL authentication.
528   *
529   * @param  callbacks  The set of callbacks to be handled.
530   */
531  @InternalUseOnly()
532  @Override()
533  public void handle(@NotNull final Callback[] callbacks)
534  {
535    for (final Callback callback : callbacks)
536    {
537      if (callback instanceof NameCallback)
538      {
539        ((NameCallback) callback).setName(authenticationID);
540      }
541      else if (callback instanceof PasswordCallback)
542      {
543        ((PasswordCallback) callback).setPassword(
544             password.stringValue().toCharArray());
545      }
546      else if (callback instanceof RealmCallback)
547      {
548        final RealmCallback rc = (RealmCallback) callback;
549        if (realm == null)
550        {
551          final String defaultRealm = rc.getDefaultText();
552          if (defaultRealm == null)
553          {
554            unhandledCallbackMessages.add(
555                 ERR_DIGESTMD5_REALM_REQUIRED_BUT_NONE_PROVIDED.get(
556                      String.valueOf(rc.getPrompt())));
557          }
558          else
559          {
560            rc.setText(defaultRealm);
561          }
562        }
563        else
564        {
565          rc.setText(realm);
566        }
567      }
568      else if (callback instanceof RealmChoiceCallback)
569      {
570        final RealmChoiceCallback rcc = (RealmChoiceCallback) callback;
571        if (realm == null)
572        {
573          final String choices =
574               StaticUtils.concatenateStrings("{", " '", ",", "'", " }",
575                    rcc.getChoices());
576          unhandledCallbackMessages.add(
577               ERR_DIGESTMD5_REALM_REQUIRED_BUT_NONE_PROVIDED.get(
578                    rcc.getPrompt(), choices));
579        }
580        else
581        {
582          final String[] choices = rcc.getChoices();
583          for (int i=0; i < choices.length; i++)
584          {
585            if (choices[i].equals(realm))
586            {
587              rcc.setSelectedIndex(i);
588              break;
589            }
590          }
591        }
592      }
593      else
594      {
595        // This is an unexpected callback.
596        if (Debug.debugEnabled(DebugType.LDAP))
597        {
598          Debug.debug(Level.WARNING, DebugType.LDAP,
599               "Unexpected DIGEST-MD5 SASL callback of type " +
600                    callback.getClass().getName());
601        }
602
603        unhandledCallbackMessages.add(ERR_DIGESTMD5_UNEXPECTED_CALLBACK.get(
604             callback.getClass().getName()));
605      }
606    }
607  }
608
609
610
611  /**
612   * {@inheritDoc}
613   */
614  @Override()
615  public int getLastMessageID()
616  {
617    return messageID;
618  }
619
620
621
622  /**
623   * {@inheritDoc}
624   */
625  @Override()
626  @NotNull()
627  public DIGESTMD5BindRequest duplicate()
628  {
629    return duplicate(getControls());
630  }
631
632
633
634  /**
635   * {@inheritDoc}
636   */
637  @Override()
638  @NotNull()
639  public DIGESTMD5BindRequest duplicate(@Nullable final Control[] controls)
640  {
641    final DIGESTMD5BindRequestProperties properties =
642         new DIGESTMD5BindRequestProperties(authenticationID, password);
643    properties.setAuthorizationID(authorizationID);
644    properties.setRealm(realm);
645    properties.setAllowedQoP(allowedQoP);
646
647    final DIGESTMD5BindRequest bindRequest =
648         new DIGESTMD5BindRequest(properties, controls);
649    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
650    bindRequest.setIntermediateResponseListener(
651         getIntermediateResponseListener());
652    bindRequest.setReferralDepth(getReferralDepth());
653    bindRequest.setReferralConnector(getReferralConnectorInternal());
654    return bindRequest;
655  }
656
657
658
659  /**
660   * {@inheritDoc}
661   */
662  @Override()
663  public void toString(@NotNull final StringBuilder buffer)
664  {
665    buffer.append("DIGESTMD5BindRequest(authenticationID='");
666    buffer.append(authenticationID);
667    buffer.append('\'');
668
669    if (authorizationID != null)
670    {
671      buffer.append(", authorizationID='");
672      buffer.append(authorizationID);
673      buffer.append('\'');
674    }
675
676    if (realm != null)
677    {
678      buffer.append(", realm='");
679      buffer.append(realm);
680      buffer.append('\'');
681    }
682
683    buffer.append(", qop='");
684    buffer.append(SASLQualityOfProtection.toString(allowedQoP));
685    buffer.append('\'');
686
687    final Control[] controls = getControls();
688    if (controls.length > 0)
689    {
690      buffer.append(", controls={");
691      for (int i=0; i < controls.length; i++)
692      {
693        if (i > 0)
694        {
695          buffer.append(", ");
696        }
697
698        buffer.append(controls[i]);
699      }
700      buffer.append('}');
701    }
702
703    buffer.append(')');
704  }
705
706
707
708  /**
709   * {@inheritDoc}
710   */
711  @Override()
712  public void toCode(@NotNull final List<String> lineList,
713                     @NotNull final String requestID,
714                     final int indentSpaces, final boolean includeProcessing)
715  {
716    // Create and update the bind request properties object.
717    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
718         "DIGESTMD5BindRequestProperties",
719         requestID + "RequestProperties",
720         "new DIGESTMD5BindRequestProperties",
721         ToCodeArgHelper.createString(authenticationID, "Authentication ID"),
722         ToCodeArgHelper.createString("---redacted-password---", "Password"));
723
724    if (authorizationID != null)
725    {
726      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
727           requestID + "RequestProperties.setAuthorizationID",
728           ToCodeArgHelper.createString(authorizationID, null));
729    }
730
731    if (realm != null)
732    {
733      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
734           requestID + "RequestProperties.setRealm",
735           ToCodeArgHelper.createString(realm, null));
736    }
737
738    final ArrayList<String> qopValues = new ArrayList<>(3);
739    for (final SASLQualityOfProtection qop : allowedQoP)
740    {
741      qopValues.add("SASLQualityOfProtection." + qop.name());
742    }
743    ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
744         requestID + "RequestProperties.setAllowedQoP",
745         ToCodeArgHelper.createRaw(qopValues, null));
746
747
748    // Create the request variable.
749    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(2);
750    constructorArgs.add(
751         ToCodeArgHelper.createRaw(requestID + "RequestProperties", null));
752
753    final Control[] controls = getControls();
754    if (controls.length > 0)
755    {
756      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
757           "Bind Controls"));
758    }
759
760    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
761         "DIGESTMD5BindRequest", requestID + "Request",
762         "new DIGESTMD5BindRequest", constructorArgs);
763
764
765    // Add lines for processing the request and obtaining the result.
766    if (includeProcessing)
767    {
768      // Generate a string with the appropriate indent.
769      final StringBuilder buffer = new StringBuilder();
770      for (int i=0; i < indentSpaces; i++)
771      {
772        buffer.append(' ');
773      }
774      final String indent = buffer.toString();
775
776      lineList.add("");
777      lineList.add(indent + "try");
778      lineList.add(indent + '{');
779      lineList.add(indent + "  BindResult " + requestID +
780           "Result = connection.bind(" + requestID + "Request);");
781      lineList.add(indent + "  // The bind was processed successfully.");
782      lineList.add(indent + '}');
783      lineList.add(indent + "catch (LDAPException e)");
784      lineList.add(indent + '{');
785      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
786           "help explain why.");
787      lineList.add(indent + "  // Note that the connection is now likely in " +
788           "an unauthenticated state.");
789      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
790      lineList.add(indent + "  String message = e.getMessage();");
791      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
792      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
793      lineList.add(indent + "  Control[] responseControls = " +
794           "e.getResponseControls();");
795      lineList.add(indent + '}');
796    }
797  }
798}