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 }