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}