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