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