001/*
002 * Copyright 2016-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2016-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) 2016-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.unboundidds;
037
038
039
040import java.util.ArrayList;
041import java.util.List;
042
043import com.unboundid.asn1.ASN1Element;
044import com.unboundid.asn1.ASN1OctetString;
045import com.unboundid.asn1.ASN1Sequence;
046import com.unboundid.ldap.sdk.BindResult;
047import com.unboundid.ldap.sdk.Control;
048import com.unboundid.ldap.sdk.InternalSDKHelper;
049import com.unboundid.ldap.sdk.LDAPConnection;
050import com.unboundid.ldap.sdk.LDAPException;
051import com.unboundid.ldap.sdk.ResultCode;
052import com.unboundid.ldap.sdk.SASLBindRequest;
053import com.unboundid.ldap.sdk.ToCodeArgHelper;
054import com.unboundid.ldap.sdk.ToCodeHelper;
055import com.unboundid.ldap.sdk.unboundidds.extensions.
056            DeregisterYubiKeyOTPDeviceExtendedRequest;
057import com.unboundid.ldap.sdk.unboundidds.extensions.
058            RegisterYubiKeyOTPDeviceExtendedRequest;
059import com.unboundid.util.Debug;
060import com.unboundid.util.NotMutable;
061import com.unboundid.util.NotNull;
062import com.unboundid.util.Nullable;
063import com.unboundid.util.StaticUtils;
064import com.unboundid.util.ThreadSafety;
065import com.unboundid.util.ThreadSafetyLevel;
066import com.unboundid.util.Validator;
067
068import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
069
070
071
072/**
073 * This class provides an implementation of a SASL bind request that may be used
074 * to authenticate to a Directory Server using the UNBOUNDID-YUBIKEY-OTP
075 * mechanism.  The credentials include at least an authentication ID and a
076 * one-time password generated by a YubiKey device.  The request may also
077 * include a static password (which may or may not be required by the server)
078 * and an optional authorization ID.
079 * <BR>
080 * <BLOCKQUOTE>
081 *   <B>NOTE:</B>  This class, and other classes within the
082 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
083 *   supported for use against Ping Identity, UnboundID, and
084 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
085 *   for proprietary functionality or for external specifications that are not
086 *   considered stable or mature enough to be guaranteed to work in an
087 *   interoperable way with other types of LDAP servers.
088 * </BLOCKQUOTE>
089 * <BR>
090 * The UNBOUNDID-YUBIKEY-OTP bind request MUST include SASL credentials with the
091 * following ASN.1 encoding:
092 * <BR><BR>
093 * <PRE>
094 *   UnboundIDYubiKeyCredentials ::= SEQUENCE {
095 *        authenticationID     [0] OCTET STRING,
096 *        authorizationID      [1] OCTET STRING OPTIONAL,
097 *        staticPassword       [2] OCTET STRING OPTIONAL,
098 *        yubiKeyOTP           [3] OCTET STRING,
099 *        ... }
100 * </PRE>
101 *
102 *
103 * @see  RegisterYubiKeyOTPDeviceExtendedRequest
104 * @see  DeregisterYubiKeyOTPDeviceExtendedRequest
105 */
106@NotMutable()
107@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
108public final class UnboundIDYubiKeyOTPBindRequest
109       extends SASLBindRequest
110{
111  /**
112   * The name for the UnboundID YubiKey SASL mechanism.
113   */
114  @NotNull public static final String UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME =
115       "UNBOUNDID-YUBIKEY-OTP";
116
117
118
119  /**
120   * The BER type for the authentication ID element of the credentials sequence.
121   */
122  private static final byte TYPE_AUTHENTICATION_ID = (byte) 0x80;
123
124
125
126  /**
127   * The BER type for the authorization ID element of the credentials sequence.
128   */
129  private static final byte TYPE_AUTHORIZATION_ID = (byte) 0x81;
130
131
132
133  /**
134   * The BER type for the static password element of the credentials sequence.
135   */
136  private static final byte TYPE_STATIC_PASSWORD = (byte) 0x82;
137
138
139
140  /**
141   * The BER type for the YubiKey OTP element of the credentials sequence.
142   */
143  private static final byte TYPE_YUBIKEY_OTP = (byte) 0x83;
144
145
146
147  /**
148   * The serial version UID for this serializable class.
149   */
150  private static final long serialVersionUID = -6124016046606933247L;
151
152
153
154  // The static password for the user, if provided.
155  @Nullable private final ASN1OctetString staticPassword;
156
157  // The message ID from the last LDAP message sent from this request.
158  private volatile int messageID = -1;
159
160  // The authentication ID for the user.
161  @NotNull private final String authenticationID;
162
163  // The authorization ID for the bind request, if provided.
164  @Nullable private final String authorizationID;
165
166  // The one-time password generated by a YubiKey device.
167  @NotNull private final String yubiKeyOTP;
168
169
170
171  /**
172   * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided
173   * information.
174   *
175   * @param  authenticationID  The authentication ID for the bind request.  It
176   *                           must not be {@code null}, and must have the form
177   *                           "dn:" followed by the DN of the target user or
178   *                           "u:" followed by the the username of the target
179   *                           user.
180   * @param  authorizationID   The authorization ID for the bind request.  It
181   *                           may be {@code null} if the authorization identity
182   *                           should be the same as the authentication
183   *                           identity.
184   * @param  staticPassword    The static password for the user specified as the
185   *                           authentication identity.  It may be {@code null}
186   *                           if authentication should be performed using only
187   *                           the YubiKey OTP.
188   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
189   *                           device.  It must not be {@code null}.
190   * @param  controls          The set of controls to include in the bind
191   *                           request.  It may be {@code null} or empty if
192   *                           there should not be any request controls.
193   */
194  public UnboundIDYubiKeyOTPBindRequest(@NotNull final String authenticationID,
195                                        @Nullable final String authorizationID,
196                                        @Nullable final String staticPassword,
197                                        @NotNull final String yubiKeyOTP,
198                                        @Nullable final Control... controls)
199  {
200    this(authenticationID, authorizationID, toASN1OctetString(staticPassword),
201         yubiKeyOTP, controls);
202  }
203
204
205
206  /**
207   * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided
208   * information.
209   *
210   * @param  authenticationID  The authentication ID for the bind request.  It
211   *                           must not be {@code null}, and must have the form
212   *                           "dn:" followed by the DN of the target user or
213   *                           "u:" followed by the the username of the target
214   *                           user.
215   * @param  authorizationID   The authorization ID for the bind request.  It
216   *                           may be {@code null} if the authorization identity
217   *                           should be the same as the authentication
218   *                           identity.
219   * @param  staticPassword    The static password for the user specified as the
220   *                           authentication identity.  It may be {@code null}
221   *                           if authentication should be performed using only
222   *                           the YubiKey OTP.
223   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
224   *                           device.  It must not be {@code null}.
225   * @param  controls          The set of controls to include in the bind
226   *                           request.  It may be {@code null} or empty if
227   *                           there should not be any request controls.
228   */
229  public UnboundIDYubiKeyOTPBindRequest(@NotNull final String authenticationID,
230                                        @Nullable final String authorizationID,
231                                        @Nullable final byte[] staticPassword,
232                                        @NotNull final String yubiKeyOTP,
233                                        @Nullable final Control... controls)
234  {
235    this(authenticationID, authorizationID, toASN1OctetString(staticPassword),
236         yubiKeyOTP, controls);
237  }
238
239
240
241  /**
242   * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided
243   * information.
244   *
245   * @param  authenticationID  The authentication ID for the bind request.  It
246   *                           must not be {@code null}, and must have the form
247   *                           "dn:" followed by the DN of the target user or
248   *                           "u:" followed by the the username of the target
249   *                           user.
250   * @param  authorizationID   The authorization ID for the bind request.  It
251   *                           may be {@code null} if the authorization identity
252   *                           should be the same as the authentication
253   *                           identity.
254   * @param  staticPassword    The static password for the user specified as the
255   *                           authentication identity.  It may be {@code null}
256   *                           if authentication should be performed using only
257   *                           the YubiKey OTP.
258   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
259   *                           device.  It must not be {@code null}.
260   * @param  controls          The set of controls to include in the bind
261   *                           request.  It may be {@code null} or empty if
262   *                           there should not be any request controls.
263   */
264  private UnboundIDYubiKeyOTPBindRequest(@NotNull final String authenticationID,
265               @Nullable final String authorizationID,
266               @Nullable final ASN1OctetString staticPassword,
267               @NotNull final String yubiKeyOTP,
268               @Nullable final Control... controls)
269  {
270    super(controls);
271
272    Validator.ensureNotNull(authenticationID);
273    Validator.ensureNotNull(yubiKeyOTP);
274
275    this.authenticationID = authenticationID;
276    this.authorizationID  = authorizationID;
277    this.staticPassword   = staticPassword;
278    this.yubiKeyOTP       = yubiKeyOTP;
279  }
280
281
282
283  /**
284   * Retrieves an ASN.1 octet string that represents the appropriate encoding
285   * for the provided password.
286   *
287   * @param  password  The password object to convert to an ASN.1 octet string.
288   *                   It may be {@code null} if no static password is required.
289   *                   Otherwise, it must either be a string or a byte array.
290   *
291   * @return  The ASN.1 octet string created from the provided password object,
292   *          or {@code null} if the provided password object was null.
293   */
294  @Nullable()
295  private static ASN1OctetString toASN1OctetString(
296                                      @Nullable final Object password)
297  {
298    if (password == null)
299    {
300      return null;
301    }
302    else if (password instanceof byte[])
303    {
304      return new ASN1OctetString(TYPE_STATIC_PASSWORD, (byte[]) password);
305    }
306    else
307    {
308      return new ASN1OctetString(TYPE_STATIC_PASSWORD,
309           String.valueOf(password));
310    }
311  }
312
313
314
315  /**
316   * Creates a new UNBOUNDID-YUBIKEY-OTP SASL bind request decoded from the
317   * provided SASL credentials.
318   *
319   * @param  saslCredentials  The SASL credentials to decode in order to create
320   *                          the UNBOUNDID-YUBIKEY-OTP SASL bind request.  It
321   *                          must not be {@code null}.
322   * @param  controls         The set of controls to include in the bind
323   *                          request.  This may be {@code null} or empty if no
324   *                          controls should be included in the request.
325   *
326   * @return  The UNBOUNDID-YUBIKEY-OTP SASL bind request decoded from the
327   *          provided credentials.
328   *
329   * @throws  LDAPException  If the provided credentials cannot be decoded to a
330   *                         valid UNBOUNDID-YUBIKEY-OTP bind request.
331   */
332  @NotNull()
333  public static UnboundIDYubiKeyOTPBindRequest decodeCredentials(
334                     @NotNull final ASN1OctetString saslCredentials,
335                     @Nullable final Control... controls)
336         throws LDAPException
337  {
338    try
339    {
340      ASN1OctetString staticPassword = null;
341      String authenticationID = null;
342      String authorizationID  = null;
343      String yubiKeyOTP = null;
344
345      for (final ASN1Element e :
346           ASN1Sequence.decodeAsSequence(saslCredentials.getValue()).elements())
347      {
348        switch (e.getType())
349        {
350          case TYPE_AUTHENTICATION_ID:
351            authenticationID =
352                 ASN1OctetString.decodeAsOctetString(e).stringValue();
353            break;
354          case TYPE_AUTHORIZATION_ID:
355            authorizationID =
356                 ASN1OctetString.decodeAsOctetString(e).stringValue();
357            break;
358          case TYPE_STATIC_PASSWORD:
359            staticPassword = ASN1OctetString.decodeAsOctetString(e);
360            break;
361          case TYPE_YUBIKEY_OTP:
362            yubiKeyOTP = ASN1OctetString.decodeAsOctetString(e).stringValue();
363            break;
364          default:
365            throw new LDAPException(ResultCode.DECODING_ERROR,
366                 ERR_YUBIKEY_OTP_DECODE_UNRECOGNIZED_CRED_ELEMENT.get(
367                      UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME,
368                      StaticUtils.toHex(e.getType())));
369        }
370      }
371
372      if (authenticationID == null)
373      {
374        throw new LDAPException(ResultCode.DECODING_ERROR,
375             ERR_YUBIKEY_OTP_DECODE_NO_AUTH_ID.get(
376                  UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
377      }
378
379      if (yubiKeyOTP == null)
380      {
381        throw new LDAPException(ResultCode.DECODING_ERROR,
382             ERR_YUBIKEY_OTP_NO_OTP.get(UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
383      }
384
385      return new UnboundIDYubiKeyOTPBindRequest(authenticationID,
386           authorizationID, staticPassword, yubiKeyOTP, controls);
387    }
388    catch (final LDAPException le)
389    {
390      Debug.debugException(le);
391      throw le;
392    }
393    catch (final Exception e)
394    {
395      Debug.debugException(e);
396      throw new LDAPException(ResultCode.DECODING_ERROR,
397           ERR_YUBIKEY_OTP_DECODE_ERROR.get(
398                UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME,
399                StaticUtils.getExceptionMessage(e)),
400           e);
401    }
402  }
403
404
405
406  /**
407   * Retrieves the authentication ID for the bind request.
408   *
409   * @return  The authentication ID for the bind request.
410   */
411  @NotNull()
412  public String getAuthenticationID()
413  {
414    return authenticationID;
415  }
416
417
418
419  /**
420   * Retrieves the authorization ID for the bind request, if any.
421   *
422   * @return  The authorization ID for the bind request, or {@code null} if the
423   *          authorization identity should match the authentication identity.
424   */
425  @Nullable()
426  public String getAuthorizationID()
427  {
428    return authorizationID;
429  }
430
431
432
433  /**
434   * Retrieves the string representation of the static password for the bind
435   * request, if any.
436   *
437   * @return  The string representation of the static password for the bind
438   *          request, or {@code null} if there is no static password.
439   */
440  @Nullable()
441  public String getStaticPasswordString()
442  {
443    if (staticPassword == null)
444    {
445      return null;
446    }
447    else
448    {
449      return staticPassword.stringValue();
450    }
451  }
452
453
454
455  /**
456   * Retrieves the bytes that comprise the static password for the bind request,
457   * if any.
458   *
459   * @return  The bytes that comprise the static password for the bind request,
460   *          or {@code null} if there is no static password.
461   */
462  @Nullable()
463  public byte[] getStaticPasswordBytes()
464  {
465    if (staticPassword == null)
466    {
467      return null;
468    }
469    else
470    {
471      return staticPassword.getValue();
472    }
473  }
474
475
476
477  /**
478   * Retrieves the YubiKey-generated one-time password to include in the bind
479   * request.
480   *
481   * @return  The YubiKey-generated one-time password to include in the bind
482   *          request.
483   */
484  @NotNull()
485  public String getYubiKeyOTP()
486  {
487    return yubiKeyOTP;
488  }
489
490
491
492  /**
493   * Sends this bind request to the target server over the provided connection
494   * and returns the corresponding response.
495   *
496   * @param  connection  The connection to use to send this bind request to the
497   *                     server and read the associated response.
498   * @param  depth       The current referral depth for this request.  It should
499   *                     always be one for the initial request, and should only
500   *                     be incremented when following referrals.
501   *
502   * @return  The bind response read from the server.
503   *
504   * @throws  LDAPException  If a problem occurs while sending the request or
505   *                         reading the response.
506   */
507  @Override()
508  @NotNull()
509  protected BindResult process(@NotNull final LDAPConnection connection,
510                               final int depth)
511            throws LDAPException
512  {
513    setReferralDepth(depth);
514
515    messageID = InternalSDKHelper.nextMessageID(connection);
516    return sendBindRequest(connection, "", encodeCredentials(), getControls(),
517         getResponseTimeoutMillis(connection));
518  }
519
520
521
522  /**
523   * Retrieves an ASN.1 octet string containing the encoded credentials for this
524   * bind request.
525   *
526   * @return  An ASN.1 octet string containing the encoded credentials for this
527   *          bind request.
528   */
529  @NotNull()
530  public ASN1OctetString encodeCredentials()
531  {
532    return encodeCredentials(authenticationID, authorizationID, staticPassword,
533         yubiKeyOTP);
534  }
535
536
537
538  /**
539   * Encodes the provided information into an ASN.1 octet string suitable for
540   * use as the SASL credentials for an UNBOUNDID-YUBIKEY-OTP bind request.
541   *
542   * @param  authenticationID  The authentication ID for the bind request.  It
543   *                           must not be {@code null}, and must have the form
544   *                           "dn:" followed by the DN of the target user or
545   *                           "u:" followed by the the username of the target
546   *                           user.
547   * @param  authorizationID   The authorization ID for the bind request.  It
548   *                           may be {@code null} if the authorization identity
549   *                           should be the same as the authentication
550   *                           identity.
551   * @param  staticPassword    The static password for the user specified as the
552   *                           authentication identity.  It may be {@code null}
553   *                           if authentication should be performed using only
554   *                           the YubiKey OTP.
555   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
556   *                           device.  It must not be {@code null}.
557   *
558   * @return  An ASN.1 octet string suitable for use as the SASL credentials for
559   *          an UNBOUNDID-YUBIKEY-OTP bind request.
560   */
561  @NotNull()
562  public static ASN1OctetString encodeCredentials(
563              @NotNull final String authenticationID,
564              @Nullable final String authorizationID,
565              @Nullable final ASN1OctetString staticPassword,
566              @NotNull final String yubiKeyOTP)
567  {
568    Validator.ensureNotNull(authenticationID);
569    Validator.ensureNotNull(yubiKeyOTP);
570
571    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
572    elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID));
573
574    if (authorizationID != null)
575    {
576      elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID));
577    }
578
579    if (staticPassword != null)
580    {
581      elements.add(new ASN1OctetString(TYPE_STATIC_PASSWORD,
582           staticPassword.getValue()));
583    }
584
585    elements.add(new ASN1OctetString(TYPE_YUBIKEY_OTP, yubiKeyOTP));
586
587    return new ASN1OctetString(new ASN1Sequence(elements).encode());
588  }
589
590
591
592  /**
593   * {@inheritDoc}
594   */
595  @Override()
596  @NotNull()
597  public UnboundIDYubiKeyOTPBindRequest duplicate()
598  {
599    return duplicate(getControls());
600  }
601
602
603
604  /**
605   * {@inheritDoc}
606   */
607  @Override()
608  @NotNull()
609  public UnboundIDYubiKeyOTPBindRequest duplicate(
610              @Nullable final Control[] controls)
611  {
612    final UnboundIDYubiKeyOTPBindRequest bindRequest =
613         new UnboundIDYubiKeyOTPBindRequest(authenticationID, authorizationID,
614              staticPassword, yubiKeyOTP, controls);
615    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
616    bindRequest.setIntermediateResponseListener(
617         getIntermediateResponseListener());
618    bindRequest.setReferralDepth(getReferralDepth());
619    bindRequest.setReferralConnector(getReferralConnectorInternal());
620    return bindRequest;
621  }
622
623
624
625  /**
626   * {@inheritDoc}
627   */
628  @Override()
629  @NotNull()
630  public String getSASLMechanismName()
631  {
632    return UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME;
633  }
634
635
636
637  /**
638   * {@inheritDoc}
639   */
640  @Override()
641  public int getLastMessageID()
642  {
643    return messageID;
644  }
645
646
647
648  /**
649   * {@inheritDoc}
650   */
651  @Override()
652  public void toString(@NotNull final StringBuilder buffer)
653  {
654    buffer.append("UnboundYubiKeyOTPBindRequest(authenticationID='");
655    buffer.append(authenticationID);
656
657    if (authorizationID != null)
658    {
659      buffer.append("', authorizationID='");
660      buffer.append(authorizationID);
661    }
662
663    buffer.append("', staticPasswordProvided=");
664    buffer.append(staticPassword != null);
665
666    final Control[] controls = getControls();
667    if (controls.length > 0)
668    {
669      buffer.append(", controls={");
670      for (int i=0; i < controls.length; i++)
671      {
672        if (i > 0)
673        {
674          buffer.append(", ");
675        }
676
677        buffer.append(controls[i]);
678      }
679      buffer.append('}');
680    }
681
682    buffer.append(')');
683  }
684
685
686
687  /**
688   * {@inheritDoc}
689   */
690  @Override()
691  public void toCode(@NotNull final List<String> lineList,
692                     @NotNull final String requestID,
693                     final int indentSpaces, final boolean includeProcessing)
694  {
695    // Create the request variable.
696    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(5);
697    constructorArgs.add(ToCodeArgHelper.createString(getAuthenticationID(),
698         "Authentication ID"));
699    constructorArgs.add(ToCodeArgHelper.createString(getAuthorizationID(),
700         "Authorization ID"));
701    constructorArgs.add(ToCodeArgHelper.createString(
702         "---redacted-static-password---", "Static Password"));
703    constructorArgs.add(ToCodeArgHelper.createString(
704         ((getStaticPasswordString() == null)
705              ? "null"
706              : "---redacted-static-password---"),
707         "Static Password"));
708    constructorArgs.add(ToCodeArgHelper.createString(
709         "---redacted-yubikey-otp---", "YubiKey OTP"));
710
711    final Control[] controls = getControls();
712    if (controls.length > 0)
713    {
714      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
715           "Bind Controls"));
716    }
717
718    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
719         "UnboundIDYubiKeyOTPBindRequest", requestID + "Request",
720         "new UnboundIDYubiKeyOTPBindRequest", constructorArgs);
721
722
723    // Add lines for processing the request and obtaining the result.
724    if (includeProcessing)
725    {
726      // Generate a string with the appropriate indent.
727      final StringBuilder buffer = new StringBuilder();
728      for (int i=0; i < indentSpaces; i++)
729      {
730        buffer.append(' ');
731      }
732      final String indent = buffer.toString();
733
734      lineList.add("");
735      lineList.add(indent + "try");
736      lineList.add(indent + '{');
737      lineList.add(indent + "  BindResult " + requestID +
738           "Result = connection.bind(" + requestID + "Request);");
739      lineList.add(indent + "  // The bind was processed successfully.");
740      lineList.add(indent + '}');
741      lineList.add(indent + "catch (LDAPException e)");
742      lineList.add(indent + '{');
743      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
744           "help explain why.");
745      lineList.add(indent + "  // Note that the connection is now likely in " +
746           "an unauthenticated state.");
747      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
748      lineList.add(indent + "  String message = e.getMessage();");
749      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
750      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
751      lineList.add(indent + "  Control[] responseControls = " +
752           "e.getResponseControls();");
753      lineList.add(indent + '}');
754    }
755  }
756}