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}