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 }