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}