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 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.util.concurrent.LinkedBlockingQueue;
026import java.util.concurrent.TimeUnit;
027
028import com.unboundid.asn1.ASN1OctetString;
029import com.unboundid.ldap.protocol.BindRequestProtocolOp;
030import com.unboundid.ldap.protocol.LDAPMessage;
031import com.unboundid.ldap.protocol.LDAPResponse;
032import com.unboundid.util.Extensible;
033import com.unboundid.util.InternalUseOnly;
034import com.unboundid.util.ThreadSafety;
035import com.unboundid.util.ThreadSafetyLevel;
036
037import static com.unboundid.ldap.sdk.LDAPMessages.*;
038import static com.unboundid.util.Debug.*;
039import 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)
053public 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}