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     */
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            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    }