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.Extensible;
035    import com.unboundid.util.InternalUseOnly;
036    import com.unboundid.util.ThreadSafety;
037    import com.unboundid.util.ThreadSafetyLevel;
038    
039    import static com.unboundid.ldap.sdk.LDAPMessages.*;
040    import static com.unboundid.util.Debug.*;
041    import 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)
055    public 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            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        // Set the appropriate timeout on the socket.
255        try
256        {
257          connection.getConnectionInternals(true).getSocket().setSoTimeout(
258               (int) timeoutMillis);
259        }
260        catch (Exception e)
261        {
262          debugException(e);
263        }
264    
265    
266        final int msgID = requestMessage.getMessageID();
267        final long requestTime = System.nanoTime();
268        connection.getConnectionStatistics().incrementNumBindRequests();
269        connection.sendMessage(requestMessage);
270    
271        while (true)
272        {
273          final LDAPResponse response = connection.readResponse(messageID);
274          if (response instanceof IntermediateResponse)
275          {
276            final IntermediateResponseListener listener =
277                 getIntermediateResponseListener();
278            if (listener != null)
279            {
280              listener.intermediateResponseReturned(
281                   (IntermediateResponse) response);
282            }
283          }
284          else
285          {
286            return handleResponse(connection, response, requestTime);
287          }
288        }
289      }
290    
291    
292    
293      /**
294       * Performs the necessary processing for handling a response.
295       *
296       * @param  connection   The connection used to read the response.
297       * @param  response     The response to be processed.
298       * @param  requestTime  The time the request was sent to the server.
299       *
300       * @return  The bind result.
301       *
302       * @throws  LDAPException  If a problem occurs.
303       */
304      private BindResult handleResponse(final LDAPConnection connection,
305                                        final LDAPResponse response,
306                                        final long requestTime)
307              throws LDAPException
308      {
309        if (response == null)
310        {
311          final long waitTime = nanosToMillis(System.nanoTime() - requestTime);
312          throw new LDAPException(ResultCode.TIMEOUT,
313               ERR_SASL_BIND_CLIENT_TIMEOUT.get(waitTime, getSASLMechanismName(),
314                    messageID, connection.getHostPort()));
315        }
316    
317        if (response instanceof ConnectionClosedResponse)
318        {
319          final ConnectionClosedResponse ccr = (ConnectionClosedResponse) response;
320          final String message = ccr.getMessage();
321          if (message == null)
322          {
323            // The connection was closed while waiting for the response.
324            throw new LDAPException(ccr.getResultCode(),
325                 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE.get(
326                      connection.getHostPort(), toString()));
327          }
328          else
329          {
330            // The connection was closed while waiting for the response.
331            throw new LDAPException(ccr.getResultCode(),
332                 ERR_CONN_CLOSED_WAITING_FOR_BIND_RESPONSE_WITH_MESSAGE.get(
333                      connection.getHostPort(), toString(), message));
334          }
335        }
336    
337        connection.getConnectionStatistics().incrementNumBindResponses(
338             System.nanoTime() - requestTime);
339        return (BindResult) response;
340      }
341    
342    
343    
344      /**
345       * {@inheritDoc}
346       */
347      @InternalUseOnly()
348      public final void responseReceived(final LDAPResponse response)
349             throws LDAPException
350      {
351        try
352        {
353          responseQueue.put(response);
354        }
355        catch (Exception e)
356        {
357          debugException(e);
358          throw new LDAPException(ResultCode.LOCAL_ERROR,
359               ERR_EXCEPTION_HANDLING_RESPONSE.get(getExceptionMessage(e)), e);
360        }
361      }
362    
363    
364    
365      /**
366       * {@inheritDoc}
367       */
368      public void toCode(final List<String> lineList, final String requestID,
369                         final int indentSpaces, final boolean includeProcessing)
370      {
371        // Create the request variable.
372        final ArrayList<ToCodeArgHelper> constructorArgs =
373             new ArrayList<ToCodeArgHelper>(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(), 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    }