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    }