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