001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2012-2015 ForgeRock AS. 026 */ 027package org.opends.server.extensions; 028 029import static org.opends.messages.ExtensionMessages.*; 030import static org.opends.server.util.ServerConstants.*; 031import static org.opends.server.util.StaticUtils.*; 032 033import java.net.InetAddress; 034import java.net.UnknownHostException; 035import java.util.HashMap; 036import java.util.List; 037 038import javax.security.sasl.Sasl; 039import javax.security.sasl.SaslException; 040 041import org.forgerock.i18n.LocalizableMessage; 042import org.forgerock.i18n.slf4j.LocalizedLogger; 043import org.forgerock.opendj.config.server.ConfigException; 044import org.forgerock.opendj.ldap.ResultCode; 045import org.opends.server.admin.server.ConfigurationChangeListener; 046import org.opends.server.admin.std.meta.DigestMD5SASLMechanismHandlerCfgDefn.QualityOfProtection; 047import org.opends.server.admin.std.server.DigestMD5SASLMechanismHandlerCfg; 048import org.opends.server.admin.std.server.SASLMechanismHandlerCfg; 049import org.opends.server.api.ClientConnection; 050import org.opends.server.api.IdentityMapper; 051import org.opends.server.api.SASLMechanismHandler; 052import org.opends.server.core.BindOperation; 053import org.opends.server.core.DirectoryServer; 054import org.forgerock.opendj.config.server.ConfigChangeResult; 055import org.opends.server.types.DN; 056import org.opends.server.types.InitializationException; 057 058/** 059 * This class provides an implementation of a SASL mechanism that authenticates 060 * clients through DIGEST-MD5. 061 */ 062public class DigestMD5SASLMechanismHandler 063 extends SASLMechanismHandler<DigestMD5SASLMechanismHandlerCfg> 064 implements ConfigurationChangeListener<DigestMD5SASLMechanismHandlerCfg> { 065 066 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 067 068 /** The current configuration for this SASL mechanism handler. */ 069 private DigestMD5SASLMechanismHandlerCfg configuration; 070 071 /** The identity mapper that will be used to map ID strings to user entries. */ 072 private IdentityMapper<?> identityMapper; 073 074 /** Properties to use when creating a SASL server to process the authentication. */ 075 private HashMap<String,String> saslProps; 076 077 /** The fully qualified domain name used when creating the SASL server. */ 078 private String serverFQDN; 079 080 /** The DN of the configuration entry for this SASL mechanism handler. */ 081 private DN configEntryDN; 082 083 /** Property used to set the realm in the environment. */ 084 private static final String REALM_PROPERTY = "com.sun.security.sasl.digest.realm"; 085 086 087 /** 088 * Creates a new instance of this SASL mechanism handler. No initialization 089 * should be done in this method, as it should all be performed in the 090 * <CODE>initializeSASLMechanismHandler</CODE> method. 091 */ 092 public DigestMD5SASLMechanismHandler() 093 { 094 super(); 095 } 096 097 098 /** {@inheritDoc} */ 099 @Override 100 public void initializeSASLMechanismHandler( 101 DigestMD5SASLMechanismHandlerCfg configuration) 102 throws ConfigException, InitializationException { 103 configuration.addDigestMD5ChangeListener(this); 104 configEntryDN = configuration.dn(); 105 try { 106 DN identityMapperDN = configuration.getIdentityMapperDN(); 107 identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN); 108 serverFQDN = getFQDN(configuration); 109 LocalizableMessage msg= NOTE_DIGEST_MD5_SERVER_FQDN.get(serverFQDN); 110 logger.info(msg); 111 String QOP = getQOP(configuration); 112 saslProps = new HashMap<>(); 113 saslProps.put(Sasl.QOP, QOP); 114 String realm=getRealm(configuration); 115 if(realm != null) { 116 msg = INFO_DIGEST_MD5_REALM.get(realm); 117 logger.error(msg); 118 saslProps.put(REALM_PROPERTY, getRealm(configuration)); 119 } 120 this.configuration = configuration; 121 DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_DIGEST_MD5, 122 this); 123 } catch (UnknownHostException unhe) { 124 logger.traceException(unhe); 125 LocalizableMessage message = ERR_SASL_CANNOT_GET_SERVER_FQDN.get(configEntryDN, getExceptionMessage(unhe)); 126 throw new InitializationException(message, unhe); 127 } 128 } 129 130 131 /** {@inheritDoc} */ 132 @Override 133 public void finalizeSASLMechanismHandler() { 134 configuration.removeDigestMD5ChangeListener(this); 135 DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_DIGEST_MD5); 136 } 137 138 139 /** {@inheritDoc} */ 140 @Override 141 public void processSASLBind(BindOperation bindOp) { 142 ClientConnection clientConnection = bindOp.getClientConnection(); 143 if (clientConnection == null) { 144 LocalizableMessage message = ERR_SASLGSSAPI_NO_CLIENT_CONNECTION.get(); 145 bindOp.setAuthFailureReason(message); 146 bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS); 147 return; 148 } 149 ClientConnection clientConn = bindOp.getClientConnection(); 150 SASLContext saslContext = 151 (SASLContext) clientConn.getSASLAuthStateInfo(); 152 if(saslContext == null) { 153 try { 154 saslContext = SASLContext.createSASLContext(saslProps, serverFQDN, 155 SASL_MECHANISM_DIGEST_MD5, identityMapper); 156 } catch (SaslException ex) { 157 logger.traceException(ex); 158 LocalizableMessage msg = 159 ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_DIGEST_MD5, 160 getExceptionMessage(ex)); 161 clientConn.setSASLAuthStateInfo(null); 162 bindOp.setAuthFailureReason(msg); 163 bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS); 164 return; 165 } 166 saslContext.evaluateInitialStage(bindOp); 167 } else { 168 saslContext.evaluateFinalStage(bindOp); 169 } 170 } 171 172 173 /** {@inheritDoc} */ 174 @Override 175 public boolean isPasswordBased(String mechanism) 176 { 177 // This is a password-based mechanism. 178 return true; 179 } 180 181 182 183 /** {@inheritDoc} */ 184 @Override 185 public boolean isSecure(String mechanism) 186 { 187 // This may be considered a secure mechanism. 188 return true; 189 } 190 191 192 /** {@inheritDoc} */ 193 @Override 194 public boolean isConfigurationAcceptable( 195 SASLMechanismHandlerCfg configuration, 196 List<LocalizableMessage> unacceptableReasons) 197 { 198 DigestMD5SASLMechanismHandlerCfg config = 199 (DigestMD5SASLMechanismHandlerCfg) configuration; 200 return isConfigurationChangeAcceptable(config, unacceptableReasons); 201 } 202 203 204 /** {@inheritDoc} */ 205 @Override 206 public boolean isConfigurationChangeAcceptable( 207 DigestMD5SASLMechanismHandlerCfg configuration, 208 List<LocalizableMessage> unacceptableReasons) 209 { 210 return true; 211 } 212 213 214 /** {@inheritDoc} */ 215 @Override 216 public ConfigChangeResult applyConfigurationChange( 217 DigestMD5SASLMechanismHandlerCfg configuration) 218 { 219 final ConfigChangeResult ccr = new ConfigChangeResult(); 220 try { 221 DN identityMapperDN = configuration.getIdentityMapperDN(); 222 identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN); 223 serverFQDN = getFQDN(configuration); 224 LocalizableMessage msg = NOTE_DIGEST_MD5_SERVER_FQDN.get(serverFQDN); 225 logger.info(msg); 226 String QOP = getQOP(configuration); 227 saslProps = new HashMap<>(); 228 saslProps.put(Sasl.QOP, QOP); 229 String realm=getRealm(configuration); 230 if(realm != null) { 231 msg = INFO_DIGEST_MD5_REALM.get(realm); 232 logger.error(msg); 233 saslProps.put(REALM_PROPERTY, getRealm(configuration)); 234 } 235 this.configuration = configuration; 236 } catch (UnknownHostException unhe) { 237 logger.traceException(unhe); 238 ccr.setResultCode(ResultCode.OPERATIONS_ERROR); 239 ccr.addMessage(ERR_SASL_CANNOT_GET_SERVER_FQDN.get(configEntryDN, getExceptionMessage(unhe))); 240 } 241 return ccr; 242 } 243 244 245 /** 246 * Retrieves the QOP (quality-of-protection) from the specified 247 * configuration. 248 * 249 * @param configuration The new configuration to use. 250 * @return A string representing the quality-of-protection. 251 */ 252 private String 253 getQOP(DigestMD5SASLMechanismHandlerCfg configuration) { 254 QualityOfProtection QOP = configuration.getQualityOfProtection(); 255 if(QOP.equals(QualityOfProtection.CONFIDENTIALITY)) { 256 return "auth-conf"; 257 } else if(QOP.equals(QualityOfProtection.INTEGRITY)) { 258 return "auth-int"; 259 } else { 260 return "auth"; 261 } 262 } 263 264 265 /** 266 * Returns the fully qualified name either defined in the configuration, or, 267 * determined by examining the system configuration. 268 * 269 * @param configuration The configuration to check. 270 * @return The fully qualified hostname of the server. 271 * 272 * @throws UnknownHostException If the name cannot be determined from the 273 * system configuration. 274 */ 275 private String getFQDN(DigestMD5SASLMechanismHandlerCfg configuration) 276 throws UnknownHostException { 277 String serverName = configuration.getServerFqdn(); 278 if (serverName == null) { 279 serverName = InetAddress.getLocalHost().getCanonicalHostName(); 280 } 281 return serverName; 282 } 283 284 285 /** 286 * Retrieve the realm either defined in the specified configuration. If this 287 * isn't defined, the SaslServer internal code uses the server name. 288 * 289 * @param configuration The configuration to check. 290 * @return A string representing the realm. 291 */ 292 private String getRealm(DigestMD5SASLMechanismHandlerCfg configuration) { 293 return configuration.getRealm(); 294 } 295}