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