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