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