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    }