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 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_SIMPLE_BIND_CLIENT_TIMEOUT.get(waitTime, messageID,
690 bindDN.stringValue(), connection.getHostPort()));
691 }
692
693 connection.getConnectionStatistics().incrementNumBindResponses(
694 System.nanoTime() - requestTime);
695 if (response instanceof ConnectionClosedResponse)
696 {
697 // The connection was closed while waiting for the response.
698 if (allowRetry)
699 {
700 final BindResult retryResult = reconnectAndRetry(connection,
701 ResultCode.SERVER_DOWN);
702 if (retryResult != null)
703 {
704 return retryResult;
705 }
706 }
707
708 final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
709 final String message = ccr.getMessage();
710 if (message == null)
711 {
712 throw new LDAPException(ccr.getResultCode(),
713 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE.get(
714 connection.getHostPort(), toString()));
715 }
716 else
717 {
718 throw new LDAPException(ccr.getResultCode(),
719 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE_WITH_MESSAGE.get(
720 connection.getHostPort(), toString(), message));
721 }
722 }
723
724 final BindResult bindResult = (BindResult) response;
725 if (allowRetry)
726 {
727 final BindResult retryResult = reconnectAndRetry(connection,
728 bindResult.getResultCode());
729 if (retryResult != null)
730 {
731 return retryResult;
732 }
733 }
734
735 return bindResult;
736 }
737
738
739
740 /**
741 * Attempts to re-establish the connection and retry processing this request
742 * on it.
743 *
744 * @param connection The connection to be re-established.
745 * @param resultCode The result code for the previous operation attempt.
746 *
747 * @return The result from re-trying the bind, or {@code null} if it could
748 * not be re-tried.
749 */
750 private BindResult reconnectAndRetry(final LDAPConnection connection,
751 final ResultCode resultCode)
752 {
753 try
754 {
755 // We will only want to retry for certain result codes that indicate a
756 // connection problem.
757 switch (resultCode.intValue())
758 {
759 case ResultCode.SERVER_DOWN_INT_VALUE:
760 case ResultCode.DECODING_ERROR_INT_VALUE:
761 case ResultCode.CONNECT_ERROR_INT_VALUE:
762 connection.reconnect();
763 return processSync(connection, false);
764 }
765 }
766 catch (final Exception e)
767 {
768 debugException(e);
769 }
770
771 return null;
772 }
773
774
775
776 /**
777 * {@inheritDoc}
778 */
779 @Override()
780 public SimpleBindRequest getRebindRequest(final String host, final int port)
781 {
782 return new SimpleBindRequest(bindDN, password, passwordProvider,
783 getControls());
784 }
785
786
787
788 /**
789 * {@inheritDoc}
790 */
791 @InternalUseOnly()
792 public void responseReceived(final LDAPResponse response)
793 throws LDAPException
794 {
795 try
796 {
797 responseQueue.put(response);
798 }
799 catch (Exception e)
800 {
801 debugException(e);
802 throw new LDAPException(ResultCode.LOCAL_ERROR,
803 ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
804 }
805 }
806
807
808
809 /**
810 * {@inheritDoc}
811 */
812 @Override()
813 public String getBindType()
814 {
815 return "SIMPLE";
816 }
817
818
819
820 /**
821 * {@inheritDoc}
822 */
823 @Override()
824 public int getLastMessageID()
825 {
826 return messageID;
827 }
828
829
830
831 /**
832 * {@inheritDoc}
833 */
834 @Override()
835 public SimpleBindRequest duplicate()
836 {
837 return duplicate(getControls());
838 }
839
840
841
842 /**
843 * {@inheritDoc}
844 */
845 @Override()
846 public SimpleBindRequest duplicate(final Control[] controls)
847 {
848 final SimpleBindRequest bindRequest =
849 new SimpleBindRequest(bindDN, password, passwordProvider, controls);
850 bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
851 return bindRequest;
852 }
853
854
855
856 /**
857 * {@inheritDoc}
858 */
859 @Override()
860 public void toString(final StringBuilder buffer)
861 {
862 buffer.append("SimpleBindRequest(dn='");
863 buffer.append(bindDN);
864 buffer.append('\'');
865
866 final Control[] controls = getControls();
867 if (controls.length > 0)
868 {
869 buffer.append(", controls={");
870 for (int i=0; i < controls.length; i++)
871 {
872 if (i > 0)
873 {
874 buffer.append(", ");
875 }
876
877 buffer.append(controls[i]);
878 }
879 buffer.append('}');
880 }
881
882 buffer.append(')');
883 }
884 }