001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 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.ArrayList;
026import java.util.List;
027import java.util.concurrent.LinkedBlockingQueue;
028import java.util.concurrent.TimeUnit;
029
030import com.unboundid.asn1.ASN1OctetString;
031import com.unboundid.ldap.protocol.BindRequestProtocolOp;
032import com.unboundid.ldap.protocol.LDAPMessage;
033import com.unboundid.ldap.protocol.LDAPResponse;
034import com.unboundid.util.Extensible;
035import com.unboundid.util.InternalUseOnly;
036import com.unboundid.util.ThreadSafety;
037import com.unboundid.util.ThreadSafetyLevel;
038
039import static com.unboundid.ldap.sdk.LDAPMessages.*;
040import static com.unboundid.util.Debug.*;
041import static com.unboundid.util.StaticUtils.*;
042
043
044
045/**
046 * This class provides an API that should be used to represent an LDAPv3 SASL
047 * bind request.  A SASL bind includes a SASL mechanism name and an optional set
048 * of credentials.
049 * <BR><BR>
050 * See <A HREF="http://www.ietf.org/rfc/rfc4422.txt">RFC 4422</A> for more
051 * information about the Simple Authentication and Security Layer.
052 */
053@Extensible()
054@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
055public abstract class SASLBindRequest
056       extends BindRequest
057       implements ResponseAcceptor
058{
059  /**
060   * The BER type to use for the credentials element in a simple bind request
061   * protocol op.
062   */
063  protected static final byte CRED_TYPE_SASL = (byte) 0xA3;
064
065
066
067  /**
068   * The serial version UID for this serializable class.
069   */
070  private static final long serialVersionUID = -5842126553864908312L;
071
072
073
074  // The message ID to use for LDAP messages used in bind processing.
075  private int messageID;
076
077  // The queue used to receive responses from the server.
078  private final LinkedBlockingQueue<LDAPResponse> responseQueue;
079
080
081
082  /**
083   * Creates a new SASL bind request with the provided controls.
084   *
085   * @param  controls  The set of controls to include in this SASL bind request.
086   */
087  protected SASLBindRequest(final Control[] controls)
088  {
089    super(controls);
090
091    messageID     = -1;
092    responseQueue = new LinkedBlockingQueue<LDAPResponse>();
093  }
094
095
096
097  /**
098   * {@inheritDoc}
099   */
100  @Override()
101  public String getBindType()
102  {
103    return getSASLMechanismName();
104  }
105
106
107
108  /**
109   * Retrieves the name of the SASL mechanism used in this SASL bind request.
110   *
111   * @return  The name of the SASL mechanism used in this SASL bind request.
112   */
113  public abstract String getSASLMechanismName();
114
115
116
117  /**
118   * {@inheritDoc}
119   */
120  @Override()
121  public int getLastMessageID()
122  {
123    return messageID;
124  }
125
126
127
128  /**
129   * Sends an LDAP message to the directory server and waits for the response.
130   *
131   * @param  connection       The connection to the directory server.
132   * @param  bindDN           The bind DN to use for the request.  It should be
133   *                          {@code null} for most types of SASL bind requests.
134   * @param  saslCredentials  The SASL credentials to use for the bind request.
135   *                          It may be {@code null} if no credentials are
136   *                          required.
137   * @param  controls         The set of controls to include in the request.  It
138   *                          may be {@code null} if no controls are required.
139   * @param  timeoutMillis   The maximum length of time in milliseconds to wait
140   *                         for a response, or zero if it should wait forever.
141   *
142   * @return  The bind response message returned by the directory server.
143   *
144   * @throws  LDAPException  If a problem occurs while sending the request or
145   *                         reading the response, or if a timeout occurred
146   *                         while waiting for the response.
147   */
148  protected final BindResult sendBindRequest(final LDAPConnection connection,
149                                  final String bindDN,
150                                  final ASN1OctetString saslCredentials,
151                                  final Control[] controls,
152                                  final long timeoutMillis)
153            throws LDAPException
154  {
155    if (messageID == -1)
156    {
157      messageID = connection.nextMessageID();
158    }
159
160    final BindRequestProtocolOp protocolOp =
161         new BindRequestProtocolOp(bindDN, getSASLMechanismName(),
162                                   saslCredentials);
163
164    final LDAPMessage requestMessage =
165         new LDAPMessage(messageID, protocolOp, controls);
166    return sendMessage(connection, requestMessage, timeoutMillis);
167  }
168
169
170
171  /**
172   * Sends an LDAP message to the directory server and waits for the response.
173   *
174   * @param  connection      The connection to the directory server.
175   * @param  requestMessage  The LDAP message to send to the directory server.
176   * @param  timeoutMillis   The maximum length of time in milliseconds to wait
177   *                         for a response, or zero if it should wait forever.
178   *
179   * @return  The response message received from the server.
180   *
181   * @throws  LDAPException  If a problem occurs while sending the request or
182   *                         reading the response, or if a timeout occurred
183   *                         while waiting for the response.
184   */
185  protected final BindResult sendMessage(final LDAPConnection connection,
186                                         final LDAPMessage requestMessage,
187                                         final long timeoutMillis)
188            throws LDAPException
189  {
190    if (connection.synchronousMode())
191    {
192      return sendMessageSync(connection, requestMessage, timeoutMillis);
193    }
194
195    final int msgID = requestMessage.getMessageID();
196    connection.registerResponseAcceptor(msgID, this);
197    try
198    {
199      final long requestTime = System.nanoTime();
200      connection.getConnectionStatistics().incrementNumBindRequests();
201      connection.sendMessage(requestMessage);
202
203      // Wait for and process the response.
204      final LDAPResponse response;
205      try
206      {
207        if (timeoutMillis > 0)
208        {
209          response = responseQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
210        }
211        else
212        {
213          response = responseQueue.take();
214        }
215      }
216      catch (InterruptedException ie)
217      {
218        debugException(ie);
219        Thread.currentThread().interrupt();
220        throw new LDAPException(ResultCode.LOCAL_ERROR,
221             ERR_BIND_INTERRUPTED.get(connection.getHostPort()), ie);
222      }
223
224      return handleResponse(connection, response, requestTime);
225    }
226    finally
227    {
228      connection.deregisterResponseAcceptor(msgID);
229    }
230  }
231
232
233
234  /**
235   * Sends an LDAP message to the directory server and waits for the response.
236   * This should only be used when the connection is operating in synchronous
237   * mode.
238   *
239   * @param  connection      The connection to the directory server.
240   * @param  requestMessage  The LDAP message to send to the directory server.
241   * @param  timeoutMillis   The maximum length of time in milliseconds to wait
242   *                         for a response, or zero if it should wait forever.
243   *
244   * @return  The response message received from the server.
245   *
246   * @throws  LDAPException  If a problem occurs while sending the request or
247   *                         reading the response, or if a timeout occurred
248   *                         while waiting for the response.
249   */
250  private BindResult sendMessageSync(final LDAPConnection connection,
251                                     final LDAPMessage requestMessage,
252                                     final long timeoutMillis)
253            throws LDAPException
254  {
255    // Set the appropriate timeout on the socket.
256    try
257    {
258      connection.getConnectionInternals(true).getSocket().setSoTimeout(
259           (int) timeoutMillis);
260    }
261    catch (Exception e)
262    {
263      debugException(e);
264    }
265
266
267    final int msgID = requestMessage.getMessageID();
268    final long requestTime = System.nanoTime();
269    connection.getConnectionStatistics().incrementNumBindRequests();
270    connection.sendMessage(requestMessage);
271
272    while (true)
273    {
274      final LDAPResponse response = connection.readResponse(messageID);
275      if (response instanceof IntermediateResponse)
276      {
277        final IntermediateResponseListener listener =
278             getIntermediateResponseListener();
279        if (listener != null)
280        {
281          listener.intermediateResponseReturned(
282               (IntermediateResponse) response);
283        }
284      }
285      else
286      {
287        return handleResponse(connection, response, requestTime);
288      }
289    }
290  }
291
292
293
294  /**
295   * Performs the necessary processing for handling a response.
296   *
297   * @param  connection   The connection used to read the response.
298   * @param  response     The response to be processed.
299   * @param  requestTime  The time the request was sent to the server.
300   *
301   * @return  The bind result.
302   *
303   * @throws  LDAPException  If a problem occurs.
304   */
305  private BindResult handleResponse(final LDAPConnection connection,
306                                    final LDAPResponse response,
307                                    final long requestTime)
308          throws LDAPException
309  {
310    if (response == null)
311    {
312      final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
313      throw new LDAPException(ResultCode.TIMEOUT,
314           ERR_SASL_BIND_CLIENT_TIMEOUT.get(waitTime, getSASLMechanismName(),
315                messageID, connection.getHostPort()));
316    }
317
318    if (response instanceof ConnectionClosedResponse)
319    {
320      final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
321      final String message = ccr.getMessage();
322      if (message == null)
323      {
324        // The connection was closed while waiting for the response.
325        throw new LDAPException(ccr.getResultCode(),
326             ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE.get(
327                  connection.getHostPort(), toString()));
328      }
329      else
330      {
331        // The connection was closed while waiting for the response.
332        throw new LDAPException(ccr.getResultCode(),
333             ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE_WITH_MESSAGE.get(
334                  connection.getHostPort(), toString(), message));
335      }
336    }
337
338    connection.getConnectionStatistics().incrementNumBindResponses(
339         System.nanoTime() - requestTime);
340    return (BindResult) response;
341  }
342
343
344
345  /**
346   * {@inheritDoc}
347   */
348  @InternalUseOnly()
349  public final void responseReceived(final LDAPResponse response)
350         throws LDAPException
351  {
352    try
353    {
354      responseQueue.put(response);
355    }
356    catch (Exception e)
357    {
358      debugException(e);
359
360      if (e instanceof InterruptedException)
361      {
362        Thread.currentThread().interrupt();
363      }
364
365      throw new LDAPException(ResultCode.LOCAL_ERROR,
366           ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
367    }
368  }
369
370
371
372  /**
373   * {@inheritDoc}
374   */
375  public void toCode(final List<String> lineList, final String requestID,
376                     final int indentSpaces, final boolean includeProcessing)
377  {
378    // Create the request variable.
379    final ArrayList<ToCodeArgHelper> constructorArgs =
380         new ArrayList<ToCodeArgHelper>(4);
381    constructorArgs.add(ToCodeArgHelper.createString(null, "Bind DN"));
382    constructorArgs.add(ToCodeArgHelper.createString(getSASLMechanismName(),
383         "SASL Mechanism Name"));
384    constructorArgs.add(ToCodeArgHelper.createByteArray(
385         "---redacted-SASL-credentials".getBytes(), true,
386         "SASL Credentials"));
387
388    final Control[] controls = getControls();
389    if (controls.length > 0)
390    {
391      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
392           "Bind Controls"));
393    }
394
395    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
396         "GenericSASLBindRequest", requestID + "Request",
397         "new GenericSASLBindRequest", constructorArgs);
398
399
400    // Add lines for processing the request and obtaining the result.
401    if (includeProcessing)
402    {
403      // Generate a string with the appropriate indent.
404      final StringBuilder buffer = new StringBuilder();
405      for (int i=0; i < indentSpaces; i++)
406      {
407        buffer.append(' ');
408      }
409      final String indent = buffer.toString();
410
411      lineList.add("");
412      lineList.add(indent + '{');
413      lineList.add(indent + "  BindResult " + requestID +
414           "Result = connection.bind(" + requestID + "Request);");
415      lineList.add(indent + "  // The bind was processed successfully.");
416      lineList.add(indent + '}');
417      lineList.add(indent + "catch (SASLBindInProgressException e)");
418      lineList.add(indent + '{');
419      lineList.add(indent + "  // The SASL bind requires multiple stages.  " +
420           "Continue it here.");
421      lineList.add(indent + "  // Do not attempt to use the connection for " +
422           "any other purpose until bind processing has completed.");
423      lineList.add(indent + '}');
424      lineList.add(indent + "catch (LDAPException e)");
425      lineList.add(indent + '{');
426      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
427           "help explain why.");
428      lineList.add(indent + "  // Note that the connection is now likely in " +
429           "an unauthenticated state.");
430      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
431      lineList.add(indent + "  String message = e.getMessage();");
432      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
433      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
434      lineList.add(indent + "  Control[] responseControls = " +
435           "e.getResponseControls();");
436      lineList.add(indent + '}');
437    }
438  }
439}