001 /*
002 * Copyright 2011-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2011-2016 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 return createBindRequest(bindDN, password, false, null, mechanism, options,
479 controls);
480 }
481
482
483
484 /**
485 * Creates a new SASL bind request using the provided information.
486 *
487 * @param bindDN The bind DN to use for the SASL bind request.
488 * For most SASL mechanisms, this should be
489 * {@code null}, since the identity of the target
490 * user should be specified in some other way
491 * (e.g., via an "authID" SASL option).
492 * @param password The password to use for the SASL bind request.
493 * It may be {@code null} if no password is
494 * required for the desired SASL mechanism.
495 * @param promptForPassword Indicates whether to interactively prompt for
496 * the password if one is needed but none was
497 * provided.
498 * @param tool The command-line tool whose input and output
499 * streams should be used when prompting for the
500 * bind password. It may be {@code null} if
501 * {@code promptForPassword} is {@code false}.
502 * @param mechanism The name of the SASL mechanism to use. It may
503 * be {@code null} if the provided set of options
504 * contains a "mech" option to specify the desired
505 * SASL option.
506 * @param options The set of SASL options to use when creating the
507 * bind request, in the form "name=value". It may
508 * be {@code null} or empty if no SASL options are
509 * needed and a value was provided for the
510 * {@code mechanism} argument. If the set of SASL
511 * options includes a "mech" option, then the
512 * {@code mechanism} argument must be {@code null}
513 * or have a value that matches the value of the
514 * "mech" SASL option.
515 * @param controls The set of controls to include in the request.
516 *
517 * @return The SASL bind request created using the provided information.
518 *
519 * @throws LDAPException If a problem is encountered while trying to create
520 * the SASL bind request.
521 */
522 public static SASLBindRequest createBindRequest(final String bindDN,
523 final byte[] password,
524 final boolean promptForPassword,
525 final CommandLineTool tool,
526 final String mechanism,
527 final List<String> options,
528 final Control... controls)
529 throws LDAPException
530 {
531 if (promptForPassword)
532 {
533 Validator.ensureNotNull(tool);
534 }
535
536 // Parse the provided set of options to ensure that they are properly
537 // formatted in name-value form, and extract the SASL mechanism.
538 final String mech;
539 final Map<String,String> optionsMap = parseOptions(options);
540 final String mechOption =
541 optionsMap.remove(toLowerCase(SASL_OPTION_MECHANISM));
542 if (mechOption != null)
543 {
544 mech = mechOption;
545 if ((mechanism != null) && (! mech.equalsIgnoreCase(mechanism)))
546 {
547 throw new LDAPException(ResultCode.PARAM_ERROR,
548 ERR_SASL_OPTION_MECH_CONFLICT.get(mechanism, mech));
549 }
550 }
551 else
552 {
553 mech = mechanism;
554 }
555
556 if (mech == null)
557 {
558 throw new LDAPException(ResultCode.PARAM_ERROR,
559 ERR_SASL_OPTION_NO_MECH.get());
560 }
561
562 if (mech.equalsIgnoreCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME))
563 {
564 return createANONYMOUSBindRequest(password, optionsMap, controls);
565 }
566 else if (mech.equalsIgnoreCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME))
567 {
568 return createCRAMMD5BindRequest(password, promptForPassword, tool,
569 optionsMap, controls);
570 }
571 else if (mech.equalsIgnoreCase(
572 DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME))
573 {
574 return createDIGESTMD5BindRequest(password, promptForPassword, tool,
575 optionsMap, controls);
576 }
577 else if (mech.equalsIgnoreCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME))
578 {
579 return createEXTERNALBindRequest(password, optionsMap, controls);
580 }
581 else if (mech.equalsIgnoreCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME))
582 {
583 return createGSSAPIBindRequest(password, promptForPassword, tool,
584 optionsMap, controls);
585 }
586 else if (mech.equalsIgnoreCase(PLAINBindRequest.PLAIN_MECHANISM_NAME))
587 {
588 return createPLAINBindRequest(password, promptForPassword, tool,
589 optionsMap, controls);
590 }
591 else
592 {
593 // If Commercial Edition classes are available, then see if the
594 // authentication attempt is for one of the Commercial Edition mechanisms.
595 try
596 {
597 final Class<?> c =
598 Class.forName("com.unboundid.ldap.sdk.unboundidds.SASLHelper");
599 final Method createBindRequestMethod = c.getMethod("createBindRequest",
600 String.class, StaticUtils.NO_BYTES.getClass(), String.class,
601 CommandLineTool.class, Map.class,
602 StaticUtils.NO_CONTROLS.getClass());
603 final Object bindRequestObject = createBindRequestMethod.invoke(null,
604 bindDN, password, mech, tool, optionsMap, controls);
605 if (bindRequestObject != null)
606 {
607 return (SASLBindRequest) bindRequestObject;
608 }
609 }
610 catch (final Exception e)
611 {
612 Debug.debugException(e);
613
614 // This may mean that there was a problem with the provided arguments.
615 // If it's an InvocationTargetException that wraps an LDAPException,
616 // then throw that LDAPException.
617 if (e instanceof InvocationTargetException)
618 {
619 final InvocationTargetException ite = (InvocationTargetException) e;
620 final Throwable t = ite.getTargetException();
621 if (t instanceof LDAPException)
622 {
623 throw (LDAPException) t;
624 }
625 }
626 }
627
628 throw new LDAPException(ResultCode.PARAM_ERROR,
629 ERR_SASL_OPTION_UNSUPPORTED_MECH.get(mech));
630 }
631 }
632
633
634
635 /**
636 * Creates a SASL ANONYMOUS bind request using the provided set of options.
637 *
638 * @param password The password to use for the bind request.
639 * @param options The set of SASL options for the bind request.
640 * @param controls The set of controls to include in the request.
641 *
642 * @return The SASL ANONYMOUS bind request that was created.
643 *
644 * @throws LDAPException If a problem is encountered while trying to create
645 * the SASL bind request.
646 */
647 private static ANONYMOUSBindRequest createANONYMOUSBindRequest(
648 final byte[] password,
649 final Map<String,String> options,
650 final Control[] controls)
651 throws LDAPException
652 {
653 if (password != null)
654 {
655 throw new LDAPException(ResultCode.PARAM_ERROR,
656 ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
657 ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME));
658 }
659
660
661 // The trace option is optional.
662 final String trace = options.remove(toLowerCase(SASL_OPTION_TRACE));
663
664 // Ensure no unsupported options were provided.
665 ensureNoUnsupportedOptions(options,
666 ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME);
667
668 return new ANONYMOUSBindRequest(trace, controls);
669 }
670
671
672
673 /**
674 * Creates a SASL CRAM-MD5 bind request using the provided password and set of
675 * options.
676 *
677 * @param password The password to use for the bind request.
678 * @param promptForPassword Indicates whether to interactively prompt for
679 * the password if one is needed but none was
680 * provided.
681 * @param tool The command-line tool whose input and output
682 * streams should be used when prompting for the
683 * bind password. It may be {@code null} if
684 * {@code promptForPassword} is {@code false}.
685 * @param options The set of SASL options for the bind request.
686 * @param controls The set of controls to include in the request.
687 *
688 * @return The SASL CRAM-MD5 bind request that was created.
689 *
690 * @throws LDAPException If a problem is encountered while trying to create
691 * the SASL bind request.
692 */
693 private static CRAMMD5BindRequest createCRAMMD5BindRequest(
694 final byte[] password,
695 final boolean promptForPassword,
696 final CommandLineTool tool,
697 final Map<String,String> options,
698 final Control[] controls)
699 throws LDAPException
700 {
701 final byte[] pw;
702 if (password == null)
703 {
704 if (promptForPassword)
705 {
706 tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
707 pw = PasswordReader.readPassword();
708 tool.getOriginalOut().println();
709 }
710 else
711 {
712 throw new LDAPException(ResultCode.PARAM_ERROR,
713 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
714 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
715 }
716 }
717 else
718 {
719 pw = password;
720 }
721
722
723 // The authID option is required.
724 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID));
725 if (authID == null)
726 {
727 throw new LDAPException(ResultCode.PARAM_ERROR,
728 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
729 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
730 }
731
732
733 // Ensure no unsupported options were provided.
734 ensureNoUnsupportedOptions(options,
735 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME);
736
737 return new CRAMMD5BindRequest(authID, pw, controls);
738 }
739
740
741
742 /**
743 * Creates a SASL DIGEST-MD5 bind request using the provided password and set
744 * of options.
745 *
746 * @param password The password to use for the bind request.
747 * @param promptForPassword Indicates whether to interactively prompt for
748 * the password if one is needed but none was
749 * provided.
750 * @param tool The command-line tool whose input and output
751 * streams should be used when prompting for the
752 * bind password. It may be {@code null} if
753 * {@code promptForPassword} is {@code false}.
754 * @param options The set of SASL options for the bind request.
755 * @param controls The set of controls to include in the request.
756 *
757 * @return The SASL DIGEST-MD5 bind request that was created.
758 *
759 * @throws LDAPException If a problem is encountered while trying to create
760 * the SASL bind request.
761 */
762 private static DIGESTMD5BindRequest createDIGESTMD5BindRequest(
763 final byte[] password,
764 final boolean promptForPassword,
765 final CommandLineTool tool,
766 final Map<String,String> options,
767 final Control[] controls)
768 throws LDAPException
769 {
770 final byte[] pw;
771 if (password == null)
772 {
773 if (promptForPassword)
774 {
775 tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
776 pw = PasswordReader.readPassword();
777 tool.getOriginalOut().println();
778 }
779 else
780 {
781 throw new LDAPException(ResultCode.PARAM_ERROR,
782 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
783 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
784 }
785 }
786 else
787 {
788 pw = password;
789 }
790
791 // The authID option is required.
792 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID));
793 if (authID == null)
794 {
795 throw new LDAPException(ResultCode.PARAM_ERROR,
796 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
797 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
798 }
799
800 final DIGESTMD5BindRequestProperties properties =
801 new DIGESTMD5BindRequestProperties(authID, pw);
802
803 // The authzID option is optional.
804 properties.setAuthorizationID(
805 options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID)));
806
807 // The realm option is optional.
808 properties.setRealm(options.remove(toLowerCase(SASL_OPTION_REALM)));
809
810 // The QoP option is optional, and may contain multiple values that need to
811 // be parsed.
812 final String qopString = options.remove(toLowerCase(SASL_OPTION_QOP));
813 if (qopString != null)
814 {
815 properties.setAllowedQoP(
816 SASLQualityOfProtection.decodeQoPList(qopString));
817 }
818
819 // Ensure no unsupported options were provided.
820 ensureNoUnsupportedOptions(options,
821 DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME);
822
823 return new DIGESTMD5BindRequest(properties, controls);
824 }
825
826
827
828 /**
829 * Creates a SASL EXTERNAL bind request using the provided set of options.
830 *
831 * @param password The password to use for the bind request.
832 * @param options The set of SASL options for the bind request.
833 * @param controls The set of controls to include in the request.
834 *
835 * @return The SASL EXTERNAL bind request that was created.
836 *
837 * @throws LDAPException If a problem is encountered while trying to create
838 * the SASL bind request.
839 */
840 private static EXTERNALBindRequest createEXTERNALBindRequest(
841 final byte[] password,
842 final Map<String,String> options,
843 final Control[] controls)
844 throws LDAPException
845 {
846 if (password != null)
847 {
848 throw new LDAPException(ResultCode.PARAM_ERROR,
849 ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get(
850 EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME));
851 }
852
853 // Ensure no unsupported options were provided.
854 ensureNoUnsupportedOptions(options,
855 EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME);
856
857 return new EXTERNALBindRequest(controls);
858 }
859
860
861
862 /**
863 * Creates a SASL GSSAPI bind request using the provided password and set of
864 * options.
865 *
866 * @param password The password to use for the bind request.
867 * @param promptForPassword Indicates whether to interactively prompt for
868 * the password if one is needed but none was
869 * provided.
870 * @param tool The command-line tool whose input and output
871 * streams should be used when prompting for the
872 * bind password. It may be {@code null} if
873 * {@code promptForPassword} is {@code false}.
874 * @param options The set of SASL options for the bind request.
875 * @param controls The set of controls to include in the request.
876 *
877 * @return The SASL GSSAPI bind request that was created.
878 *
879 * @throws LDAPException If a problem is encountered while trying to create
880 * the SASL bind request.
881 */
882 private static GSSAPIBindRequest createGSSAPIBindRequest(
883 final byte[] password,
884 final boolean promptForPassword,
885 final CommandLineTool tool,
886 final Map<String,String> options,
887 final Control[] controls)
888 throws LDAPException
889 {
890 // The authID option is required.
891 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID));
892 if (authID == null)
893 {
894 throw new LDAPException(ResultCode.PARAM_ERROR,
895 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
896 GSSAPIBindRequest.GSSAPI_MECHANISM_NAME));
897 }
898 final GSSAPIBindRequestProperties gssapiProperties =
899 new GSSAPIBindRequestProperties(authID, password);
900
901 // The authzID option is optional.
902 gssapiProperties.setAuthorizationID(
903 options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID)));
904
905 // The configFile option is optional.
906 gssapiProperties.setConfigFilePath(options.remove(toLowerCase(
907 SASL_OPTION_CONFIG_FILE)));
908
909 // The debug option is optional.
910 gssapiProperties.setEnableGSSAPIDebugging(getBooleanValue(options,
911 SASL_OPTION_DEBUG, false));
912
913 // The kdcAddress option is optional.
914 gssapiProperties.setKDCAddress(options.remove(
915 toLowerCase(SASL_OPTION_KDC_ADDRESS)));
916
917 // The protocol option is optional.
918 final String protocol = options.remove(toLowerCase(SASL_OPTION_PROTOCOL));
919 if (protocol != null)
920 {
921 gssapiProperties.setServicePrincipalProtocol(protocol);
922 }
923
924 // The realm option is optional.
925 gssapiProperties.setRealm(options.remove(toLowerCase(SASL_OPTION_REALM)));
926
927 // The QoP option is optional, and may contain multiple values that need to
928 // be parsed.
929 final String qopString = options.remove(toLowerCase(SASL_OPTION_QOP));
930 if (qopString != null)
931 {
932 gssapiProperties.setAllowedQoP(
933 SASLQualityOfProtection.decodeQoPList(qopString));
934 }
935
936 // The renewTGT option is optional.
937 gssapiProperties.setRenewTGT(getBooleanValue(options, SASL_OPTION_RENEW_TGT,
938 false));
939
940 // The requireCache option is optional.
941 gssapiProperties.setRequireCachedCredentials(getBooleanValue(options,
942 SASL_OPTION_REQUIRE_CACHE, false));
943
944 // The ticketCache option is optional.
945 gssapiProperties.setTicketCachePath(options.remove(toLowerCase(
946 SASL_OPTION_TICKET_CACHE_PATH)));
947
948 // The useTicketCache option is optional.
949 gssapiProperties.setUseTicketCache(getBooleanValue(options,
950 SASL_OPTION_USE_TICKET_CACHE, true));
951
952 // Ensure no unsupported options were provided.
953 ensureNoUnsupportedOptions(options,
954 GSSAPIBindRequest.GSSAPI_MECHANISM_NAME);
955
956 // A password must have been provided unless useTicketCache=true and
957 // requireTicketCache=true.
958 if (password == null)
959 {
960 if (! (gssapiProperties.useTicketCache() &&
961 gssapiProperties.requireCachedCredentials()))
962 {
963 if (promptForPassword)
964 {
965 tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
966 gssapiProperties.setPassword(PasswordReader.readPassword());
967 tool.getOriginalOut().println();
968 }
969 else
970 {
971 throw new LDAPException(ResultCode.PARAM_ERROR,
972 ERR_SASL_OPTION_GSSAPI_PASSWORD_REQUIRED.get());
973 }
974 }
975 }
976
977 return new GSSAPIBindRequest(gssapiProperties, controls);
978 }
979
980
981
982 /**
983 * Creates a SASL PLAIN bind request using the provided password and set of
984 * options.
985 *
986 * @param password The password to use for the bind request.
987 * @param promptForPassword Indicates whether to interactively prompt for
988 * the password if one is needed but none was
989 * provided.
990 * @param tool The command-line tool whose input and output
991 * streams should be used when prompting for the
992 * bind password. It may be {@code null} if
993 * {@code promptForPassword} is {@code false}.
994 * @param options The set of SASL options for the bind request.
995 * @param controls The set of controls to include in the request.
996 *
997 * @return The SASL PLAIN bind request that was created.
998 *
999 * @throws LDAPException If a problem is encountered while trying to create
1000 * the SASL bind request.
1001 */
1002 private static PLAINBindRequest createPLAINBindRequest(
1003 final byte[] password,
1004 final boolean promptForPassword,
1005 final CommandLineTool tool,
1006 final Map<String,String> options,
1007 final Control[] controls)
1008 throws LDAPException
1009 {
1010 final byte[] pw;
1011 if (password == null)
1012 {
1013 if (promptForPassword)
1014 {
1015 tool.getOriginalOut().print(INFO_LDAP_TOOL_ENTER_BIND_PASSWORD.get());
1016 pw = PasswordReader.readPassword();
1017 tool.getOriginalOut().println();
1018 }
1019 else
1020 {
1021 throw new LDAPException(ResultCode.PARAM_ERROR,
1022 ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get(
1023 CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME));
1024 }
1025 }
1026 else
1027 {
1028 pw = password;
1029 }
1030
1031 // The authID option is required.
1032 final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID));
1033 if (authID == null)
1034 {
1035 throw new LDAPException(ResultCode.PARAM_ERROR,
1036 ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID,
1037 PLAINBindRequest.PLAIN_MECHANISM_NAME));
1038 }
1039
1040 // The authzID option is optional.
1041 final String authzID = options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID));
1042
1043 // Ensure no unsupported options were provided.
1044 ensureNoUnsupportedOptions(options,
1045 PLAINBindRequest.PLAIN_MECHANISM_NAME);
1046
1047 return new PLAINBindRequest(authID, authzID, pw, controls);
1048 }
1049
1050
1051
1052 /**
1053 * Parses the provided list of SASL options.
1054 *
1055 * @param options The list of options to be parsed.
1056 *
1057 * @return A map with the parsed set of options.
1058 *
1059 * @throws LDAPException If a problem is encountered while parsing options.
1060 */
1061 private static Map<String,String>
1062 parseOptions(final List<String> options)
1063 throws LDAPException
1064 {
1065 if (options == null)
1066 {
1067 return new HashMap<String,String>(0);
1068 }
1069
1070 final HashMap<String,String> m = new HashMap<String,String>(options.size());
1071 for (final String s : options)
1072 {
1073 final int equalPos = s.indexOf('=');
1074 if (equalPos < 0)
1075 {
1076 throw new LDAPException(ResultCode.PARAM_ERROR,
1077 ERR_SASL_OPTION_MISSING_EQUAL.get(s));
1078 }
1079 else if (equalPos == 0)
1080 {
1081 throw new LDAPException(ResultCode.PARAM_ERROR,
1082 ERR_SASL_OPTION_STARTS_WITH_EQUAL.get(s));
1083 }
1084
1085 final String name = s.substring(0, equalPos);
1086 final String value = s.substring(equalPos + 1);
1087 if (m.put(toLowerCase(name), value) != null)
1088 {
1089 throw new LDAPException(ResultCode.PARAM_ERROR,
1090 ERR_SASL_OPTION_NOT_MULTI_VALUED.get(name));
1091 }
1092 }
1093
1094 return m;
1095 }
1096
1097
1098
1099 /**
1100 * Ensures that the provided map is empty, and will throw an exception if it
1101 * isn't. This method is intended for internal use only.
1102 *
1103 * @param options The map of options to ensure is empty.
1104 * @param mechanism The associated SASL mechanism.
1105 *
1106 * @throws LDAPException If the map of SASL options is not empty.
1107 */
1108 @InternalUseOnly()
1109 public static void ensureNoUnsupportedOptions(
1110 final Map<String,String> options,
1111 final String mechanism)
1112 throws LDAPException
1113 {
1114 if (! options.isEmpty())
1115 {
1116 for (final String s : options.keySet())
1117 {
1118 throw new LDAPException(ResultCode.PARAM_ERROR,
1119 ERR_SASL_OPTION_UNSUPPORTED_FOR_MECH.get(s,mechanism));
1120 }
1121 }
1122 }
1123
1124
1125
1126 /**
1127 * Retrieves the value of the specified option and parses it as a boolean.
1128 * Values of "true", "t", "yes", "y", "on", and "1" will be treated as
1129 * {@code true}. Values of "false", "f", "no", "n", "off", and "0" will be
1130 * treated as {@code false}.
1131 *
1132 * @param m The map from which to retrieve the option. It must not be
1133 * {@code null}.
1134 * @param o The name of the option to examine.
1135 * @param d The default value to use if the given option was not provided.
1136 *
1137 * @return The parsed boolean value.
1138 *
1139 * @throws LDAPException If the option value cannot be parsed as a boolean.
1140 */
1141 static boolean getBooleanValue(final Map<String,String> m, final String o,
1142 final boolean d)
1143 throws LDAPException
1144 {
1145 final String s = toLowerCase(m.remove(toLowerCase(o)));
1146 if (s == null)
1147 {
1148 return d;
1149 }
1150 else if (s.equals("true") ||
1151 s.equals("t") ||
1152 s.equals("yes") ||
1153 s.equals("y") ||
1154 s.equals("on") ||
1155 s.equals("1"))
1156 {
1157 return true;
1158 }
1159 else if (s.equals("false") ||
1160 s.equals("f") ||
1161 s.equals("no") ||
1162 s.equals("n") ||
1163 s.equals("off") ||
1164 s.equals("0"))
1165 {
1166 return false;
1167 }
1168 else
1169 {
1170 throw new LDAPException(ResultCode.PARAM_ERROR,
1171 ERR_SASL_OPTION_MALFORMED_BOOLEAN_VALUE.get(o));
1172 }
1173 }
1174
1175
1176
1177 /**
1178 * Retrieves a string representation of the SASL usage information. This will
1179 * include the supported SASL mechanisms and the properties that may be used
1180 * with each.
1181 *
1182 * @param maxWidth The maximum line width to use for the output. If this is
1183 * less than or equal to zero, then no wrapping will be
1184 * performed.
1185 *
1186 * @return A string representation of the usage information
1187 */
1188 public static String getUsageString(final int maxWidth)
1189 {
1190 final StringBuilder buffer = new StringBuilder();
1191
1192 for (final String line : getUsage(maxWidth))
1193 {
1194 buffer.append(line);
1195 buffer.append(EOL);
1196 }
1197
1198 return buffer.toString();
1199 }
1200
1201
1202
1203 /**
1204 * Retrieves lines that make up the SASL usage information, optionally
1205 * wrapping long lines.
1206 *
1207 * @param maxWidth The maximum line width to use for the output. If this is
1208 * less than or equal to zero, then no wrapping will be
1209 * performed.
1210 *
1211 * @return The lines that make up the SASL usage information.
1212 */
1213 public static List<String> getUsage(final int maxWidth)
1214 {
1215 final ArrayList<String> lines = new ArrayList<String>(100);
1216
1217 boolean first = true;
1218 for (final SASLMechanismInfo i : getSupportedSASLMechanisms())
1219 {
1220 if (first)
1221 {
1222 first = false;
1223 }
1224 else
1225 {
1226 lines.add("");
1227 lines.add("");
1228 }
1229
1230 lines.addAll(
1231 wrapLine(INFO_SASL_HELP_MECHANISM.get(i.getName()), maxWidth));
1232 lines.add("");
1233
1234 for (final String line : wrapLine(i.getDescription(), maxWidth - 4))
1235 {
1236 lines.add(" " + line);
1237 }
1238 lines.add("");
1239
1240 for (final String line :
1241 wrapLine(INFO_SASL_HELP_MECHANISM_OPTIONS.get(i.getName()),
1242 maxWidth - 4))
1243 {
1244 lines.add(" " + line);
1245 }
1246
1247 if (i.acceptsPassword())
1248 {
1249 lines.add("");
1250 if (i.requiresPassword())
1251 {
1252 for (final String line :
1253 wrapLine(INFO_SASL_HELP_PASSWORD_REQUIRED.get(i.getName()),
1254 maxWidth - 4))
1255 {
1256 lines.add(" " + line);
1257 }
1258 }
1259 else
1260 {
1261 for (final String line :
1262 wrapLine(INFO_SASL_HELP_PASSWORD_OPTIONAL.get(i.getName()),
1263 maxWidth - 4))
1264 {
1265 lines.add(" " + line);
1266 }
1267 }
1268 }
1269
1270 for (final SASLOption o : i.getOptions())
1271 {
1272 lines.add("");
1273 lines.add(" * " + o.getName());
1274 for (final String line : wrapLine(o.getDescription(), maxWidth - 14))
1275 {
1276 lines.add(" " + line);
1277 }
1278 }
1279 }
1280
1281 return lines;
1282 }
1283 }