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