001    /*
002     * Copyright 2007-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2013 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.logging.Level;
026    import javax.security.auth.callback.Callback;
027    import javax.security.auth.callback.CallbackHandler;
028    import javax.security.auth.callback.NameCallback;
029    import javax.security.auth.callback.PasswordCallback;
030    import javax.security.sasl.Sasl;
031    import javax.security.sasl.SaslClient;
032    
033    import com.unboundid.asn1.ASN1OctetString;
034    import com.unboundid.util.DebugType;
035    import com.unboundid.util.InternalUseOnly;
036    import com.unboundid.util.NotMutable;
037    import com.unboundid.util.ThreadSafety;
038    import com.unboundid.util.ThreadSafetyLevel;
039    
040    import static com.unboundid.ldap.sdk.LDAPMessages.*;
041    import static com.unboundid.util.Debug.*;
042    import static com.unboundid.util.StaticUtils.*;
043    import static com.unboundid.util.Validator.*;
044    
045    
046    
047    /**
048     * This class provides a SASL CRAM-MD5 bind request implementation as described
049     * in draft-ietf-sasl-crammd5.  The CRAM-MD5 mechanism can be used to
050     * authenticate over an insecure channel without exposing the credentials
051     * (although it requires that the server have access to the clear-text
052     * password).    It is similar to DIGEST-MD5, but does not provide as many
053     * options, and provides slightly weaker protection because the client does not
054     * contribute any of the random data used during bind processing.
055     * <BR><BR>
056     * Elements included in a CRAM-MD5 bind request include:
057     * <UL>
058     *   <LI>Authentication ID -- A string which identifies the user that is
059     *       attempting to authenticate.  It should be an "authzId" value as
060     *       described in section 5.2.1.8 of
061     *       <A HREF="http://www.ietf.org/rfc/rfc4513.txt">RFC 4513</A>.  That is,
062     *       it should be either "dn:" followed by the distinguished name of the
063     *       target user, or "u:" followed by the username.  If the "u:" form is
064     *       used, then the mechanism used to resolve the provided username to an
065     *       entry may vary from server to server.</LI>
066     *   <LI>Password -- The clear-text password for the target user.</LI>
067     * </UL>
068     * <H2>Example</H2>
069     * The following example demonstrates the process for performing a CRAM-MD5
070     * bind against a directory server with a username of "john.doe" and a password
071     * of "password":
072     * <PRE>
073     *   CRAMMD5BindRequest bindRequest =
074     *        new CRAMMD5BindRequest("u:john.doe", "password");
075     *   try
076     *   {
077     *     BindResult bindResult = connection.bind(bindRequest);
078     *     // If we get here, then the bind was successful.
079     *   }
080     *   catch (LDAPException le)
081     *   {
082     *     // The bind failed for some reason.
083     *   }
084     * </PRE>
085     */
086    @NotMutable()
087    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
088    public final class CRAMMD5BindRequest
089           extends SASLBindRequest
090           implements CallbackHandler
091    {
092      /**
093       * The name for the CRAM-MD5 SASL mechanism.
094       */
095      public static final String CRAMMD5_MECHANISM_NAME = "CRAM-MD5";
096    
097    
098    
099      /**
100       * The serial version UID for this serializable class.
101       */
102      private static final long serialVersionUID = -4556570436768136483L;
103    
104    
105    
106      // The password for this bind request.
107      private final ASN1OctetString password;
108    
109      // The message ID from the last LDAP message sent from this request.
110      private int messageID = -1;
111    
112      // The authentication ID string for this bind request.
113      private final String authenticationID;
114    
115    
116    
117      /**
118       * Creates a new SASL CRAM-MD5 bind request with the provided authentication
119       * ID and password.  It will not include any controls.
120       *
121       * @param  authenticationID  The authentication ID for this bind request.  It
122       *                           must not be {@code null}.
123       * @param  password          The password for this bind request.  It must not
124       *                           be {@code null}.
125       */
126      public CRAMMD5BindRequest(final String authenticationID,
127                                final String password)
128      {
129        this(authenticationID, new ASN1OctetString(password), NO_CONTROLS);
130    
131        ensureNotNull(password);
132      }
133    
134    
135    
136      /**
137       * Creates a new SASL CRAM-MD5 bind request with the provided authentication
138       * ID and password.  It will not include any controls.
139       *
140       * @param  authenticationID  The authentication ID for this bind request.  It
141       *                           must not be {@code null}.
142       * @param  password          The password for this bind request.  It must not
143       *                           be {@code null}.
144       */
145      public CRAMMD5BindRequest(final String authenticationID,
146                                final byte[] password)
147      {
148        this(authenticationID, new ASN1OctetString(password), NO_CONTROLS);
149    
150        ensureNotNull(password);
151      }
152    
153    
154    
155      /**
156       * Creates a new SASL CRAM-MD5 bind request with the provided authentication
157       * ID and password.  It will not include any controls.
158       *
159       * @param  authenticationID  The authentication ID for this bind request.  It
160       *                           must not be {@code null}.
161       * @param  password          The password for this bind request.  It must not
162       *                           be {@code null}.
163       */
164      public CRAMMD5BindRequest(final String authenticationID,
165                                final ASN1OctetString password)
166      {
167        this(authenticationID, password, NO_CONTROLS);
168      }
169    
170    
171    
172      /**
173       * Creates a new SASL CRAM-MD5 bind request with the provided authentication
174       * ID, password, and set of controls.
175       *
176       * @param  authenticationID  The authentication ID for this bind request.  It
177       *                           must not be {@code null}.
178       * @param  password          The password for this bind request.  It must not
179       *                           be {@code null}.
180       * @param  controls          The set of controls to include in the request.
181       */
182      public CRAMMD5BindRequest(final String authenticationID,
183                                final String password, final Control... controls)
184      {
185        this(authenticationID, new ASN1OctetString(password), controls);
186    
187        ensureNotNull(password);
188      }
189    
190    
191    
192      /**
193       * Creates a new SASL CRAM-MD5 bind request with the provided authentication
194       * ID, password, and set of controls.
195       *
196       * @param  authenticationID  The authentication ID for this bind request.  It
197       *                           must not be {@code null}.
198       * @param  password          The password for this bind request.  It must not
199       *                           be {@code null}.
200       * @param  controls          The set of controls to include in the request.
201       */
202      public CRAMMD5BindRequest(final String authenticationID,
203                                final byte[] password, final Control... controls)
204      {
205        this(authenticationID, new ASN1OctetString(password), controls);
206    
207        ensureNotNull(password);
208      }
209    
210    
211    
212      /**
213       * Creates a new SASL CRAM-MD5 bind request with the provided authentication
214       * ID, password, and set of controls.
215       *
216       * @param  authenticationID  The authentication ID for this bind request.  It
217       *                           must not be {@code null}.
218       * @param  password          The password for this bind request.  It must not
219       *                           be {@code null}.
220       * @param  controls          The set of controls to include in the request.
221       */
222      public CRAMMD5BindRequest(final String authenticationID,
223                                final ASN1OctetString password,
224                                final Control... controls)
225      {
226        super(controls);
227    
228        ensureNotNull(authenticationID, password);
229    
230        this.authenticationID = authenticationID;
231        this.password         = password;
232      }
233    
234    
235    
236      /**
237       * {@inheritDoc}
238       */
239      @Override()
240      public String getSASLMechanismName()
241      {
242        return CRAMMD5_MECHANISM_NAME;
243      }
244    
245    
246    
247      /**
248       * Retrieves the authentication ID for this bind request.
249       *
250       * @return  The authentication ID for this bind request.
251       */
252      public String getAuthenticationID()
253      {
254        return authenticationID;
255      }
256    
257    
258    
259      /**
260       * Retrieves the string representation of the password for this bind request.
261       *
262       * @return  The string representation of the password for this bind request.
263       */
264      public String getPasswordString()
265      {
266        return password.stringValue();
267      }
268    
269    
270    
271      /**
272       * Retrieves the bytes that comprise the the password for this bind request.
273       *
274       * @return  The bytes that comprise the password for this bind request.
275       */
276      public byte[] getPasswordBytes()
277      {
278        return password.getValue();
279      }
280    
281    
282    
283      /**
284       * Sends this bind request to the target server over the provided connection
285       * and returns the corresponding response.
286       *
287       * @param  connection  The connection to use to send this bind request to the
288       *                     server and read the associated response.
289       * @param  depth       The current referral depth for this request.  It should
290       *                     always be one for the initial request, and should only
291       *                     be incremented when following referrals.
292       *
293       * @return  The bind response read from the server.
294       *
295       * @throws  LDAPException  If a problem occurs while sending the request or
296       *                         reading the response.
297       */
298      @Override()
299      protected BindResult process(final LDAPConnection connection, final int depth)
300                throws LDAPException
301      {
302        final SaslClient saslClient;
303        final String[] mechanisms = { CRAMMD5_MECHANISM_NAME };
304    
305        try
306        {
307          saslClient = Sasl.createSaslClient(mechanisms, null, "ldap",
308                                             connection.getConnectedAddress(), null,
309                                             this);
310        }
311        catch (Exception e)
312        {
313          debugException(e);
314          throw new LDAPException(ResultCode.LOCAL_ERROR,
315               ERR_CRAMMD5_CANNOT_CREATE_SASL_CLIENT.get(getExceptionMessage(e)),
316               e);
317        }
318    
319        final SASLHelper helper = new SASLHelper(this, connection,
320             CRAMMD5_MECHANISM_NAME, saslClient, getControls(),
321             getResponseTimeoutMillis(connection));
322    
323        try
324        {
325          return helper.processSASLBind();
326        }
327        finally
328        {
329          messageID = helper.getMessageID();
330        }
331      }
332    
333    
334    
335      /**
336       * {@inheritDoc}
337       */
338      @Override()
339      public CRAMMD5BindRequest getRebindRequest(final String host, final int port)
340      {
341        return new CRAMMD5BindRequest(authenticationID, password, getControls());
342      }
343    
344    
345    
346      /**
347       * Handles any necessary callbacks required for SASL authentication.
348       *
349       * @param  callbacks  The set of callbacks to be handled.
350       */
351      @InternalUseOnly()
352      public void handle(final Callback[] callbacks)
353      {
354        for (final Callback callback : callbacks)
355        {
356          if (callback instanceof NameCallback)
357          {
358            ((NameCallback) callback).setName(authenticationID);
359          }
360          else if (callback instanceof PasswordCallback)
361          {
362            ((PasswordCallback) callback).setPassword(
363                 password.stringValue().toCharArray());
364          }
365          else
366          {
367            // This is an unexpected callback.
368            if (debugEnabled(DebugType.LDAP))
369            {
370              debug(Level.WARNING, DebugType.LDAP,
371                    "Unexpected CRAM-MD5 SASL callback of type " +
372                    callback.getClass().getName());
373            }
374          }
375        }
376      }
377    
378    
379    
380      /**
381       * {@inheritDoc}
382       */
383      @Override()
384      public int getLastMessageID()
385      {
386        return messageID;
387      }
388    
389    
390    
391      /**
392       * {@inheritDoc}
393       */
394      @Override()
395      public CRAMMD5BindRequest duplicate()
396      {
397        return duplicate(getControls());
398      }
399    
400    
401    
402      /**
403       * {@inheritDoc}
404       */
405      @Override()
406      public CRAMMD5BindRequest duplicate(final Control[] controls)
407      {
408        final CRAMMD5BindRequest bindRequest =
409             new CRAMMD5BindRequest(authenticationID, password, controls);
410        bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
411        return bindRequest;
412      }
413    
414    
415    
416      /**
417       * {@inheritDoc}
418       */
419      @Override()
420      public void toString(final StringBuilder buffer)
421      {
422        buffer.append("CRAMMD5BindRequest(authenticationID='");
423        buffer.append(authenticationID);
424        buffer.append('\'');
425    
426        final Control[] controls = getControls();
427        if (controls.length > 0)
428        {
429          buffer.append(", controls={");
430          for (int i=0; i < controls.length; i++)
431          {
432            if (i > 0)
433            {
434              buffer.append(", ");
435            }
436    
437            buffer.append(controls[i]);
438          }
439          buffer.append('}');
440        }
441    
442        buffer.append(')');
443      }
444    }