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