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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.Arrays;
026import java.util.concurrent.LinkedBlockingQueue;
027import java.util.concurrent.TimeUnit;
028
029import com.unboundid.asn1.ASN1Buffer;
030import com.unboundid.asn1.ASN1BufferSequence;
031import com.unboundid.asn1.ASN1Element;
032import com.unboundid.asn1.ASN1Integer;
033import com.unboundid.asn1.ASN1OctetString;
034import com.unboundid.asn1.ASN1Sequence;
035import com.unboundid.ldap.protocol.LDAPMessage;
036import com.unboundid.ldap.protocol.LDAPResponse;
037import com.unboundid.ldap.protocol.ProtocolOp;
038import com.unboundid.util.InternalUseOnly;
039import com.unboundid.util.LDAPSDKUsageException;
040import com.unboundid.util.NotMutable;
041import com.unboundid.util.ThreadSafety;
042import com.unboundid.util.ThreadSafetyLevel;
043
044import static com.unboundid.ldap.sdk.LDAPMessages.*;
045import static com.unboundid.util.Debug.*;
046import 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)
056public 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}