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}