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.forgerock.opendj.config.server.ConfigurationChangeListener; 036import org.forgerock.opendj.server.config.meta.DigestMD5SASLMechanismHandlerCfgDefn.QualityOfProtection; 037import org.forgerock.opendj.server.config.server.DigestMD5SASLMechanismHandlerCfg; 038import org.forgerock.opendj.server.config.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 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 056 057 /** The current configuration for this SASL mechanism handler. */ 058 private DigestMD5SASLMechanismHandlerCfg configuration; 059 060 /** The identity mapper that will be used to map ID strings to user entries. */ 061 private IdentityMapper<?> identityMapper; 062 063 /** Properties to use when creating a SASL server to process the authentication. */ 064 private HashMap<String,String> saslProps; 065 066 /** The fully qualified domain name used when creating the SASL server. */ 067 private String serverFQDN; 068 069 /** The DN of the configuration entry for this SASL mechanism handler. */ 070 private DN configEntryDN; 071 072 /** Property used to set the realm in the environment. */ 073 private static final String REALM_PROPERTY = "com.sun.security.sasl.digest.realm"; 074 075 /** 076 * Creates a new instance of this SASL mechanism handler. No initialization 077 * should be done in this method, as it should all be performed in the 078 * <CODE>initializeSASLMechanismHandler</CODE> method. 079 */ 080 public DigestMD5SASLMechanismHandler() 081 { 082 super(); 083 } 084 085 @Override 086 public void initializeSASLMechanismHandler( 087 DigestMD5SASLMechanismHandlerCfg configuration) 088 throws ConfigException, InitializationException { 089 configuration.addDigestMD5ChangeListener(this); 090 configEntryDN = configuration.dn(); 091 try { 092 DN identityMapperDN = configuration.getIdentityMapperDN(); 093 identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN); 094 serverFQDN = getFQDN(configuration); 095 LocalizableMessage msg= NOTE_DIGEST_MD5_SERVER_FQDN.get(serverFQDN); 096 logger.info(msg); 097 String QOP = getQOP(configuration); 098 saslProps = new HashMap<>(); 099 saslProps.put(Sasl.QOP, QOP); 100 String realm=getRealm(configuration); 101 if(realm != null) { 102 msg = INFO_DIGEST_MD5_REALM.get(realm); 103 logger.error(msg); 104 saslProps.put(REALM_PROPERTY, getRealm(configuration)); 105 } 106 this.configuration = configuration; 107 DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_DIGEST_MD5, 108 this); 109 } catch (UnknownHostException unhe) { 110 logger.traceException(unhe); 111 LocalizableMessage message = ERR_SASL_CANNOT_GET_SERVER_FQDN.get(configEntryDN, getExceptionMessage(unhe)); 112 throw new InitializationException(message, unhe); 113 } 114 } 115 116 @Override 117 public void finalizeSASLMechanismHandler() { 118 configuration.removeDigestMD5ChangeListener(this); 119 DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_DIGEST_MD5); 120 } 121 122 @Override 123 public void processSASLBind(BindOperation bindOp) { 124 ClientConnection clientConnection = bindOp.getClientConnection(); 125 if (clientConnection == null) { 126 LocalizableMessage message = ERR_SASLGSSAPI_NO_CLIENT_CONNECTION.get(); 127 bindOp.setAuthFailureReason(message); 128 bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS); 129 return; 130 } 131 ClientConnection clientConn = bindOp.getClientConnection(); 132 SASLContext saslContext = 133 (SASLContext) clientConn.getSASLAuthStateInfo(); 134 if(saslContext == null) { 135 try { 136 saslContext = SASLContext.createSASLContext(saslProps, serverFQDN, 137 SASL_MECHANISM_DIGEST_MD5, identityMapper); 138 } catch (SaslException ex) { 139 logger.traceException(ex); 140 LocalizableMessage msg = 141 ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_DIGEST_MD5, 142 getExceptionMessage(ex)); 143 clientConn.setSASLAuthStateInfo(null); 144 bindOp.setAuthFailureReason(msg); 145 bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS); 146 return; 147 } 148 saslContext.evaluateInitialStage(bindOp); 149 } else { 150 saslContext.evaluateFinalStage(bindOp); 151 } 152 } 153 154 @Override 155 public boolean isPasswordBased(String mechanism) 156 { 157 // This is a password-based mechanism. 158 return true; 159 } 160 161 @Override 162 public boolean isSecure(String mechanism) 163 { 164 // This may be considered a secure mechanism. 165 return true; 166 } 167 168 @Override 169 public boolean isConfigurationAcceptable( 170 SASLMechanismHandlerCfg configuration, 171 List<LocalizableMessage> unacceptableReasons) 172 { 173 DigestMD5SASLMechanismHandlerCfg config = 174 (DigestMD5SASLMechanismHandlerCfg) configuration; 175 return isConfigurationChangeAcceptable(config, unacceptableReasons); 176 } 177 178 @Override 179 public boolean isConfigurationChangeAcceptable( 180 DigestMD5SASLMechanismHandlerCfg configuration, 181 List<LocalizableMessage> unacceptableReasons) 182 { 183 return true; 184 } 185 186 @Override 187 public ConfigChangeResult applyConfigurationChange( 188 DigestMD5SASLMechanismHandlerCfg configuration) 189 { 190 final ConfigChangeResult ccr = new ConfigChangeResult(); 191 try { 192 DN identityMapperDN = configuration.getIdentityMapperDN(); 193 identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN); 194 serverFQDN = getFQDN(configuration); 195 LocalizableMessage msg = NOTE_DIGEST_MD5_SERVER_FQDN.get(serverFQDN); 196 logger.info(msg); 197 String QOP = getQOP(configuration); 198 saslProps = new HashMap<>(); 199 saslProps.put(Sasl.QOP, QOP); 200 String realm=getRealm(configuration); 201 if(realm != null) { 202 msg = INFO_DIGEST_MD5_REALM.get(realm); 203 logger.error(msg); 204 saslProps.put(REALM_PROPERTY, getRealm(configuration)); 205 } 206 this.configuration = configuration; 207 } catch (UnknownHostException unhe) { 208 logger.traceException(unhe); 209 ccr.setResultCode(ResultCode.OPERATIONS_ERROR); 210 ccr.addMessage(ERR_SASL_CANNOT_GET_SERVER_FQDN.get(configEntryDN, getExceptionMessage(unhe))); 211 } 212 return ccr; 213 } 214 215 /** 216 * Retrieves the QOP (quality-of-protection) from the specified 217 * configuration. 218 * 219 * @param configuration The new configuration to use. 220 * @return A string representing the quality-of-protection. 221 */ 222 private String 223 getQOP(DigestMD5SASLMechanismHandlerCfg configuration) { 224 QualityOfProtection QOP = configuration.getQualityOfProtection(); 225 if(QOP.equals(QualityOfProtection.CONFIDENTIALITY)) { 226 return "auth-conf"; 227 } else if(QOP.equals(QualityOfProtection.INTEGRITY)) { 228 return "auth-int"; 229 } else { 230 return "auth"; 231 } 232 } 233 234 /** 235 * Returns the fully qualified name either defined in the configuration, or, 236 * determined by examining the system configuration. 237 * 238 * @param configuration The configuration to check. 239 * @return The fully qualified hostname of the server. 240 * 241 * @throws UnknownHostException If the name cannot be determined from the 242 * system configuration. 243 */ 244 private String getFQDN(DigestMD5SASLMechanismHandlerCfg configuration) 245 throws UnknownHostException { 246 String serverName = configuration.getServerFqdn(); 247 if (serverName == null) { 248 serverName = InetAddress.getLocalHost().getCanonicalHostName(); 249 } 250 return serverName; 251 } 252 253 /** 254 * Retrieve the realm either defined in the specified configuration. If this 255 * isn't defined, the SaslServer internal code uses the server name. 256 * 257 * @param configuration The configuration to check. 258 * @return A string representing the realm. 259 */ 260 private String getRealm(DigestMD5SASLMechanismHandlerCfg configuration) { 261 return configuration.getRealm(); 262 } 263}