001 /* 002 * Copyright 2007-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005 /* 006 * Copyright (C) 2008-2014 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021 package com.unboundid.ldap.sdk; 022 023 024 025 import java.util.Arrays; 026 import java.util.concurrent.LinkedBlockingQueue; 027 import java.util.concurrent.TimeUnit; 028 029 import com.unboundid.asn1.ASN1Buffer; 030 import com.unboundid.asn1.ASN1BufferSequence; 031 import com.unboundid.asn1.ASN1Element; 032 import com.unboundid.asn1.ASN1Integer; 033 import com.unboundid.asn1.ASN1OctetString; 034 import com.unboundid.asn1.ASN1Sequence; 035 import com.unboundid.ldap.protocol.LDAPMessage; 036 import com.unboundid.ldap.protocol.LDAPResponse; 037 import com.unboundid.ldap.protocol.ProtocolOp; 038 import com.unboundid.util.InternalUseOnly; 039 import com.unboundid.util.LDAPSDKUsageException; 040 041 import static com.unboundid.ldap.sdk.LDAPMessages.*; 042 import static com.unboundid.util.Debug.*; 043 import static com.unboundid.util.StaticUtils.*; 044 045 046 047 /** 048 * This class implements the processing necessary to perform an LDAPv3 simple 049 * bind operation, which authenticates using a bind DN and password. 050 */ 051 public final class SimpleBindRequest 052 extends BindRequest 053 implements ResponseAcceptor, ProtocolOp 054 { 055 /** 056 * The BER type to use for the credentials element in a simple bind request 057 * protocol op. 058 */ 059 private static final byte CRED_TYPE_SIMPLE = (byte) 0x80; 060 061 062 063 /** 064 * The ASN.1 octet string that will be used for the bind DN if none was 065 * provided. 066 */ 067 private static final ASN1OctetString NO_BIND_DN = new ASN1OctetString(); 068 069 070 071 /** 072 * The ASN.1 octet string that will be used for the bind password if none was 073 * provided. 074 */ 075 private static final ASN1OctetString NO_PASSWORD = 076 new ASN1OctetString(CRED_TYPE_SIMPLE); 077 078 079 080 /** 081 * The serial version UID for this serializable class. 082 */ 083 private static final long serialVersionUID = 4725871243149974407L; 084 085 086 087 // The message ID from the last LDAP message sent from this request. 088 private int messageID = -1; 089 090 // The bind DN for this simple bind request. 091 private final ASN1OctetString bindDN; 092 093 // The password for this simple bind request. 094 private final ASN1OctetString password; 095 096 // The queue that will be used to receive response messages from the server. 097 private final LinkedBlockingQueue<LDAPResponse> responseQueue = 098 new LinkedBlockingQueue<LDAPResponse>(); 099 100 // The password provider that should be used to obtain the password for this 101 // simple bind request. 102 private final PasswordProvider passwordProvider; 103 104 105 106 /** 107 * Creates a new simple bind request that may be used to perform an anonymous 108 * bind to the directory server (i.e., with a zero-length bind DN and a 109 * zero-length password). 110 */ 111 public SimpleBindRequest() 112 { 113 this(NO_BIND_DN, NO_PASSWORD, null, NO_CONTROLS); 114 } 115 116 117 118 /** 119 * Creates a new simple bind request with the provided bind DN and password. 120 * 121 * @param bindDN The bind DN for this simple bind request. 122 * @param password The password for this simple bind request. 123 */ 124 public SimpleBindRequest(final String bindDN, final String password) 125 { 126 this(bindDN, password, NO_CONTROLS); 127 } 128 129 130 131 /** 132 * Creates a new simple bind request with the provided bind DN and password. 133 * 134 * @param bindDN The bind DN for this simple bind request. 135 * @param password The password for this simple bind request. 136 */ 137 public SimpleBindRequest(final String bindDN, final byte[] password) 138 { 139 this(bindDN, password, NO_CONTROLS); 140 } 141 142 143 144 /** 145 * Creates a new simple bind request with the provided bind DN and password. 146 * 147 * @param bindDN The bind DN for this simple bind request. 148 * @param password The password for this simple bind request. 149 */ 150 public SimpleBindRequest(final DN bindDN, final String password) 151 { 152 this(bindDN, password, NO_CONTROLS); 153 } 154 155 156 157 /** 158 * Creates a new simple bind request with the provided bind DN and password. 159 * 160 * @param bindDN The bind DN for this simple bind request. 161 * @param password The password for this simple bind request. 162 */ 163 public SimpleBindRequest(final DN bindDN, final byte[] password) 164 { 165 this(bindDN, password, NO_CONTROLS); 166 } 167 168 169 170 /** 171 * Creates a new simple bind request with the provided bind DN and password. 172 * 173 * @param bindDN The bind DN for this simple bind request. 174 * @param password The password for this simple bind request. 175 * @param controls The set of controls for this simple bind request. 176 */ 177 public SimpleBindRequest(final String bindDN, final String password, 178 final Control... controls) 179 { 180 super(controls); 181 182 if (bindDN == null) 183 { 184 this.bindDN = NO_BIND_DN; 185 } 186 else 187 { 188 this.bindDN = new ASN1OctetString(bindDN); 189 } 190 191 if (password == null) 192 { 193 this.password = NO_PASSWORD; 194 } 195 else 196 { 197 this.password = new ASN1OctetString(CRED_TYPE_SIMPLE, password); 198 } 199 200 passwordProvider = null; 201 } 202 203 204 205 /** 206 * Creates a new simple bind request with the provided bind DN and password. 207 * 208 * @param bindDN The bind DN for this simple bind request. 209 * @param password The password for this simple bind request. 210 * @param controls The set of controls for this simple bind request. 211 */ 212 public SimpleBindRequest(final String bindDN, final byte[] password, 213 final Control... controls) 214 { 215 super(controls); 216 217 if (bindDN == null) 218 { 219 this.bindDN = NO_BIND_DN; 220 } 221 else 222 { 223 this.bindDN = new ASN1OctetString(bindDN); 224 } 225 226 if (password == null) 227 { 228 this.password = NO_PASSWORD; 229 } 230 else 231 { 232 this.password = new ASN1OctetString(CRED_TYPE_SIMPLE, password); 233 } 234 235 passwordProvider = null; 236 } 237 238 239 240 /** 241 * Creates a new simple bind request with the provided bind DN and password. 242 * 243 * @param bindDN The bind DN for this simple bind request. 244 * @param password The password for this simple bind request. 245 * @param controls The set of controls for this simple bind request. 246 */ 247 public SimpleBindRequest(final DN bindDN, final String password, 248 final Control... controls) 249 { 250 super(controls); 251 252 if (bindDN == null) 253 { 254 this.bindDN = NO_BIND_DN; 255 } 256 else 257 { 258 this.bindDN = new ASN1OctetString(bindDN.toString()); 259 } 260 261 if (password == null) 262 { 263 this.password = NO_PASSWORD; 264 } 265 else 266 { 267 this.password = new ASN1OctetString(CRED_TYPE_SIMPLE, password); 268 } 269 270 passwordProvider = null; 271 } 272 273 274 275 /** 276 * Creates a new simple bind request with the provided bind DN and password. 277 * 278 * @param bindDN The bind DN for this simple bind request. 279 * @param password The password for this simple bind request. 280 * @param controls The set of controls for this simple bind request. 281 */ 282 public SimpleBindRequest(final DN bindDN, final byte[] password, 283 final Control... controls) 284 { 285 super(controls); 286 287 if (bindDN == null) 288 { 289 this.bindDN = NO_BIND_DN; 290 } 291 else 292 { 293 this.bindDN = new ASN1OctetString(bindDN.toString()); 294 } 295 296 if (password == null) 297 { 298 this.password = NO_PASSWORD; 299 } 300 else 301 { 302 this.password = new ASN1OctetString(CRED_TYPE_SIMPLE, password); 303 } 304 305 passwordProvider = null; 306 } 307 308 309 310 /** 311 * Creates a new simple bind request with the provided bind DN and that will 312 * use a password provider in order to obtain the bind password. 313 * 314 * @param bindDN The bind DN for this simple bind request. It 315 * must not be {@code null}. 316 * @param passwordProvider The password provider that will be used to obtain 317 * the password for this simple bind request. It 318 * must not be {@code null}. 319 * @param controls The set of controls for this simple bind request. 320 */ 321 public SimpleBindRequest(final String bindDN, 322 final PasswordProvider passwordProvider, 323 final Control... controls) 324 { 325 super(controls); 326 327 this.bindDN = new ASN1OctetString(bindDN); 328 this.passwordProvider = passwordProvider; 329 330 password = null; 331 } 332 333 334 335 /** 336 * Creates a new simple bind request with the provided bind DN and that will 337 * use a password provider in order to obtain the bind password. 338 * 339 * @param bindDN The bind DN for this simple bind request. It 340 * must not be {@code null}. 341 * @param passwordProvider The password provider that will be used to obtain 342 * the password for this simple bind request. It 343 * must not be {@code null}. 344 * @param controls The set of controls for this simple bind request. 345 */ 346 public SimpleBindRequest(final DN bindDN, 347 final PasswordProvider passwordProvider, 348 final Control... controls) 349 { 350 super(controls); 351 352 this.bindDN = new ASN1OctetString(bindDN.toString()); 353 this.passwordProvider = passwordProvider; 354 355 password = null; 356 } 357 358 359 360 /** 361 * Creates a new simple bind request with the provided bind DN and password. 362 * 363 * @param bindDN The bind DN for this simple bind request. 364 * @param password The password for this simple bind request. 365 * @param passwordProvider The password provider that will be used to obtain 366 * the password to use for the bind request. 367 * @param controls The set of controls for this simple bind request. 368 */ 369 private SimpleBindRequest(final ASN1OctetString bindDN, 370 final ASN1OctetString password, 371 final PasswordProvider passwordProvider, 372 final Control... controls) 373 { 374 super(controls); 375 376 this.bindDN = bindDN; 377 this.password = password; 378 this.passwordProvider = passwordProvider; 379 } 380 381 382 383 /** 384 * Retrieves the bind DN for this simple bind request. 385 * 386 * @return The bind DN for this simple bind request. 387 */ 388 public String getBindDN() 389 { 390 return bindDN.stringValue(); 391 } 392 393 394 395 /** 396 * Retrieves the password for this simple bind request, if no password 397 * provider has been configured. 398 * 399 * @return The password for this simple bind request, or {@code null} if a 400 * password provider will be used to obtain the password. 401 */ 402 public ASN1OctetString getPassword() 403 { 404 return password; 405 } 406 407 408 409 /** 410 * Retrieves the password provider for this simple bind request, if defined. 411 * 412 * @return The password provider for this simple bind request, or 413 * {@code null} if this bind request was created with an explicit 414 * password rather than a password provider. 415 */ 416 public PasswordProvider getPasswordProvider() 417 { 418 return passwordProvider; 419 } 420 421 422 423 /** 424 * {@inheritDoc} 425 */ 426 public byte getProtocolOpType() 427 { 428 return LDAPMessage.PROTOCOL_OP_TYPE_BIND_REQUEST; 429 } 430 431 432 433 /** 434 * {@inheritDoc} 435 */ 436 public void writeTo(final ASN1Buffer buffer) 437 { 438 final ASN1BufferSequence requestSequence = 439 buffer.beginSequence(LDAPMessage.PROTOCOL_OP_TYPE_BIND_REQUEST); 440 buffer.addElement(VERSION_ELEMENT); 441 buffer.addElement(bindDN); 442 443 if (passwordProvider == null) 444 { 445 buffer.addElement(password); 446 } 447 else 448 { 449 byte[] pwBytes; 450 try 451 { 452 pwBytes = passwordProvider.getPasswordBytes(); 453 } 454 catch (final LDAPException le) 455 { 456 debugException(le); 457 throw new LDAPRuntimeException(le); 458 } 459 460 final ASN1OctetString pw = new ASN1OctetString(CRED_TYPE_SIMPLE, pwBytes); 461 buffer.addElement(pw); 462 buffer.setZeroBufferOnClear(); 463 Arrays.fill(pwBytes, (byte) 0x00); 464 } 465 466 requestSequence.end(); 467 } 468 469 470 471 /** 472 * {@inheritDoc} 473 * Use of this method is only supported if the bind request was created with a 474 * static password. It is not allowed if the password will be obtained 475 * through a password provider. 476 * 477 * @throws LDAPSDKUsageException If this bind request was created with a 478 * password provider rather than a static 479 * password. 480 */ 481 public ASN1Element encodeProtocolOp() 482 throws LDAPSDKUsageException 483 { 484 if (password == null) 485 { 486 throw new LDAPSDKUsageException( 487 ERR_SIMPLE_BIND_ENCODE_PROTOCOL_OP_WITH_PROVIDER.get()); 488 } 489 490 return new ASN1Sequence(LDAPMessage.PROTOCOL_OP_TYPE_BIND_REQUEST, 491 new ASN1Integer(3), 492 bindDN, 493 password); 494 } 495 496 497 498 /** 499 * {@inheritDoc} 500 */ 501 @Override() 502 protected BindResult process(final LDAPConnection connection, final int depth) 503 throws LDAPException 504 { 505 if (connection.synchronousMode()) 506 { 507 return processSync(connection, 508 connection.getConnectionOptions().autoReconnect()); 509 } 510 511 // See if a bind DN was provided without a password. If that is the case 512 // and this should not be allowed, then throw an exception. 513 if (password != null) 514 { 515 if ((bindDN.getValue().length > 0) && (password.getValue().length == 0) && 516 connection.getConnectionOptions().bindWithDNRequiresPassword()) 517 { 518 final LDAPException le = new LDAPException(ResultCode.PARAM_ERROR, 519 ERR_SIMPLE_BIND_DN_WITHOUT_PASSWORD.get()); 520 debugCodingError(le); 521 throw le; 522 } 523 } 524 525 526 // Create the LDAP message. 527 messageID = connection.nextMessageID(); 528 final LDAPMessage message = new LDAPMessage(messageID, this, getControls()); 529 530 531 // Register with the connection reader to be notified of responses for the 532 // request that we've created. 533 connection.registerResponseAcceptor(messageID, this); 534 535 536 try 537 { 538 // Send the request to the server. 539 debugLDAPRequest(this); 540 final long requestTime = System.nanoTime(); 541 connection.getConnectionStatistics().incrementNumBindRequests(); 542 connection.sendMessage(message); 543 544 // Wait for and process the response. 545 final LDAPResponse response; 546 try 547 { 548 final long responseTimeout = getResponseTimeoutMillis(connection); 549 if (responseTimeout > 0) 550 { 551 response = responseQueue.poll(responseTimeout, TimeUnit.MILLISECONDS); 552 } 553 else 554 { 555 response = responseQueue.take(); 556 } 557 } 558 catch (InterruptedException ie) 559 { 560 debugException(ie); 561 throw new LDAPException(ResultCode.LOCAL_ERROR, 562 ERR_BIND_INTERRUPTED.get(connection.getHostPort()), ie); 563 } 564 565 return handleResponse(connection, response, requestTime, false); 566 } 567 finally 568 { 569 connection.deregisterResponseAcceptor(messageID); 570 } 571 } 572 573 574 575 /** 576 * Processes this bind operation in synchronous mode, in which the same 577 * thread will send the request and read the response. 578 * 579 * @param connection The connection to use to communicate with the directory 580 * server. 581 * @param allowRetry Indicates whether the request may be re-tried on a 582 * re-established connection if the initial attempt fails 583 * in a way that indicates the connection is no longer 584 * valid and autoReconnect is true. 585 * 586 * @return An LDAP result object that provides information about the result 587 * of the bind processing. 588 * 589 * @throws LDAPException If a problem occurs while sending the request or 590 * reading the response. 591 */ 592 private BindResult processSync(final LDAPConnection connection, 593 final boolean allowRetry) 594 throws LDAPException 595 { 596 // Create the LDAP message. 597 messageID = connection.nextMessageID(); 598 final LDAPMessage message = 599 new LDAPMessage(messageID, this, getControls()); 600 601 602 // Set the appropriate timeout on the socket. 603 try 604 { 605 connection.getConnectionInternals(true).getSocket().setSoTimeout( 606 (int) getResponseTimeoutMillis(connection)); 607 } 608 catch (Exception e) 609 { 610 debugException(e); 611 } 612 613 614 // Send the request to the server. 615 final long requestTime = System.nanoTime(); 616 debugLDAPRequest(this); 617 connection.getConnectionStatistics().incrementNumBindRequests(); 618 try 619 { 620 connection.sendMessage(message); 621 } 622 catch (final LDAPException le) 623 { 624 debugException(le); 625 626 if (allowRetry) 627 { 628 final BindResult bindResult = reconnectAndRetry(connection, 629 le.getResultCode()); 630 if (bindResult != null) 631 { 632 return bindResult; 633 } 634 } 635 } 636 637 while (true) 638 { 639 final LDAPResponse response = connection.readResponse(messageID); 640 if (response instanceof IntermediateResponse) 641 { 642 final IntermediateResponseListener listener = 643 getIntermediateResponseListener(); 644 if (listener != null) 645 { 646 listener.intermediateResponseReturned( 647 (IntermediateResponse) response); 648 } 649 } 650 else 651 { 652 return handleResponse(connection, response, requestTime, allowRetry); 653 } 654 } 655 } 656 657 658 659 /** 660 * Performs the necessary processing for handling a response. 661 * 662 * @param connection The connection used to read the response. 663 * @param response The response to be processed. 664 * @param requestTime The time the request was sent to the server. 665 * @param allowRetry Indicates whether the request may be re-tried on a 666 * re-established connection if the initial attempt fails 667 * in a way that indicates the connection is no longer 668 * valid and autoReconnect is true. 669 * 670 * @return The bind result. 671 * 672 * @throws LDAPException If a problem occurs. 673 */ 674 private BindResult handleResponse(final LDAPConnection connection, 675 final LDAPResponse response, 676 final long requestTime, 677 final boolean allowRetry) 678 throws LDAPException 679 { 680 if (response == null) 681 { 682 final long waitTime = nanosToMillis(System.nanoTime() - requestTime); 683 throw new LDAPException(ResultCode.TIMEOUT, 684 ERR_SIMPLE_BIND_CLIENT_TIMEOUT.get(waitTime, messageID, 685 bindDN.stringValue(), connection.getHostPort())); 686 } 687 688 connection.getConnectionStatistics().incrementNumBindResponses( 689 System.nanoTime() - requestTime); 690 if (response instanceof ConnectionClosedResponse) 691 { 692 // The connection was closed while waiting for the response. 693 if (allowRetry) 694 { 695 final BindResult retryResult = reconnectAndRetry(connection, 696 ResultCode.SERVER_DOWN); 697 if (retryResult != null) 698 { 699 return retryResult; 700 } 701 } 702 703 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response; 704 final String message = ccr.getMessage(); 705 if (message == null) 706 { 707 throw new LDAPException(ccr.getResultCode(), 708 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE.get( 709 connection.getHostPort(), toString())); 710 } 711 else 712 { 713 throw new LDAPException(ccr.getResultCode(), 714 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE_WITH_MESSAGE.get( 715 connection.getHostPort(), toString(), message)); 716 } 717 } 718 719 final BindResult bindResult = (BindResult) response; 720 if (allowRetry) 721 { 722 final BindResult retryResult = reconnectAndRetry(connection, 723 bindResult.getResultCode()); 724 if (retryResult != null) 725 { 726 return retryResult; 727 } 728 } 729 730 return bindResult; 731 } 732 733 734 735 /** 736 * Attempts to re-establish the connection and retry processing this request 737 * on it. 738 * 739 * @param connection The connection to be re-established. 740 * @param resultCode The result code for the previous operation attempt. 741 * 742 * @return The result from re-trying the bind, or {@code null} if it could 743 * not be re-tried. 744 */ 745 private BindResult reconnectAndRetry(final LDAPConnection connection, 746 final ResultCode resultCode) 747 { 748 try 749 { 750 // We will only want to retry for certain result codes that indicate a 751 // connection problem. 752 switch (resultCode.intValue()) 753 { 754 case ResultCode.SERVER_DOWN_INT_VALUE: 755 case ResultCode.DECODING_ERROR_INT_VALUE: 756 case ResultCode.CONNECT_ERROR_INT_VALUE: 757 connection.reconnect(); 758 return processSync(connection, false); 759 } 760 } 761 catch (final Exception e) 762 { 763 debugException(e); 764 } 765 766 return null; 767 } 768 769 770 771 /** 772 * {@inheritDoc} 773 */ 774 @Override() 775 public SimpleBindRequest getRebindRequest(final String host, final int port) 776 { 777 return new SimpleBindRequest(bindDN, password, passwordProvider, 778 getControls()); 779 } 780 781 782 783 /** 784 * {@inheritDoc} 785 */ 786 @InternalUseOnly() 787 public void responseReceived(final LDAPResponse response) 788 throws LDAPException 789 { 790 try 791 { 792 responseQueue.put(response); 793 } 794 catch (Exception e) 795 { 796 debugException(e); 797 throw new LDAPException(ResultCode.LOCAL_ERROR, 798 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e); 799 } 800 } 801 802 803 804 /** 805 * {@inheritDoc} 806 */ 807 @Override() 808 public String getBindType() 809 { 810 return "SIMPLE"; 811 } 812 813 814 815 /** 816 * {@inheritDoc} 817 */ 818 @Override() 819 public int getLastMessageID() 820 { 821 return messageID; 822 } 823 824 825 826 /** 827 * {@inheritDoc} 828 */ 829 @Override() 830 public SimpleBindRequest duplicate() 831 { 832 return duplicate(getControls()); 833 } 834 835 836 837 /** 838 * {@inheritDoc} 839 */ 840 @Override() 841 public SimpleBindRequest duplicate(final Control[] controls) 842 { 843 final SimpleBindRequest bindRequest = 844 new SimpleBindRequest(bindDN, password, passwordProvider, controls); 845 bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 846 return bindRequest; 847 } 848 849 850 851 /** 852 * {@inheritDoc} 853 */ 854 @Override() 855 public void toString(final StringBuilder buffer) 856 { 857 buffer.append("SimpleBindRequest(dn='"); 858 buffer.append(bindDN); 859 buffer.append('\''); 860 861 final Control[] controls = getControls(); 862 if (controls.length > 0) 863 { 864 buffer.append(", controls={"); 865 for (int i=0; i < controls.length; i++) 866 { 867 if (i > 0) 868 { 869 buffer.append(", "); 870 } 871 872 buffer.append(controls[i]); 873 } 874 buffer.append('}'); 875 } 876 877 buffer.append(')'); 878 } 879 }