001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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    }