001/*
002 * Copyright 2016-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2016-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) 2016-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.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    messageID = InternalSDKHelper.nextMessageID(connection);
514    return sendBindRequest(connection, "", encodeCredentials(), getControls(),
515         getResponseTimeoutMillis(connection));
516  }
517
518
519
520  /**
521   * Retrieves an ASN.1 octet string containing the encoded credentials for this
522   * bind request.
523   *
524   * @return  An ASN.1 octet string containing the encoded credentials for this
525   *          bind request.
526   */
527  @NotNull()
528  public ASN1OctetString encodeCredentials()
529  {
530    return encodeCredentials(authenticationID, authorizationID, staticPassword,
531         yubiKeyOTP);
532  }
533
534
535
536  /**
537   * Encodes the provided information into an ASN.1 octet string suitable for
538   * use as the SASL credentials for an UNBOUNDID-YUBIKEY-OTP bind request.
539   *
540   * @param  authenticationID  The authentication ID for the bind request.  It
541   *                           must not be {@code null}, and must have the form
542   *                           "dn:" followed by the DN of the target user or
543   *                           "u:" followed by the the username of the target
544   *                           user.
545   * @param  authorizationID   The authorization ID for the bind request.  It
546   *                           may be {@code null} if the authorization identity
547   *                           should be the same as the authentication
548   *                           identity.
549   * @param  staticPassword    The static password for the user specified as the
550   *                           authentication identity.  It may be {@code null}
551   *                           if authentication should be performed using only
552   *                           the YubiKey OTP.
553   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
554   *                           device.  It must not be {@code null}.
555   *
556   * @return  An ASN.1 octet string suitable for use as the SASL credentials for
557   *          an UNBOUNDID-YUBIKEY-OTP bind request.
558   */
559  @NotNull()
560  public static ASN1OctetString encodeCredentials(
561              @NotNull final String authenticationID,
562              @Nullable final String authorizationID,
563              @Nullable final ASN1OctetString staticPassword,
564              @NotNull final String yubiKeyOTP)
565  {
566    Validator.ensureNotNull(authenticationID);
567    Validator.ensureNotNull(yubiKeyOTP);
568
569    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
570    elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID));
571
572    if (authorizationID != null)
573    {
574      elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID));
575    }
576
577    if (staticPassword != null)
578    {
579      elements.add(new ASN1OctetString(TYPE_STATIC_PASSWORD,
580           staticPassword.getValue()));
581    }
582
583    elements.add(new ASN1OctetString(TYPE_YUBIKEY_OTP, yubiKeyOTP));
584
585    return new ASN1OctetString(new ASN1Sequence(elements).encode());
586  }
587
588
589
590  /**
591   * {@inheritDoc}
592   */
593  @Override()
594  @NotNull()
595  public UnboundIDYubiKeyOTPBindRequest duplicate()
596  {
597    return duplicate(getControls());
598  }
599
600
601
602  /**
603   * {@inheritDoc}
604   */
605  @Override()
606  @NotNull()
607  public UnboundIDYubiKeyOTPBindRequest duplicate(
608              @Nullable final Control[] controls)
609  {
610    final UnboundIDYubiKeyOTPBindRequest bindRequest =
611         new UnboundIDYubiKeyOTPBindRequest(authenticationID, authorizationID,
612              staticPassword, yubiKeyOTP, controls);
613    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
614    return bindRequest;
615  }
616
617
618
619  /**
620   * {@inheritDoc}
621   */
622  @Override()
623  @NotNull()
624  public String getSASLMechanismName()
625  {
626    return UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME;
627  }
628
629
630
631  /**
632   * {@inheritDoc}
633   */
634  @Override()
635  public int getLastMessageID()
636  {
637    return messageID;
638  }
639
640
641
642  /**
643   * {@inheritDoc}
644   */
645  @Override()
646  public void toString(@NotNull final StringBuilder buffer)
647  {
648    buffer.append("UnboundYubiKeyOTPBindRequest(authenticationID='");
649    buffer.append(authenticationID);
650
651    if (authorizationID != null)
652    {
653      buffer.append("', authorizationID='");
654      buffer.append(authorizationID);
655    }
656
657    buffer.append("', staticPasswordProvided=");
658    buffer.append(staticPassword != null);
659
660    final Control[] controls = getControls();
661    if (controls.length > 0)
662    {
663      buffer.append(", controls={");
664      for (int i=0; i < controls.length; i++)
665      {
666        if (i > 0)
667        {
668          buffer.append(", ");
669        }
670
671        buffer.append(controls[i]);
672      }
673      buffer.append('}');
674    }
675
676    buffer.append(')');
677  }
678
679
680
681  /**
682   * {@inheritDoc}
683   */
684  @Override()
685  public void toCode(@NotNull final List<String> lineList,
686                     @NotNull final String requestID,
687                     final int indentSpaces, final boolean includeProcessing)
688  {
689    // Create the request variable.
690    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(5);
691    constructorArgs.add(ToCodeArgHelper.createString(getAuthenticationID(),
692         "Authentication ID"));
693    constructorArgs.add(ToCodeArgHelper.createString(getAuthorizationID(),
694         "Authorization ID"));
695    constructorArgs.add(ToCodeArgHelper.createString(
696         "---redacted-static-password---", "Static Password"));
697    constructorArgs.add(ToCodeArgHelper.createString(
698         ((getStaticPasswordString() == null)
699              ? "null"
700              : "---redacted-static-password---"),
701         "Static Password"));
702    constructorArgs.add(ToCodeArgHelper.createString(
703         "---redacted-yubikey-otp---", "YubiKey OTP"));
704
705    final Control[] controls = getControls();
706    if (controls.length > 0)
707    {
708      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
709           "Bind Controls"));
710    }
711
712    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
713         "UnboundIDYubiKeyOTPBindRequest", requestID + "Request",
714         "new UnboundIDYubiKeyOTPBindRequest", constructorArgs);
715
716
717    // Add lines for processing the request and obtaining the result.
718    if (includeProcessing)
719    {
720      // Generate a string with the appropriate indent.
721      final StringBuilder buffer = new StringBuilder();
722      for (int i=0; i < indentSpaces; i++)
723      {
724        buffer.append(' ');
725      }
726      final String indent = buffer.toString();
727
728      lineList.add("");
729      lineList.add(indent + "try");
730      lineList.add(indent + '{');
731      lineList.add(indent + "  BindResult " + requestID +
732           "Result = connection.bind(" + requestID + "Request);");
733      lineList.add(indent + "  // The bind was processed successfully.");
734      lineList.add(indent + '}');
735      lineList.add(indent + "catch (LDAPException e)");
736      lineList.add(indent + '{');
737      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
738           "help explain why.");
739      lineList.add(indent + "  // Note that the connection is now likely in " +
740           "an unauthenticated state.");
741      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
742      lineList.add(indent + "  String message = e.getMessage();");
743      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
744      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
745      lineList.add(indent + "  Control[] responseControls = " +
746           "e.getResponseControls();");
747      lineList.add(indent + '}');
748    }
749  }
750}