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.concurrent.LinkedBlockingQueue;
026    import java.util.concurrent.TimeUnit;
027    
028    import com.unboundid.asn1.ASN1OctetString;
029    import com.unboundid.ldap.protocol.BindRequestProtocolOp;
030    import com.unboundid.ldap.protocol.LDAPMessage;
031    import com.unboundid.ldap.protocol.LDAPResponse;
032    import com.unboundid.util.InternalUseOnly;
033    
034    import static com.unboundid.ldap.sdk.LDAPMessages.*;
035    import static com.unboundid.util.Debug.*;
036    import static com.unboundid.util.StaticUtils.*;
037    
038    
039    
040    /**
041     * This class provides an API that should be used to represent an LDAPv3 SASL
042     * bind request.  A SASL bind includes a SASL mechanism name and an optional set
043     * of credentials.
044     * <BR><BR>
045     * See <A HREF="http://www.ietf.org/rfc/rfc4422.txt">RFC 4422</A> for more
046     * information about the Simple Authentication and Security Layer.
047     */
048    public abstract class SASLBindRequest
049           extends BindRequest
050           implements ResponseAcceptor
051    {
052      /**
053       * The BER type to use for the credentials element in a simple bind request
054       * protocol op.
055       */
056      protected static final byte CRED_TYPE_SASL = (byte) 0xA3;
057    
058    
059    
060      /**
061       * The serial version UID for this serializable class.
062       */
063      private static final long serialVersionUID = -5842126553864908312L;
064    
065    
066    
067      // The message ID to use for LDAP messages used in bind processing.
068      private int messageID;
069    
070      // The queue used to receive responses from the server.
071      private final LinkedBlockingQueue<LDAPResponse> responseQueue;
072    
073    
074    
075      /**
076       * Creates a new SASL bind request with the provided controls.
077       *
078       * @param  controls  The set of controls to include in this SASL bind request.
079       */
080      protected SASLBindRequest(final Control[] controls)
081      {
082        super(controls);
083    
084        messageID     = -1;
085        responseQueue = new LinkedBlockingQueue<LDAPResponse>();
086      }
087    
088    
089    
090      /**
091       * {@inheritDoc}
092       */
093      @Override()
094      public String getBindType()
095      {
096        return getSASLMechanismName();
097      }
098    
099    
100    
101      /**
102       * Retrieves the name of the SASL mechanism used in this SASL bind request.
103       *
104       * @return  The name of the SASL mechanism used in this SASL bind request.
105       */
106      public abstract String getSASLMechanismName();
107    
108    
109    
110      /**
111       * {@inheritDoc}
112       */
113      @Override()
114      public int getLastMessageID()
115      {
116        return messageID;
117      }
118    
119    
120    
121      /**
122       * Sends an LDAP message to the directory server and waits for the response.
123       *
124       * @param  connection       The connection to the directory server.
125       * @param  bindDN           The bind DN to use for the request.  It should be
126       *                          {@code null} for most types of SASL bind requests.
127       * @param  saslCredentials  The SASL credentials to use for the bind request.
128       *                          It may be {@code null} if no credentials are
129       *                          required.
130       * @param  controls         The set of controls to include in the request.  It
131       *                          may be {@code null} if no controls are required.
132       * @param  timeoutMillis   The maximum length of time in milliseconds to wait
133       *                         for a response, or zero if it should wait forever.
134       *
135       * @return  The bind response message returned by the directory server.
136       *
137       * @throws  LDAPException  If a problem occurs while sending the request or
138       *                         reading the response, or if a timeout occurred
139       *                         while waiting for the response.
140       */
141      protected final BindResult sendBindRequest(final LDAPConnection connection,
142                                      final String bindDN,
143                                      final ASN1OctetString saslCredentials,
144                                      final Control[] controls,
145                                      final long timeoutMillis)
146                throws LDAPException
147      {
148        if (messageID == -1)
149        {
150          messageID = connection.nextMessageID();
151        }
152    
153        final BindRequestProtocolOp protocolOp =
154             new BindRequestProtocolOp(bindDN, getSASLMechanismName(),
155                                       saslCredentials);
156    
157        final LDAPMessage requestMessage =
158             new LDAPMessage(messageID, protocolOp, controls);
159        return sendMessage(connection, requestMessage, timeoutMillis);
160      }
161    
162    
163    
164      /**
165       * Sends an LDAP message to the directory server and waits for the response.
166       *
167       * @param  connection      The connection to the directory server.
168       * @param  requestMessage  The LDAP message to send to the directory server.
169       * @param  timeoutMillis   The maximum length of time in milliseconds to wait
170       *                         for a response, or zero if it should wait forever.
171       *
172       * @return  The response message received from the server.
173       *
174       * @throws  LDAPException  If a problem occurs while sending the request or
175       *                         reading the response, or if a timeout occurred
176       *                         while waiting for the response.
177       */
178      protected final BindResult sendMessage(final LDAPConnection connection,
179                                             final LDAPMessage requestMessage,
180                                             final long timeoutMillis)
181                throws LDAPException
182      {
183        if (connection.synchronousMode())
184        {
185          return sendMessageSync(connection, requestMessage, timeoutMillis);
186        }
187    
188        final int msgID = requestMessage.getMessageID();
189        connection.registerResponseAcceptor(msgID, this);
190        try
191        {
192          final long requestTime = System.nanoTime();
193          connection.getConnectionStatistics().incrementNumBindRequests();
194          connection.sendMessage(requestMessage);
195    
196          // Wait for and process the response.
197          final LDAPResponse response;
198          try
199          {
200            if (timeoutMillis > 0)
201            {
202              response = responseQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
203            }
204            else
205            {
206              response = responseQueue.take();
207            }
208          }
209          catch (InterruptedException ie)
210          {
211            debugException(ie);
212            throw new LDAPException(ResultCode.LOCAL_ERROR,
213                 ERR_BIND_INTERRUPTED.get(connection.getHostPort()), ie);
214          }
215    
216          return handleResponse(connection, response, requestTime);
217        }
218        finally
219        {
220          connection.deregisterResponseAcceptor(msgID);
221        }
222      }
223    
224    
225    
226      /**
227       * Sends an LDAP message to the directory server and waits for the response.
228       * This should only be used when the connection is operating in synchronous
229       * mode.
230       *
231       * @param  connection      The connection to the directory server.
232       * @param  requestMessage  The LDAP message to send to the directory server.
233       * @param  timeoutMillis   The maximum length of time in milliseconds to wait
234       *                         for a response, or zero if it should wait forever.
235       *
236       * @return  The response message received from the server.
237       *
238       * @throws  LDAPException  If a problem occurs while sending the request or
239       *                         reading the response, or if a timeout occurred
240       *                         while waiting for the response.
241       */
242      private BindResult sendMessageSync(final LDAPConnection connection,
243                                         final LDAPMessage requestMessage,
244                                         final long timeoutMillis)
245                throws LDAPException
246      {
247        // Set the appropriate timeout on the socket.
248        try
249        {
250          connection.getConnectionInternals(true).getSocket().setSoTimeout(
251               (int) timeoutMillis);
252        }
253        catch (Exception e)
254        {
255          debugException(e);
256        }
257    
258    
259        final int msgID = requestMessage.getMessageID();
260        final long requestTime = System.nanoTime();
261        connection.getConnectionStatistics().incrementNumBindRequests();
262        connection.sendMessage(requestMessage);
263    
264        while (true)
265        {
266          final LDAPResponse response = connection.readResponse(messageID);
267          if (response instanceof IntermediateResponse)
268          {
269            final IntermediateResponseListener listener =
270                 getIntermediateResponseListener();
271            if (listener != null)
272            {
273              listener.intermediateResponseReturned(
274                   (IntermediateResponse) response);
275            }
276          }
277          else
278          {
279            return handleResponse(connection, response, requestTime);
280          }
281        }
282      }
283    
284    
285    
286      /**
287       * Performs the necessary processing for handling a response.
288       *
289       * @param  connection   The connection used to read the response.
290       * @param  response     The response to be processed.
291       * @param  requestTime  The time the request was sent to the server.
292       *
293       * @return  The bind result.
294       *
295       * @throws  LDAPException  If a problem occurs.
296       */
297      private BindResult handleResponse(final LDAPConnection connection,
298                                        final LDAPResponse response,
299                                        final long requestTime)
300              throws LDAPException
301      {
302        if (response == null)
303        {
304          final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
305          throw new LDAPException(ResultCode.TIMEOUT,
306               ERR_SASL_BIND_CLIENT_TIMEOUT.get(waitTime, getSASLMechanismName(),
307                    messageID, connection.getHostPort()));
308        }
309    
310        if (response instanceof ConnectionClosedResponse)
311        {
312          final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
313          final String message = ccr.getMessage();
314          if (message == null)
315          {
316            // The connection was closed while waiting for the response.
317            throw new LDAPException(ccr.getResultCode(),
318                 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE.get(
319                      connection.getHostPort(), toString()));
320          }
321          else
322          {
323            // The connection was closed while waiting for the response.
324            throw new LDAPException(ccr.getResultCode(),
325                 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE_WITH_MESSAGE.get(
326                      connection.getHostPort(), toString(), message));
327          }
328        }
329    
330        connection.getConnectionStatistics().incrementNumBindResponses(
331             System.nanoTime() - requestTime);
332        return (BindResult) response;
333      }
334    
335    
336    
337      /**
338       * {@inheritDoc}
339       */
340      @InternalUseOnly()
341      public final void responseReceived(final LDAPResponse response)
342             throws LDAPException
343      {
344        try
345        {
346          responseQueue.put(response);
347        }
348        catch (Exception e)
349        {
350          debugException(e);
351          throw new LDAPException(ResultCode.LOCAL_ERROR,
352               ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
353        }
354      }
355    }