package com.hwlcn.ldap.util; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import com.hwlcn.core.annotation.InternalUseOnly; import com.hwlcn.core.annotation.ThreadSafety; import com.hwlcn.ldap.ldap.sdk.ANONYMOUSBindRequest; import com.hwlcn.ldap.ldap.sdk.Control; import com.hwlcn.ldap.ldap.sdk.CRAMMD5BindRequest; import com.hwlcn.ldap.ldap.sdk.DIGESTMD5BindRequest; import com.hwlcn.ldap.ldap.sdk.EXTERNALBindRequest; import com.hwlcn.ldap.ldap.sdk.GSSAPIBindRequest; import com.hwlcn.ldap.ldap.sdk.GSSAPIBindRequestProperties; import com.hwlcn.ldap.ldap.sdk.LDAPException; import com.hwlcn.ldap.ldap.sdk.PLAINBindRequest; import com.hwlcn.ldap.ldap.sdk.ResultCode; import com.hwlcn.ldap.ldap.sdk.SASLBindRequest; import static com.hwlcn.ldap.util.StaticUtils.*; import static com.hwlcn.ldap.util.UtilityMessages.*; @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class SASLUtils { public static final String SASL_OPTION_AUTH_ID = "authID"; public static final String SASL_OPTION_AUTHZ_ID = "authzID"; public static final String SASL_OPTION_CONFIG_FILE = "configFile"; public static final String SASL_OPTION_DEBUG = "debug"; public static final String SASL_OPTION_KDC_ADDRESS = "kdcAddress"; public static final String SASL_OPTION_MECHANISM = "mech"; public static final String SASL_OPTION_PROTOCOL = "protocol"; public static final String SASL_OPTION_REALM = "realm"; public static final String SASL_OPTION_REQUIRE_CACHE = "requireCache"; public static final String SASL_OPTION_RENEW_TGT = "renewTGT"; public static final String SASL_OPTION_TICKET_CACHE_PATH = "ticketCache"; public static final String SASL_OPTION_TRACE = "trace"; public static final String SASL_OPTION_USE_TICKET_CACHE = "useTicketCache"; private static final Map<String,SASLMechanismInfo> SASL_MECHANISMS; static { final TreeMap<String,SASLMechanismInfo> m = new TreeMap<String,SASLMechanismInfo>(); m.put(toLowerCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME), new SASLMechanismInfo(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME, INFO_SASL_ANONYMOUS_DESCRIPTION.get(), false, false, new SASLOption(SASL_OPTION_TRACE, INFO_SASL_ANONYMOUS_OPTION_TRACE.get(), false, false))); m.put(toLowerCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME), new SASLMechanismInfo(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME, INFO_SASL_CRAM_MD5_DESCRIPTION.get(), true, true, new SASLOption(SASL_OPTION_AUTH_ID, INFO_SASL_CRAM_MD5_OPTION_AUTH_ID.get(), true, false))); m.put(toLowerCase(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME), new SASLMechanismInfo(DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME, INFO_SASL_DIGEST_MD5_DESCRIPTION.get(), true, true, new SASLOption(SASL_OPTION_AUTH_ID, INFO_SASL_DIGEST_MD5_OPTION_AUTH_ID.get(), true, false), new SASLOption(SASL_OPTION_AUTHZ_ID, INFO_SASL_DIGEST_MD5_OPTION_AUTHZ_ID.get(), false, false), new SASLOption(SASL_OPTION_REALM, INFO_SASL_DIGEST_MD5_OPTION_REALM.get(), false, false))); m.put(toLowerCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME), new SASLMechanismInfo(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME, INFO_SASL_EXTERNAL_DESCRIPTION.get(), false, false)); m.put(toLowerCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME), new SASLMechanismInfo(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME, INFO_SASL_GSSAPI_DESCRIPTION.get(), true, false, new SASLOption(SASL_OPTION_AUTH_ID, INFO_SASL_GSSAPI_OPTION_AUTH_ID.get(), true, false), new SASLOption(SASL_OPTION_AUTHZ_ID, INFO_SASL_GSSAPI_OPTION_AUTHZ_ID.get(), false, false), new SASLOption(SASL_OPTION_CONFIG_FILE, INFO_SASL_GSSAPI_OPTION_CONFIG_FILE.get(), false, false), new SASLOption(SASL_OPTION_DEBUG, INFO_SASL_GSSAPI_OPTION_DEBUG.get(), false, false), new SASLOption(SASL_OPTION_KDC_ADDRESS, INFO_SASL_GSSAPI_OPTION_KDC_ADDRESS.get(), false, false), new SASLOption(SASL_OPTION_PROTOCOL, INFO_SASL_GSSAPI_OPTION_PROTOCOL.get(), false, false), new SASLOption(SASL_OPTION_REALM, INFO_SASL_GSSAPI_OPTION_REALM.get(), false, false), new SASLOption(SASL_OPTION_RENEW_TGT, INFO_SASL_GSSAPI_OPTION_RENEW_TGT.get(), false, false), new SASLOption(SASL_OPTION_REQUIRE_CACHE, INFO_SASL_GSSAPI_OPTION_REQUIRE_TICKET_CACHE.get(), false, false), new SASLOption(SASL_OPTION_TICKET_CACHE_PATH, INFO_SASL_GSSAPI_OPTION_TICKET_CACHE.get(), false, false), new SASLOption(SASL_OPTION_USE_TICKET_CACHE, INFO_SASL_GSSAPI_OPTION_USE_TICKET_CACHE.get(), false, false))); m.put(toLowerCase(PLAINBindRequest.PLAIN_MECHANISM_NAME), new SASLMechanismInfo(PLAINBindRequest.PLAIN_MECHANISM_NAME, INFO_SASL_PLAIN_DESCRIPTION.get(), true, true, new SASLOption(SASL_OPTION_AUTH_ID, INFO_SASL_PLAIN_OPTION_AUTH_ID.get(), true, false), new SASLOption(SASL_OPTION_AUTHZ_ID, INFO_SASL_PLAIN_OPTION_AUTHZ_ID.get(), false, false))); try { final Class<?> c = Class.forName("com.hwlcn.ldap.ldap.sdk.unboundidds.SASLHelper"); final Method addCESASLInfoMethod = c.getMethod("addCESASLInfo", Map.class); addCESASLInfoMethod.invoke(null, m); } catch (final Exception e) { Debug.debugException(e); } SASL_MECHANISMS = Collections.unmodifiableMap(m); } private SASLUtils() { } public static List<SASLMechanismInfo> getSupportedSASLMechanisms() { return Collections.unmodifiableList(new ArrayList<SASLMechanismInfo>( SASL_MECHANISMS.values())); } public static SASLMechanismInfo getSASLMechanismInfo(final String mechanism) { return SASL_MECHANISMS.get(toLowerCase(mechanism)); } public static SASLBindRequest createBindRequest(final String bindDN, final String password, final String mechanism, final String... options) throws LDAPException { return createBindRequest(bindDN, (password == null ? null : getBytes(password)), mechanism, StaticUtils.toList(options)); } public static SASLBindRequest createBindRequest(final String bindDN, final String password, final String mechanism, final List<String> options, final Control... controls) throws LDAPException { return createBindRequest(bindDN, (password == null ? null : getBytes(password)), mechanism, options, controls); } public static SASLBindRequest createBindRequest(final String bindDN, final byte[] password, final String mechanism, final String... options) throws LDAPException { return createBindRequest(bindDN, password, mechanism, StaticUtils.toList(options)); } public static SASLBindRequest createBindRequest(final String bindDN, final byte[] password, final String mechanism, final List<String> options, final Control... controls) throws LDAPException { final String mech; final Map<String,String> optionsMap = parseOptions(options); final String mechOption = optionsMap.remove(toLowerCase(SASL_OPTION_MECHANISM)); if (mechOption != null) { mech = mechOption; if ((mechanism != null) && (! mech.equalsIgnoreCase(mechanism))) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_MECH_CONFLICT.get(mechanism, mech)); } } else { mech = mechanism; } if (mech == null) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_NO_MECH.get()); } if (mech.equalsIgnoreCase(ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME)) { return createANONYMOUSBindRequest(password, optionsMap, controls); } else if (mech.equalsIgnoreCase(CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)) { return createCRAMMD5BindRequest(password, optionsMap, controls); } else if (mech.equalsIgnoreCase( DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME)) { return createDIGESTMD5BindRequest(password, optionsMap, controls); } else if (mech.equalsIgnoreCase(EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME)) { return createEXTERNALBindRequest(password, optionsMap, controls); } else if (mech.equalsIgnoreCase(GSSAPIBindRequest.GSSAPI_MECHANISM_NAME)) { return createGSSAPIBindRequest(password, optionsMap, controls); } else if (mech.equalsIgnoreCase(PLAINBindRequest.PLAIN_MECHANISM_NAME)) { return createPLAINBindRequest(password, optionsMap, controls); } else { try { final Class<?> c = Class.forName("com.hwlcn.ldap.ldap.sdk.unboundidds.SASLHelper"); final Method createBindRequestMethod = c.getMethod("createBindRequest", String.class, StaticUtils.NO_BYTES.getClass(), String.class, Map.class, StaticUtils.NO_CONTROLS.getClass()); final Object bindRequestObject = createBindRequestMethod.invoke(null, bindDN, password, mech, optionsMap, controls); if (bindRequestObject != null) { return (SASLBindRequest) bindRequestObject; } } catch (final Exception e) { Debug.debugException(e); if (e instanceof InvocationTargetException) { final InvocationTargetException ite = (InvocationTargetException) e; final Throwable t = ite.getTargetException(); if (t instanceof LDAPException) { throw (LDAPException) t; } } } throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_UNSUPPORTED_MECH.get(mech)); } } private static ANONYMOUSBindRequest createANONYMOUSBindRequest( final byte[] password, final Map<String,String> options, final Control[] controls) throws LDAPException { if (password != null) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get( ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME)); } final String trace = options.remove(toLowerCase(SASL_OPTION_TRACE)); ensureNoUnsupportedOptions(options, ANONYMOUSBindRequest.ANONYMOUS_MECHANISM_NAME); return new ANONYMOUSBindRequest(trace, controls); } private static CRAMMD5BindRequest createCRAMMD5BindRequest( final byte[] password, final Map<String,String> options, final Control[] controls) throws LDAPException { if (password == null) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get( CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)); } final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); if (authID == null) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)); } ensureNoUnsupportedOptions(options, CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME); return new CRAMMD5BindRequest(authID, password, controls); } private static DIGESTMD5BindRequest createDIGESTMD5BindRequest( final byte[] password, final Map<String,String> options, final Control[] controls) throws LDAPException { if (password == null) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get( DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME)); } final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); if (authID == null) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, CRAMMD5BindRequest.CRAMMD5_MECHANISM_NAME)); } final String authzID = options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID)); final String realm = options.remove(toLowerCase(SASL_OPTION_REALM)); ensureNoUnsupportedOptions(options, DIGESTMD5BindRequest.DIGESTMD5_MECHANISM_NAME); return new DIGESTMD5BindRequest(authID, authzID, password, realm, controls); } private static EXTERNALBindRequest createEXTERNALBindRequest( final byte[] password, final Map<String,String> options, final Control[] controls) throws LDAPException { if (password != null) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_MECH_DOESNT_ACCEPT_PASSWORD.get( EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME)); } ensureNoUnsupportedOptions(options, EXTERNALBindRequest.EXTERNAL_MECHANISM_NAME); return new EXTERNALBindRequest(controls); } private static GSSAPIBindRequest createGSSAPIBindRequest( final byte[] password, final Map<String,String> options, final Control[] controls) throws LDAPException { final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); if (authID == null) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, GSSAPIBindRequest.GSSAPI_MECHANISM_NAME)); } final GSSAPIBindRequestProperties gssapiProperties = new GSSAPIBindRequestProperties(authID, password); gssapiProperties.setAuthorizationID( options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID))); gssapiProperties.setConfigFilePath(options.remove(toLowerCase( SASL_OPTION_CONFIG_FILE))); gssapiProperties.setEnableGSSAPIDebugging(getBooleanValue(options, SASL_OPTION_DEBUG, false)); gssapiProperties.setKDCAddress(options.remove( toLowerCase(SASL_OPTION_KDC_ADDRESS))); final String protocol = options.remove(toLowerCase(SASL_OPTION_PROTOCOL)); if (protocol != null) { gssapiProperties.setServicePrincipalProtocol(protocol); } gssapiProperties.setRealm(options.remove(toLowerCase(SASL_OPTION_REALM))); gssapiProperties.setRenewTGT(getBooleanValue(options, SASL_OPTION_RENEW_TGT, false)); gssapiProperties.setRequireCachedCredentials(getBooleanValue(options, SASL_OPTION_REQUIRE_CACHE, false)); gssapiProperties.setTicketCachePath(options.remove(toLowerCase( SASL_OPTION_TICKET_CACHE_PATH))); gssapiProperties.setUseTicketCache(getBooleanValue(options, SASL_OPTION_USE_TICKET_CACHE, true)); ensureNoUnsupportedOptions(options, GSSAPIBindRequest.GSSAPI_MECHANISM_NAME); if (password == null) { if (! (gssapiProperties.useTicketCache() && gssapiProperties.requireCachedCredentials())) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_GSSAPI_PASSWORD_REQUIRED.get()); } } return new GSSAPIBindRequest(gssapiProperties, controls); } private static PLAINBindRequest createPLAINBindRequest( final byte[] password, final Map<String,String> options, final Control[] controls) throws LDAPException { if (password == null) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_MECH_REQUIRES_PASSWORD.get( PLAINBindRequest.PLAIN_MECHANISM_NAME)); } final String authID = options.remove(toLowerCase(SASL_OPTION_AUTH_ID)); if (authID == null) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_MISSING_REQUIRED_OPTION.get(SASL_OPTION_AUTH_ID, PLAINBindRequest.PLAIN_MECHANISM_NAME)); } final String authzID = options.remove(toLowerCase(SASL_OPTION_AUTHZ_ID)); ensureNoUnsupportedOptions(options, PLAINBindRequest.PLAIN_MECHANISM_NAME); return new PLAINBindRequest(authID, authzID, password, controls); } private static Map<String,String> parseOptions(final List<String> options) throws LDAPException { if (options == null) { return new HashMap<String,String>(0); } final HashMap<String,String> m = new HashMap<String,String>(options.size()); for (final String s : options) { final int equalPos = s.indexOf('='); if (equalPos < 0) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_MISSING_EQUAL.get(s)); } else if (equalPos == 0) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_STARTS_WITH_EQUAL.get(s)); } final String name = s.substring(0, equalPos); final String value = s.substring(equalPos + 1); if (m.put(toLowerCase(name), value) != null) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_NOT_MULTI_VALUED.get(name)); } } return m; } @InternalUseOnly() public static void ensureNoUnsupportedOptions( final Map<String,String> options, final String mechanism) throws LDAPException { if (! options.isEmpty()) { for (final String s : options.keySet()) { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_UNSUPPORTED_FOR_MECH.get(s,mechanism)); } } } static boolean getBooleanValue(final Map<String,String> m, final String o, final boolean d) throws LDAPException { final String s = toLowerCase(m.remove(toLowerCase(o))); if (s == null) { return d; } else if (s.equals("true") || s.equals("t") || s.equals("yes") || s.equals("y") || s.equals("on") || s.equals("1")) { return true; } else if (s.equals("false") || s.equals("f") || s.equals("no") || s.equals("n") || s.equals("off") || s.equals("0")) { return false; } else { throw new LDAPException(ResultCode.PARAM_ERROR, ERR_SASL_OPTION_MALFORMED_BOOLEAN_VALUE.get(o)); } } }