001 /*
002 * Copyright 2011-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2011-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.util;
022
023
024
025 import java.lang.reflect.InvocationTargetException;
026 import java.lang.reflect.Method;
027 import java.util.ArrayList;
028 import java.util.Collections;
029 import java.util.HashMap;
030 import java.util.List;
031 import java.util.Map;
032 import java.util.TreeMap;
033
034 import com.unboundid.ldap.sdk.ANONYMOUSBindRequest;
035 import com.unboundid.ldap.sdk.Control;
036 import com.unboundid.ldap.sdk.CRAMMD5BindRequest;
037 import com.unboundid.ldap.sdk.DIGESTMD5BindRequest;
038 import com.unboundid.ldap.sdk.DIGESTMD5BindRequestProperties;
039 import com.unboundid.ldap.sdk.EXTERNALBindRequest;
040 import com.unboundid.ldap.sdk.GSSAPIBindRequest;
041 import com.unboundid.ldap.sdk.GSSAPIBindRequestProperties;
042 import com.unboundid.ldap.sdk.LDAPException;
043 import com.unboundid.ldap.sdk.PLAINBindRequest;
044 import com.unboundid.ldap.sdk.ResultCode;
045 import com.unboundid.ldap.sdk.SASLBindRequest;
046 import com.unboundid.ldap.sdk.SASLQualityOfProtection;
047
048 import static com.unboundid.util.StaticUtils.*;
049 import static com.unboundid.util.UtilityMessages.*;
050
051
052
053 /**
054 * This class provides a utility that may be used to help process SASL bind
055 * operations using the LDAP SDK.
056 */
057 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
058 public final class SASLUtils
059 {
060 /**
061 * The name of the SASL option that specifies the authentication ID. It may
062 * be used in conjunction with the CRAM-MD5, DIGEST-MD5, GSSAPI, and PLAIN
063 * mechanisms.
064 */
065 public static final String SASL_OPTION_AUTH_ID = "authID";
066
067
068
069 /**
070 * The name of the SASL option that specifies the authorization ID. It may
071 * be used in conjunction with the DIGEST-MD5, GSSAPI, and PLAIN mechanisms.
072 */
073 public static final String SASL_OPTION_AUTHZ_ID = "authzID";
074
075
076
077 /**
078 * The name of the SASL option that specifies the path to the JAAS config
079 * file. It may be used in conjunction with the GSSAPI mechanism.
080 */
081 public static final String SASL_OPTION_CONFIG_FILE = "configFile";
082
083
084
085 /**
086 * The name of the SASL option that indicates whether debugging should be
087 * enabled. It may be used in conjunction with the GSSAPI mechanism.
088 */
089 public static final String SASL_OPTION_DEBUG = "debug";
090
091
092
093 /**
094 * The name of the SASL option that specifies the KDC address. It may be used
095 * in conjunction with the GSSAPI mechanism.
096 */
097 public static final String SASL_OPTION_KDC_ADDRESS = "kdcAddress";
098
099
100
101
102 /**
103 * The name of the SASL option that specifies the desired SASL mechanism to
104 * use to authenticate to the server.
105 */
106 public static final String SASL_OPTION_MECHANISM = "mech";
107
108
109
110 /**
111 * The name of the SASL option that specifies the GSSAPI service principal
112 * protocol. It may be used in conjunction with the GSSAPI mechanism.
113 */
114 public static final String SASL_OPTION_PROTOCOL = "protocol";
115
116
117
118 /**
119 * The name of the SASL option that specifies the quality of protection that
120 * should be used for communication that occurs after the authentication has
121 * completed.
122 */
123 public static final String SASL_OPTION_QOP = "qop";
124
125
126
127 /**
128 * The name of the SASL option that specifies the realm name. It may be used
129 * in conjunction with the DIGEST-MD5 and GSSAPI mechanisms.
130 */
131 public static final String SASL_OPTION_REALM = "realm";
132
133
134
135 /**
136 * The name of the SASL option that indicates whether to require an existing
137 * Kerberos session from the ticket cache. It may be used in conjunction with
138 * the GSSAPI mechanism.
139 */
140 public static final String SASL_OPTION_REQUIRE_CACHE = "requireCache";
141
142
143
144 /**
145 * The name of the SASL option that indicates whether to attempt to renew the
146 * Kerberos TGT for an existing session. It may be used in conjunction with
147 * the GSSAPI mechanism.
148 */
149 public static final String SASL_OPTION_RENEW_TGT = "renewTGT";
150
151
152
153 /**
154 * The name of the SASL option that specifies the path to the Kerberos ticket
155 * cache to use. It may be used in conjunction with the GSSAPI mechanism.
156 */
157 public static final String SASL_OPTION_TICKET_CACHE_PATH = "ticketCache";
158
159
160
161 /**
162 * The name of the SASL option that specifies the trace string. It may be
163 * used in conjunction with the ANONYMOUS mechanism.
164 */
165 public static final String SASL_OPTION_TRACE = "trace";
166
167
168
169 /**
170 * The name of the SASL option that specifies whether to use a Kerberos ticket
171 * cache. It may be used in conjunction with the GSSAPI mechanism.
172 */
173 public static final String SASL_OPTION_USE_TICKET_CACHE = "useTicketCache";
174
175
176
177 /**
178 * A map with information about all supported SASL mechanisms, mapped from
179 * lowercase mechanism name to an object with information about that
180 * mechanism.
181 */
182 private static final Map<String,SASLMechanismInfo> SASL_MECHANISMS;
183
184
185
186 static
187 {
188 final TreeMap<String,SASLMechanismInfo> m =
189 new TreeMap<String,SASLMechanismInfo>();
190
191 m.put(toLowerCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME),
192 new SASLMechanismInfo(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME,
193 INFO_SASL_ANONYMOUS_DESCRIPTION.get(), false, false,
194 new SASLOption(SASL_OPTION_TRACE,
195 INFO_SASL_ANONYMOUS_OPTION_TRACE.get(), false, false)));
196
197 m.put(toLowerCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME),
198 new SASLMechanismInfo(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME,
199 INFO_SASL_CRAM_MD5_DESCRIPTION.get(), true, true,
200 new SASLOption(SASL_OPTION_AUTH_ID,
201 INFO_SASL_CRAM_MD5_OPTION_AUTH_ID.get(), true, false)));
202
203 m.put(toLowerCase(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME),
204 new SASLMechanismInfo(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME,
205 INFO_SASL_DIGEST_MD5_DESCRIPTION.get(), true, true,
206 new SASLOption(SASL_OPTION_AUTH_ID,
207 INFO_SASL_DIGEST_MD5_OPTION_AUTH_ID.get(), true, false),
208 new SASLOption(SASL_OPTION_AUTHZ_ID,
209 INFO_SASL_DIGEST_MD5_OPTION_AUTHZ_ID.get(), false, false),
210 new SASLOption(SASL_OPTION_REALM,
211 INFO_SASL_DIGEST_MD5_OPTION_REALM.get(), false, false),
212 new SASLOption(SASL_OPTION_QOP,
213 INFO_SASL_DIGEST_MD5_OPTION_QOP.get(), false, false)));
214
215 m.put(toLowerCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME),
216 new SASLMechanismInfo(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME,
217 INFO_SASL_EXTERNAL_DESCRIPTION.get(), false, false));
218
219 m.put(toLowerCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME),
220 new SASLMechanismInfo(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME,
221 INFO_SASL_GSSAPI_DESCRIPTION.get(), true, false,
222 new SASLOption(SASL_OPTION_AUTH_ID,
223 INFO_SASL_GSSAPI_OPTION_AUTH_ID.get(), true, false),
224 new SASLOption(SASL_OPTION_AUTHZ_ID,
225 INFO_SASL_GSSAPI_OPTION_AUTHZ_ID.get(), false, false),
226 new SASLOption(SASL_OPTION_CONFIG_FILE,
227 INFO_SASL_GSSAPI_OPTION_CONFIG_FILE.get(), false, false),
228 new SASLOption(SASL_OPTION_DEBUG,
229 INFO_SASL_GSSAPI_OPTION_DEBUG.get(), false, false),
230 new SASLOption(SASL_OPTION_KDC_ADDRESS,
231 INFO_SASL_GSSAPI_OPTION_KDC_ADDRESS.get(), false, false),
232 new SASLOption(SASL_OPTION_PROTOCOL,
233 INFO_SASL_GSSAPI_OPTION_PROTOCOL.get(), false, false),
234 new SASLOption(SASL_OPTION_REALM,
235 INFO_SASL_GSSAPI_OPTION_REALM.get(), false, false),
236 new SASLOption(SASL_OPTION_QOP,
237 INFO_SASL_GSSAPI_OPTION_QOP.get(), false, false),
238 new SASLOption(SASL_OPTION_RENEW_TGT,
239 INFO_SASL_GSSAPI_OPTION_RENEW_TGT.get(), false, false),
240 new SASLOption(SASL_OPTION_REQUIRE_CACHE,
241 INFO_SASL_GSSAPI_OPTION_REQUIRE_TICKET_CACHE.get(), false,
242 false),
243 new SASLOption(SASL_OPTION_TICKET_CACHE_PATH,
244 INFO_SASL_GSSAPI_OPTION_TICKET_CACHE.get(), false, false),
245 new SASLOption(SASL_OPTION_USE_TICKET_CACHE,
246 INFO_SASL_GSSAPI_OPTION_USE_TICKET_CACHE.get(), false,
247 false)));
248
249 m.put(toLowerCase(PLAINBindRequest.PLAIN_MECHANISM_NAME),
250 new SASLMechanismInfo(PLAINBindRequest.PLAIN_MECHANISM_NAME,
251 INFO_SASL_PLAIN_DESCRIPTION.get(), true, true,
252 new SASLOption(SASL_OPTION_AUTH_ID,
253 INFO_SASL_PLAIN_OPTION_AUTH_ID.get(), true, false),
254 new SASLOption(SASL_OPTION_AUTHZ_ID,
255 INFO_SASL_PLAIN_OPTION_AUTHZ_ID.get(), false, false)));
256
257
258 // If Commercial Edition classes are available, then register support for
259 // any additional SASL mechanisms that it provides.
260 try
261 {
262 final Class<?> c =
263 Class.forName("com.unboundid.ldap.sdk.unboundidds.SASLHelper");
264 final Method addCESASLInfoMethod =
265 c.getMethod("addCESASLInfo", Map.class);
266 addCESASLInfoMethod.invoke(null, m);
267 }
268 catch (final Exception e)
269 {
270 // This is fine. It simply means that the Commercial Edition classes
271 // are not available.
272 Debug.debugException(e);
273 }
274
275 SASL_MECHANISMS = Collections.unmodifiableMap(m);
276 }
277
278
279
280 /**
281 * Prevent this utility class from being instantiated.
282 */
283 private SASLUtils()
284 {
285 // No implementation required.
286 }
287
288
289
290 /**
291 * Retrieves information about the SASL mechanisms supported for use by this
292 * class.
293 *
294 * @return Information about the SASL mechanisms supported for use by this
295 * class.
296 */
297 public static List<SASLMechanismInfo> getSupportedSASLMechanisms()
298 {
299 return Collections.unmodifiableList(new ArrayList<SASLMechanismInfo>(
300 SASL_MECHANISMS.values()));
301 }
302
303
304
305 /**
306 * Retrieves information about the specified SASL mechanism.
307 *
308 * @param mechanism The name of the SASL mechanism for which to retrieve
309 * information. It will not be treated in a case-sensitive
310 * manner.
311 *
312 * @return Information about the requested SASL mechanism, or {@code null} if
313 * no information about the specified mechanism is available.
314 */
315 public static SASLMechanismInfo getSASLMechanismInfo(final String mechanism)
316 {
317 return SASL_MECHANISMS.get(toLowerCase(mechanism));
318 }
319
320
321
322 /**
323 * Creates a new SASL bind request using the provided information.
324 *
325 * @param bindDN The bind DN to use for the SASL bind request. For most
326 * SASL mechanisms, this should be {@code null}, since the
327 * identity of the target user should be specified in some
328 * other way (e.g., via an "authID" SASL option).
329 * @param password The password to use for the SASL bind request. It may
330 * be {@code null} if no password is required for the
331 * desired SASL mechanism.
332 * @param mechanism The name of the SASL mechanism to use. It may be
333 * {@code null} if the provided set of options contains a
334 * "mech" option to specify the desired SASL option.
335 * @param options The set of SASL options to use when creating the bind
336 * request, in the form "name=value". It may be
337 * {@code null} or empty if no SASL options are needed and
338 * a value was provided for the {@code mechanism} argument.
339 * If the set of SASL options includes a "mech" option,
340 * then the {@code mechanism} argument must be {@code null}
341 * or have a value that matches the value of the "mech"
342 * SASL option.
343 *
344 * @return The SASL bind request created using the provided information.
345 *
346 * @throws LDAPException If a problem is encountered while trying to create
347 * the SASL bind request.
348 */
349 public static SASLBindRequest createBindRequest(final String bindDN,
350 final String password,
351 final String mechanism,
352 final String... options)
353 throws LDAPException
354 {
355 return createBindRequest(bindDN,
356 (password == null ? null : getBytes(password)), mechanism,
357 StaticUtils.toList(options));
358 }
359
360
361
362 /**
363 * Creates a new SASL bind request using the provided information.
364 *
365 * @param bindDN The bind DN to use for the SASL bind request. For most
366 * SASL mechanisms, this should be {@code null}, since the
367 * identity of the target user should be specified in some
368 * other way (e.g., via an "authID" SASL option).
369 * @param password The password to use for the SASL bind request. It may
370 * be {@code null} if no password is required for the
371 * desired SASL mechanism.
372 * @param mechanism The name of the SASL mechanism to use. It may be
373 * {@code null} if the provided set of options contains a
374 * "mech" option to specify the desired SASL option.
375 * @param options The set of SASL options to use when creating the bind
376 * request, in the form "name=value". It may be
377 * {@code null} or empty if no SASL options are needed and
378 * a value was provided for the {@code mechanism} argument.
379 * If the set of SASL options includes a "mech" option,
380 * then the {@code mechanism} argument must be {@code null}
381 * or have a value that matches the value of the "mech"
382 * SASL option.
383 * @param controls The set of controls to include in the request.
384 *
385 * @return The SASL bind request created using the provided information.
386 *
387 * @throws LDAPException If a problem is encountered while trying to create
388 * the SASL bind request.
389 */
390 public static SASLBindRequest createBindRequest(final String bindDN,
391 final String password,
392 final String mechanism,
393 final List<String> options,
394 final Control... controls)
395 throws LDAPException
396 {
397 return createBindRequest(bindDN,
398 (password == null ? null : getBytes(password)), mechanism, options,
399 controls);
400 }
401
402
403
404 /**
405 * Creates a new SASL bind request using the provided information.
406 *
407 * @param bindDN The bind DN to use for the SASL bind request. For most
408 * SASL mechanisms, this should be {@code null}, since the
409 * identity of the target user should be specified in some
410 * other way (e.g., via an "authID" SASL option).
411 * @param password The password to use for the SASL bind request. It may
412 * be {@code null} if no password is required for the
413 * desired SASL mechanism.
414 * @param mechanism The name of the SASL mechanism to use. It may be
415 * {@code null} if the provided set of options contains a
416 * "mech" option to specify the desired SASL option.
417 * @param options The set of SASL options to use when creating the bind
418 * request, in the form "name=value". It may be
419 * {@code null} or empty if no SASL options are needed and
420 * a value was provided for the {@code mechanism} argument.
421 * If the set of SASL options includes a "mech" option,
422 * then the {@code mechanism} argument must be {@code null}
423 * or have a value that matches the value of the "mech"
424 * SASL option.
425 *
426 * @return The SASL bind request created using the provided information.
427 *
428 * @throws LDAPException If a problem is encountered while trying to create
429 * the SASL bind request.
430 */
431 public static SASLBindRequest createBindRequest(final String bindDN,
432 final byte[] password,
433 final String mechanism,
434 final String... options)
435 throws LDAPException
436 {
437 return createBindRequest(bindDN, password, mechanism,
438 StaticUtils.toList(options));
439 }
440
441
442
443 /**
444 * Creates a new SASL bind request using the provided information.
445 *
446 * @param bindDN The bind DN to use for the SASL bind request. For most
447 * SASL mechanisms, this should be {@code null}, since the
448 * identity of the target user should be specified in some
449 * other way (e.g., via an "authID" SASL option).
450 * @param password The password to use for the SASL bind request. It may
451 * be {@code null} if no password is required for the
452 * desired SASL mechanism.
453 * @param mechanism The name of the SASL mechanism to use. It may be
454 * {@code null} if the provided set of options contains a
455 * "mech" option to specify the desired SASL option.
456 * @param options The set of SASL options to use when creating the bind
457 * request, in the form "name=value". It may be
458 * {@code null} or empty if no SASL options are needed and
459 * a value was provided for the {@code mechanism} argument.
460 * If the set of SASL options includes a "mech" option,
461 * then the {@code mechanism} argument must be {@code null}
462 * or have a value that matches the value of the "mech"
463 * SASL option.
464 * @param controls The set of controls to include in the request.
465 *
466 * @return The SASL bind request created using the provided information.
467 *
468 * @throws LDAPException If a problem is encountered while trying to create
469 * the SASL bind request.
470 */
471 public static SASLBindRequest createBindRequest(final String bindDN,
472 final byte[] password,
473 final String mechanism,
474 final List<String> options,
475 final Control... controls)
476 throws LDAPException
477 {
478 // Parse the provided set of options to ensure that they are properly
479 // formatted in name-value form, and extract the SASL mechanism.
480 final String mech;
481 final Map<String,String> optionsMap = parseOptions(options);
482 final String mechOption =
483 optionsMap.remove(toLowerCase(SASL_OPTION_MECHANISM));
484 if (mechOption != null)
485 {
486 mech = mechOption;
487 if ((mechanism != null) && (! mech.equalsIgnoreCase(mechanism)))
488 {
489 throw new LDAPException(ResultCode.PARAM_ERROR,
490 ERR_SASL_OPTION_MECH_CONFLICT.get(mechanism, mech));
491 }
492 }
493 else
494 {
495 mech = mechanism;
496 }
497
498 if (mech == null)
499 {
500 throw new LDAPException(ResultCode.PARAM_ERROR,
501 ERR_SASL_OPTION_NO_MECH.get());
502 }
503
504 if (mech.equalsIgnoreCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME))
505 {
506 return createANONYMOUSBindRequest(password, optionsMap, controls);
507 }
508 else if (mech.equalsIgnoreCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME))
509 {
510 return createCRAMMD5BindRequest(password, optionsMap, controls);
511 }
512 else if (mech.equalsIgnoreCase(
513 DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME))
514 {
515 return createDIGESTMD5BindRequest(password, optionsMap, controls);
516 }
517 else if (mech.equalsIgnoreCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME))
518 {
519 return createEXTERNALBindRequest(password, optionsMap, controls);
520 }
521 else if (mech.equalsIgnoreCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME))
522 {
523 return createGSSAPIBindRequest(password, optionsMap, controls);
524 }
525 else if (mech.equalsIgnoreCase(PLAINBindRequest.PLAIN_MECHANISM_NAME))
526 {
527 return createPLAINBindRequest(password, optionsMap, controls);
528 }
529 else
530 {
531 // If Commercial Edition classes are available, then see if the
532 // authentication attempt is for one of the Commercial Edition mechanisms.
533 try
534 {
535 final Class<?> c =
536 Class.forName("com.unboundid.ldap.sdk.unboundidds.SASLHelper");
537 final Method createBindRequestMethod = c.getMethod("createBindRequest",
538 String.class, StaticUtils.NO_BYTES.getClass(), String.class,
539 Map.class, StaticUtils.NO_CONTROLS.getClass());
540 final Object bindRequestObject = createBindRequestMethod.invoke(null,
541 bindDN, password, mech, optionsMap, controls);
542 if (bindRequestObject != null)
543 {
544 return (SASLBindRequest) bindRequestObject;
545 }
546 }
547 catch (final Exception e)
548 {
549 Debug.debugException(e);
550
551 // This may mean that there was a problem with the provided arguments.
552 // If it's an InvocationTargetException that wraps an LDAPException,
553 // then throw that LDAPException.
554 if (e instanceof InvocationTargetException)
555 {
556 final InvocationTargetException ite = (InvocationTargetException) e;
557 final Throwable t = ite.getTargetException();
558 if (t instanceof LDAPException)
559 {
560 throw (LDAPException) t;
561 }
562 }
563 }
564
565 throw new LDAPException(ResultCode.PARAM_ERROR,
566 ERR_SASL_OPTION_UNSUPPORTED_MECH.get(mech));
567 }
568 }
569
570
571
572 /**
573 * Creates a SASL ANONYMOUS bind request using the provided set of options.
574 *
575 * @param password The password to use for the bind request.
576 * @param options The set of SASL options for the bind request.
577 * @param controls The set of controls to include in the request.
578 *
579 * @return The SASL ANONYMOUS bind request that was created.
580 *
581 * @throws LDAPException If a problem is encountered while trying to create
582 * the SASL bind request.
583 */
584 private static ANONYMOUSBindRequest createANONYMOUSBindRequest(
585 final byte[] password,
586 final Map<String,String> options,
587 final Control[] controls)
588 throws LDAPException
589 {
590 if (password != null)
591 {
592 throw new LDAPException(ResultCode.PARAM_ERROR,
593 ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
594 ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME));
595 }
596
597
598 // The trace option is optional.
599 final String trace = options.remove(toLowerCase(SASL_OPTION_TRACE));
600
601 // Ensure no unsupported options were provided.
602 ensureNoUnsupportedOptions(options,
603 ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME);
604
605 return new ANONYMOUSBindRequest(trace, controls);
606 }
607
608
609
610 /**
611 * Creates a SASL CRAM-MD5 bind request using the provided password and set of
612 * options.
613 *
614 * @param password The password to use for the bind request.
615 * @param options The set of SASL options for the bind request.
616 * @param controls The set of controls to include in the request.
617 *
618 * @return The SASL CRAM-MD5 bind request that was created.
619 *
620 * @throws LDAPException If a problem is encountered while trying to create
621 * the SASL bind request.
622 */
623 private static CRAMMD5BindRequest createCRAMMD5BindRequest(
624 final byte[] password,
625 final Map<String,String> options,
626 final Control[] controls)
627 throws LDAPException
628 {
629 if (password == null)
630 {
631 throw new LDAPException(ResultCode.PARAM_ERROR,
632 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
633 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
634 }
635
636
637 // The authID option is required.
638 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID));
639 if (authID == null)
640 {
641 throw new LDAPException(ResultCode.PARAM_ERROR,
642 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
643 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
644 }
645
646
647 // Ensure no unsupported options were provided.
648 ensureNoUnsupportedOptions(options,
649 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME);
650
651 return new CRAMMD5BindRequest(authID, password, controls);
652 }
653
654
655
656 /**
657 * Creates a SASL DIGEST-MD5 bind request using the provided password and set
658 * of options.
659 *
660 * @param password The password to use for the bind request.
661 * @param options The set of SASL options for the bind request.
662 * @param controls The set of controls to include in the request.
663 *
664 * @return The SASL DIGEST-MD5 bind request that was created.
665 *
666 * @throws LDAPException If a problem is encountered while trying to create
667 * the SASL bind request.
668 */
669 private static DIGESTMD5BindRequest createDIGESTMD5BindRequest(
670 final byte[] password,
671 final Map<String,String> options,
672 final Control[] controls)
673 throws LDAPException
674 {
675 if (password == null)
676 {
677 throw new LDAPException(ResultCode.PARAM_ERROR,
678 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
679 DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME));
680 }
681
682 // The authID option is required.
683 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID));
684 if (authID == null)
685 {
686 throw new LDAPException(ResultCode.PARAM_ERROR,
687 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
688 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
689 }
690
691 final DIGESTMD5BindRequestProperties properties =
692 new DIGESTMD5BindRequestProperties(authID, password);
693
694 // The authzID option is optional.
695 properties.setAuthorizationID(
696 options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID)));
697
698 // The realm option is optional.
699 properties.setRealm(options.remove(toLowerCase(SASL_OPTION_REALM)));
700
701 // The QoP option is optional, and may contain multiple values that need to
702 // be parsed.
703 final String qopString = options.remove(toLowerCase(SASL_OPTION_QOP));
704 if (qopString != null)
705 {
706 properties.setAllowedQoP(
707 SASLQualityOfProtection.decodeQoPList(qopString));
708 }
709
710 // Ensure no unsupported options were provided.
711 ensureNoUnsupportedOptions(options,
712 DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME);
713
714 return new DIGESTMD5BindRequest(properties, controls);
715 }
716
717
718
719 /**
720 * Creates a SASL EXTERNAL bind request using the provided set of options.
721 *
722 * @param password The password to use for the bind request.
723 * @param options The set of SASL options for the bind request.
724 * @param controls The set of controls to include in the request.
725 *
726 * @return The SASL EXTERNAL bind request that was created.
727 *
728 * @throws LDAPException If a problem is encountered while trying to create
729 * the SASL bind request.
730 */
731 private static EXTERNALBindRequest createEXTERNALBindRequest(
732 final byte[] password,
733 final Map<String,String> options,
734 final Control[] controls)
735 throws LDAPException
736 {
737 if (password != null)
738 {
739 throw new LDAPException(ResultCode.PARAM_ERROR,
740 ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
741 EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME));
742 }
743
744 // Ensure no unsupported options were provided.
745 ensureNoUnsupportedOptions(options,
746 EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME);
747
748 return new EXTERNALBindRequest(controls);
749 }
750
751
752
753 /**
754 * Creates a SASL GSSAPI bind request using the provided password and set of
755 * options.
756 *
757 * @param password The password to use for the bind request.
758 * @param options The set of SASL options for the bind request.
759 * @param controls The set of controls to include in the request.
760 *
761 * @return The SASL GSSAPI bind request that was created.
762 *
763 * @throws LDAPException If a problem is encountered while trying to create
764 * the SASL bind request.
765 */
766 private static GSSAPIBindRequest createGSSAPIBindRequest(
767 final byte[] password,
768 final Map<String,String> options,
769 final Control[] controls)
770 throws LDAPException
771 {
772 // The authID option is required.
773 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID));
774 if (authID == null)
775 {
776 throw new LDAPException(ResultCode.PARAM_ERROR,
777 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
778 GSSAPIBindRequest.GSSAPI_MECHANISM_NAME));
779 }
780 final GSSAPIBindRequestProperties gssapiProperties =
781 new GSSAPIBindRequestProperties(authID, password);
782
783 // The authzID option is optional.
784 gssapiProperties.setAuthorizationID(
785 options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID)));
786
787 // The configFile option is optional.
788 gssapiProperties.setConfigFilePath(options.remove(toLowerCase(
789 SASL_OPTION_CONFIG_FILE)));
790
791 // The debug option is optional.
792 gssapiProperties.setEnableGSSAPIDebugging(getBooleanValue(options,
793 SASL_OPTION_DEBUG, false));
794
795 // The kdcAddress option is optional.
796 gssapiProperties.setKDCAddress(options.remove(
797 toLowerCase(SASL_OPTION_KDC_ADDRESS)));
798
799 // The protocol option is optional.
800 final String protocol = options.remove(toLowerCase(SASL_OPTION_PROTOCOL));
801 if (protocol != null)
802 {
803 gssapiProperties.setServicePrincipalProtocol(protocol);
804 }
805
806 // The realm option is optional.
807 gssapiProperties.setRealm(options.remove(toLowerCase(SASL_OPTION_REALM)));
808
809 // The QoP option is optional, and may contain multiple values that need to
810 // be parsed.
811 final String qopString = options.remove(toLowerCase(SASL_OPTION_QOP));
812 if (qopString != null)
813 {
814 gssapiProperties.setAllowedQoP(
815 SASLQualityOfProtection.decodeQoPList(qopString));
816 }
817
818 // The renewTGT option is optional.
819 gssapiProperties.setRenewTGT(getBooleanValue(options, SASL_OPTION_RENEW_TGT,
820 false));
821
822 // The requireCache option is optional.
823 gssapiProperties.setRequireCachedCredentials(getBooleanValue(options,
824 SASL_OPTION_REQUIRE_CACHE, false));
825
826 // The ticketCache option is optional.
827 gssapiProperties.setTicketCachePath(options.remove(toLowerCase(
828 SASL_OPTION_TICKET_CACHE_PATH)));
829
830 // The useTicketCache option is optional.
831 gssapiProperties.setUseTicketCache(getBooleanValue(options,
832 SASL_OPTION_USE_TICKET_CACHE, true));
833
834 // Ensure no unsupported options were provided.
835 ensureNoUnsupportedOptions(options,
836 GSSAPIBindRequest.GSSAPI_MECHANISM_NAME);
837
838 // A password must have been provided unless useTicketCache=true and
839 // requireTicketCache=true.
840 if (password == null)
841 {
842 if (! (gssapiProperties.useTicketCache() &&
843 gssapiProperties.requireCachedCredentials()))
844 {
845 throw new LDAPException(ResultCode.PARAM_ERROR,
846 ERR_SASL_OPTION_GSSAPI_PASSWORD_REQUIRED.get());
847 }
848 }
849
850 return new GSSAPIBindRequest(gssapiProperties, controls);
851 }
852
853
854
855 /**
856 * Creates a SASL PLAIN bind request using the provided password and set of
857 * options.
858 *
859 * @param password The password to use for the bind request.
860 * @param options The set of SASL options for the bind request.
861 * @param controls The set of controls to include in the request.
862 *
863 * @return The SASL PLAIN bind request that was created.
864 *
865 * @throws LDAPException If a problem is encountered while trying to create
866 * the SASL bind request.
867 */
868 private static PLAINBindRequest createPLAINBindRequest(
869 final byte[] password,
870 final Map<String,String> options,
871 final Control[] controls)
872 throws LDAPException
873 {
874 if (password == null)
875 {
876 throw new LDAPException(ResultCode.PARAM_ERROR,
877 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
878 PLAINBindRequest.PLAIN_MECHANISM_NAME));
879 }
880
881 // The authID option is required.
882 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID));
883 if (authID == null)
884 {
885 throw new LDAPException(ResultCode.PARAM_ERROR,
886 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
887 PLAINBindRequest.PLAIN_MECHANISM_NAME));
888 }
889
890 // The authzID option is optional.
891 final String authzID = options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID));
892
893 // Ensure no unsupported options were provided.
894 ensureNoUnsupportedOptions(options,
895 PLAINBindRequest.PLAIN_MECHANISM_NAME);
896
897 return new PLAINBindRequest(authID, authzID, password, controls);
898 }
899
900
901
902 /**
903 * Parses the provided list of SASL options.
904 *
905 * @param options The list of options to be parsed.
906 *
907 * @return A map with the parsed set of options.
908 *
909 * @throws LDAPException If a problem is encountered while parsing options.
910 */
911 private static Map<String,String>
912 parseOptions(final List<String> options)
913 throws LDAPException
914 {
915 if (options == null)
916 {
917 return new HashMap<String,String>(0);
918 }
919
920 final HashMap<String,String> m = new HashMap<String,String>(options.size());
921 for (final String s : options)
922 {
923 final int equalPos = s.indexOf('=');
924 if (equalPos < 0)
925 {
926 throw new LDAPException(ResultCode.PARAM_ERROR,
927 ERR_SASL_OPTION_MISSING_EQUAL.get(s));
928 }
929 else if (equalPos == 0)
930 {
931 throw new LDAPException(ResultCode.PARAM_ERROR,
932 ERR_SASL_OPTION_STARTS_WITH_EQUAL.get(s));
933 }
934
935 final String name = s.substring(0, equalPos);
936 final String value = s.substring(equalPos + 1);
937 if (m.put(toLowerCase(name), value) != null)
938 {
939 throw new LDAPException(ResultCode.PARAM_ERROR,
940 ERR_SASL_OPTION_NOT_MULTI_VALUED.get(name));
941 }
942 }
943
944 return m;
945 }
946
947
948
949 /**
950 * Ensures that the provided map is empty, and will throw an exception if it
951 * isn't. This method is intended for internal use only.
952 *
953 * @param options The map of options to ensure is empty.
954 * @param mechanism The associated SASL mechanism.
955 *
956 * @throws LDAPException If the map of SASL options is not empty.
957 */
958 @InternalUseOnly()
959 public static void ensureNoUnsupportedOptions(
960 final Map<String,String> options,
961 final String mechanism)
962 throws LDAPException
963 {
964 if (! options.isEmpty())
965 {
966 for (final String s : options.keySet())
967 {
968 throw new LDAPException(ResultCode.PARAM_ERROR,
969 ERR_SASL_OPTION_UNSUPPORTED_FOR_MECH.get(s,mechanism));
970 }
971 }
972 }
973
974
975
976 /**
977 * Retrieves the value of the specified option and parses it as a boolean.
978 * Values of "true", "t", "yes", "y", "on", and "1" will be treated as
979 * {@code true}. Values of "false", "f", "no", "n", "off", and "0" will be
980 * treated as {@code false}.
981 *
982 * @param m The map from which to retrieve the option. It must not be
983 * {@code null}.
984 * @param o The name of the option to examine.
985 * @param d The default value to use if the given option was not provided.
986 *
987 * @return The parsed boolean value.
988 *
989 * @throws LDAPException If the option value cannot be parsed as a boolean.
990 */
991 static boolean getBooleanValue(final Map<String,String> m, final String o,
992 final boolean d)
993 throws LDAPException
994 {
995 final String s = toLowerCase(m.remove(toLowerCase(o)));
996 if (s == null)
997 {
998 return d;
999 }
1000 else if (s.equals("true") ||
1001 s.equals("t") ||
1002 s.equals("yes") ||
1003 s.equals("y") ||
1004 s.equals("on") ||
1005 s.equals("1"))
1006 {
1007 return true;
1008 }
1009 else if (s.equals("false") ||
1010 s.equals("f") ||
1011 s.equals("no") ||
1012 s.equals("n") ||
1013 s.equals("off") ||
1014 s.equals("0"))
1015 {
1016 return false;
1017 }
1018 else
1019 {
1020 throw new LDAPException(ResultCode.PARAM_ERROR,
1021 ERR_SASL_OPTION_MALFORMED_BOOLEAN_VALUE.get(o));
1022 }
1023 }
1024 }