001/*
002 * Copyright 2007-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2022 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-2022 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    unhandledCallbackMessages.clear();
466
467
468    final HashMap<String,Object> saslProperties =
469         new HashMap<>(StaticUtils.computeMapCapacity(20));
470    saslProperties.put(Sasl.QOP, SASLQualityOfProtection.toString(allowedQoP));
471    saslProperties.put(Sasl.SERVER_AUTH, "false");
472
473    final SaslClient saslClient;
474    try
475    {
476      final String[] mechanisms = { DIGESTMD5_MECHANISM_NAME };
477      saslClient = Sasl.createSaslClient(mechanisms, authorizationID, "ldap",
478                                         connection.getConnectedAddress(),
479                                         saslProperties, this);
480    }
481    catch (final Exception e)
482    {
483      Debug.debugException(e);
484      throw new LDAPException(ResultCode.LOCAL_ERROR,
485           ERR_DIGESTMD5_CANNOT_CREATE_SASL_CLIENT.get(
486                StaticUtils.getExceptionMessage(e)),
487           e);
488    }
489
490    final SASLClientBindHandler bindHandler = new SASLClientBindHandler(this,
491         connection, DIGESTMD5_MECHANISM_NAME, saslClient, getControls(),
492         getResponseTimeoutMillis(connection), unhandledCallbackMessages);
493
494    try
495    {
496      return bindHandler.processSASLBind();
497    }
498    finally
499    {
500      messageID = bindHandler.getMessageID();
501    }
502  }
503
504
505
506  /**
507   * {@inheritDoc}
508   */
509  @Override()
510  @NotNull()
511  public DIGESTMD5BindRequest getRebindRequest(@NotNull final String host,
512                                               final int port)
513  {
514    final DIGESTMD5BindRequestProperties properties =
515         new DIGESTMD5BindRequestProperties(authenticationID, password);
516    properties.setAuthorizationID(authorizationID);
517    properties.setRealm(realm);
518    properties.setAllowedQoP(allowedQoP);
519
520    return new DIGESTMD5BindRequest(properties, getControls());
521  }
522
523
524
525  /**
526   * Handles any necessary callbacks required for SASL authentication.
527   *
528   * @param  callbacks  The set of callbacks to be handled.
529   */
530  @InternalUseOnly()
531  @Override()
532  public void handle(@NotNull final Callback[] callbacks)
533  {
534    for (final Callback callback : callbacks)
535    {
536      if (callback instanceof NameCallback)
537      {
538        ((NameCallback) callback).setName(authenticationID);
539      }
540      else if (callback instanceof PasswordCallback)
541      {
542        ((PasswordCallback) callback).setPassword(
543             password.stringValue().toCharArray());
544      }
545      else if (callback instanceof RealmCallback)
546      {
547        final RealmCallback rc = (RealmCallback) callback;
548        if (realm == null)
549        {
550          final String defaultRealm = rc.getDefaultText();
551          if (defaultRealm == null)
552          {
553            unhandledCallbackMessages.add(
554                 ERR_DIGESTMD5_REALM_REQUIRED_BUT_NONE_PROVIDED.get(
555                      String.valueOf(rc.getPrompt())));
556          }
557          else
558          {
559            rc.setText(defaultRealm);
560          }
561        }
562        else
563        {
564          rc.setText(realm);
565        }
566      }
567      else if (callback instanceof RealmChoiceCallback)
568      {
569        final RealmChoiceCallback rcc = (RealmChoiceCallback) callback;
570        if (realm == null)
571        {
572          final String choices =
573               StaticUtils.concatenateStrings("{", " '", ",", "'", " }",
574                    rcc.getChoices());
575          unhandledCallbackMessages.add(
576               ERR_DIGESTMD5_REALM_REQUIRED_BUT_NONE_PROVIDED.get(
577                    rcc.getPrompt(), choices));
578        }
579        else
580        {
581          final String[] choices = rcc.getChoices();
582          for (int i=0; i < choices.length; i++)
583          {
584            if (choices[i].equals(realm))
585            {
586              rcc.setSelectedIndex(i);
587              break;
588            }
589          }
590        }
591      }
592      else
593      {
594        // This is an unexpected callback.
595        if (Debug.debugEnabled(DebugType.LDAP))
596        {
597          Debug.debug(Level.WARNING, DebugType.LDAP,
598               "Unexpected DIGEST-MD5 SASL callback of type " +
599                    callback.getClass().getName());
600        }
601
602        unhandledCallbackMessages.add(ERR_DIGESTMD5_UNEXPECTED_CALLBACK.get(
603             callback.getClass().getName()));
604      }
605    }
606  }
607
608
609
610  /**
611   * {@inheritDoc}
612   */
613  @Override()
614  public int getLastMessageID()
615  {
616    return messageID;
617  }
618
619
620
621  /**
622   * {@inheritDoc}
623   */
624  @Override()
625  @NotNull()
626  public DIGESTMD5BindRequest duplicate()
627  {
628    return duplicate(getControls());
629  }
630
631
632
633  /**
634   * {@inheritDoc}
635   */
636  @Override()
637  @NotNull()
638  public DIGESTMD5BindRequest duplicate(@Nullable final Control[] controls)
639  {
640    final DIGESTMD5BindRequestProperties properties =
641         new DIGESTMD5BindRequestProperties(authenticationID, password);
642    properties.setAuthorizationID(authorizationID);
643    properties.setRealm(realm);
644    properties.setAllowedQoP(allowedQoP);
645
646    final DIGESTMD5BindRequest bindRequest =
647         new DIGESTMD5BindRequest(properties, controls);
648    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
649    return bindRequest;
650  }
651
652
653
654  /**
655   * {@inheritDoc}
656   */
657  @Override()
658  public void toString(@NotNull final StringBuilder buffer)
659  {
660    buffer.append("DIGESTMD5BindRequest(authenticationID='");
661    buffer.append(authenticationID);
662    buffer.append('\'');
663
664    if (authorizationID != null)
665    {
666      buffer.append(", authorizationID='");
667      buffer.append(authorizationID);
668      buffer.append('\'');
669    }
670
671    if (realm != null)
672    {
673      buffer.append(", realm='");
674      buffer.append(realm);
675      buffer.append('\'');
676    }
677
678    buffer.append(", qop='");
679    buffer.append(SASLQualityOfProtection.toString(allowedQoP));
680    buffer.append('\'');
681
682    final Control[] controls = getControls();
683    if (controls.length > 0)
684    {
685      buffer.append(", controls={");
686      for (int i=0; i < controls.length; i++)
687      {
688        if (i > 0)
689        {
690          buffer.append(", ");
691        }
692
693        buffer.append(controls[i]);
694      }
695      buffer.append('}');
696    }
697
698    buffer.append(')');
699  }
700
701
702
703  /**
704   * {@inheritDoc}
705   */
706  @Override()
707  public void toCode(@NotNull final List<String> lineList,
708                     @NotNull final String requestID,
709                     final int indentSpaces, final boolean includeProcessing)
710  {
711    // Create and update the bind request properties object.
712    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
713         "DIGESTMD5BindRequestProperties",
714         requestID + "RequestProperties",
715         "new DIGESTMD5BindRequestProperties",
716         ToCodeArgHelper.createString(authenticationID, "Authentication ID"),
717         ToCodeArgHelper.createString("---redacted-password---", "Password"));
718
719    if (authorizationID != null)
720    {
721      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
722           requestID + "RequestProperties.setAuthorizationID",
723           ToCodeArgHelper.createString(authorizationID, null));
724    }
725
726    if (realm != null)
727    {
728      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
729           requestID + "RequestProperties.setRealm",
730           ToCodeArgHelper.createString(realm, null));
731    }
732
733    final ArrayList<String> qopValues = new ArrayList<>(3);
734    for (final SASLQualityOfProtection qop : allowedQoP)
735    {
736      qopValues.add("SASLQualityOfProtection." + qop.name());
737    }
738    ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
739         requestID + "RequestProperties.setAllowedQoP",
740         ToCodeArgHelper.createRaw(qopValues, null));
741
742
743    // Create the request variable.
744    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(2);
745    constructorArgs.add(
746         ToCodeArgHelper.createRaw(requestID + "RequestProperties", null));
747
748    final Control[] controls = getControls();
749    if (controls.length > 0)
750    {
751      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
752           "Bind Controls"));
753    }
754
755    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
756         "DIGESTMD5BindRequest", requestID + "Request",
757         "new DIGESTMD5BindRequest", constructorArgs);
758
759
760    // Add lines for processing the request and obtaining the result.
761    if (includeProcessing)
762    {
763      // Generate a string with the appropriate indent.
764      final StringBuilder buffer = new StringBuilder();
765      for (int i=0; i < indentSpaces; i++)
766      {
767        buffer.append(' ');
768      }
769      final String indent = buffer.toString();
770
771      lineList.add("");
772      lineList.add(indent + "try");
773      lineList.add(indent + '{');
774      lineList.add(indent + "  BindResult " + requestID +
775           "Result = connection.bind(" + requestID + "Request);");
776      lineList.add(indent + "  // The bind was processed successfully.");
777      lineList.add(indent + '}');
778      lineList.add(indent + "catch (LDAPException e)");
779      lineList.add(indent + '{');
780      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
781           "help explain why.");
782      lineList.add(indent + "  // Note that the connection is now likely in " +
783           "an unauthenticated state.");
784      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
785      lineList.add(indent + "  String message = e.getMessage();");
786      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
787      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
788      lineList.add(indent + "  Control[] responseControls = " +
789           "e.getResponseControls();");
790      lineList.add(indent + '}');
791    }
792  }
793}