mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

matthew_swift
05.42.2009 22094368c2865dcfb6daf8366425212b721a4657
opends/src/server/org/opends/server/extensions/GSSAPISASLMechanismHandler.java
@@ -25,10 +25,15 @@
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import org.opends.messages.Message;
import static org.opends.messages.ExtensionMessages.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
@@ -38,6 +43,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
@@ -45,8 +51,11 @@
import javax.security.auth.login.LoginException;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import org.opends.messages.Message;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.admin.std.meta.GSSAPISASLMechanismHandlerCfgDefn.*;
import org.opends.server.admin.std.meta.
  GSSAPISASLMechanismHandlerCfgDefn.QualityOfProtection;
import org.opends.server.admin.std.server.GSSAPISASLMechanismHandlerCfg;
import org.opends.server.admin.std.server.SASLMechanismHandlerCfg;
import org.opends.server.api.ClientConnection;
@@ -55,35 +64,31 @@
import org.opends.server.config.ConfigException;
import org.opends.server.core.BindOperation;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.messages.ExtensionMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * This class provides an implementation of a SASL mechanism that authenticates
 * clients through Kerberos over GSSAPI.
 * This class provides an implementation of a SASL mechanism that
 * authenticates clients through Kerberos over GSSAPI.
 */
public class GSSAPISASLMechanismHandler
       extends SASLMechanismHandler<GSSAPISASLMechanismHandlerCfg>
       implements ConfigurationChangeListener< GSSAPISASLMechanismHandlerCfg>,
       CallbackHandler {
public class GSSAPISASLMechanismHandler extends
    SASLMechanismHandler<GSSAPISASLMechanismHandlerCfg> implements
    ConfigurationChangeListener<GSSAPISASLMechanismHandlerCfg>, CallbackHandler
{
  //The tracer object for the debug logger.
  // The tracer object for the debug logger.
  private static final DebugTracer TRACER = getTracer();
  // The DN of the configuration entry for this SASL mechanism handler.
  // The DN of the configuration entry for this SASL mechanism
  // handler.
  private DN configEntryDN;
  // The current configuration for this SASL mechanism handler.
@@ -92,20 +97,24 @@
  // The identity mapper that will be used to map identities.
  private IdentityMapper<?> identityMapper;
  //The properties to use when creating a SASL server to process the GSSAPI
  //authentication.
  private HashMap<String,String> saslProps;
  // The properties to use when creating a SASL server to process the
  // GSSAPI authentication.
  private HashMap<String, String> saslProps;
  //The fully qualified domain name used when creating the SASL server.
  // The fully qualified domain name used when creating the SASL
  // server.
  private String serverFQDN;
  //The login context used to perform server-side authentication.
  // The login context used to perform server-side authentication.
  private LoginContext loginContext;
  /**
   * Creates a new instance of this SASL mechanism handler.  No initialization
   * should be done in this method, as it should all be performed in the
   * <CODE>initializeSASLMechanismHandler</CODE> method.
   * Creates a new instance of this SASL mechanism handler. No
   * initialization should be done in this method, as it should all be
   * performed in the <CODE>initializeSASLMechanismHandler</CODE>
   * method.
   */
  public GSSAPISASLMechanismHandler()
  {
@@ -113,255 +122,308 @@
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void
  initializeSASLMechanismHandler(GSSAPISASLMechanismHandlerCfg configuration)
  throws ConfigException, InitializationException {
      configuration.addGSSAPIChangeListener(this);
      this.configuration = configuration;
      configEntryDN = configuration.dn();
      try {
          DN identityMapperDN = configuration.getIdentityMapperDN();
          identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
          serverFQDN = getFQDN(configuration);
          Message msg= INFO_GSSAPI_SERVER_FQDN.get(serverFQDN);
          logError(msg);
          saslProps = new HashMap<String,String>();
          saslProps.put(Sasl.QOP, getQOP(configuration));
          saslProps.put(Sasl.REUSE, "false");
          String configFileName=configureLoginConfFile(configuration);
          System.setProperty(JAAS_PROPERTY_CONFIG_FILE, configFileName);
          System.setProperty(JAAS_PROPERTY_SUBJECT_CREDS_ONLY, "false");
          getKdcRealm(configuration);
          DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_GSSAPI,
                  this);
          login();
      } catch (UnknownHostException unhe) {
          if (debugEnabled()) {
            TRACER.debugCaught(DebugLogLevel.ERROR, unhe);
          }
          Message message = ERR_SASL_CANNOT_GET_SERVER_FQDN.get(
                  String.valueOf(configEntryDN), getExceptionMessage(unhe));
          throw new InitializationException(message, unhe);
      } catch(IOException ioe) {
          if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
            }
          Message message = ERR_SASLGSSAPI_CANNOT_CREATE_JAAS_CONFIG.get(
                                                     getExceptionMessage(ioe));
          throw new InitializationException(message, ioe);
      } catch (LoginException le) {
          if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, le);
           }
          Message message = ERR_SASLGSSAPI_CANNOT_CREATE_LOGIN_CONTEXT.get(
                  getExceptionMessage(le));
          throw new InitializationException(message, le);
  public void initializeSASLMechanismHandler(
      GSSAPISASLMechanismHandlerCfg configuration) throws ConfigException,
      InitializationException
  {
    configuration.addGSSAPIChangeListener(this);
    this.configuration = configuration;
    configEntryDN = configuration.dn();
    try
    {
      DN identityMapperDN = configuration.getIdentityMapperDN();
      identityMapper = DirectoryServer.getIdentityMapper(identityMapperDN);
      serverFQDN = getFQDN(configuration);
      Message msg = INFO_GSSAPI_SERVER_FQDN.get(serverFQDN);
      logError(msg);
      saslProps = new HashMap<String, String>();
      saslProps.put(Sasl.QOP, getQOP(configuration));
      saslProps.put(Sasl.REUSE, "false");
      String configFileName = configureLoginConfFile(configuration);
      System.setProperty(JAAS_PROPERTY_CONFIG_FILE, configFileName);
      System.setProperty(JAAS_PROPERTY_SUBJECT_CREDS_ONLY, "false");
      getKdcRealm(configuration);
      DirectoryServer.registerSASLMechanismHandler(SASL_MECHANISM_GSSAPI, this);
      login();
    }
    catch (UnknownHostException unhe)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, unhe);
      }
      Message message = ERR_SASL_CANNOT_GET_SERVER_FQDN.get(String
          .valueOf(configEntryDN), getExceptionMessage(unhe));
      throw new InitializationException(message, unhe);
    }
    catch (IOException ioe)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
      }
      Message message = ERR_SASLGSSAPI_CANNOT_CREATE_JAAS_CONFIG
          .get(getExceptionMessage(ioe));
      throw new InitializationException(message, ioe);
    }
    catch (LoginException le)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, le);
      }
      Message message = ERR_SASLGSSAPI_CANNOT_CREATE_LOGIN_CONTEXT
          .get(getExceptionMessage(le));
      throw new InitializationException(message, le);
    }
  }
  /**
   * Checks to make sure that the ds-cfg-kdc-address and dc-cfg-realm are
   * both defined in the configuration. If only one is set, then that is an
   * error. If both are defined, or, both are null that is fine.
   * Checks to make sure that the ds-cfg-kdc-address and dc-cfg-realm
   * are both defined in the configuration. If only one is set, then
   * that is an error. If both are defined, or, both are null that is
   * fine.
   *
   * @param configuration The configuration to use.
   * @throws InitializationException If the properties violate the requirements.
   * @param configuration
   *          The configuration to use.
   * @throws InitializationException
   *           If the properties violate the requirements.
   */
  private void getKdcRealm(GSSAPISASLMechanismHandlerCfg configuration)
  throws InitializationException {
      String kdcAddress = configuration.getKdcAddress();
      String realm = configuration.getRealm();
      if((kdcAddress != null && realm == null) ||
         (kdcAddress == null && realm != null)) {
          Message message = ERR_SASLGSSAPI_KDC_REALM_NOT_DEFINED.get();
          throw new InitializationException(message);
      } else if(kdcAddress != null && realm != null) {
          System.setProperty(KRBV_PROPERTY_KDC, kdcAddress);
          System.setProperty(KRBV_PROPERTY_REALM, realm);
      throws InitializationException
  {
    String kdcAddress = configuration.getKdcAddress();
    String realm = configuration.getRealm();
    if ((kdcAddress != null && realm == null)
        || (kdcAddress == null && realm != null))
    {
      Message message = ERR_SASLGSSAPI_KDC_REALM_NOT_DEFINED.get();
      throw new InitializationException(message);
    }
    else if (kdcAddress != null && realm != null)
    {
      System.setProperty(KRBV_PROPERTY_KDC, kdcAddress);
      System.setProperty(KRBV_PROPERTY_REALM, realm);
      }
    }
  }
  /**
   * During login, callbacks are usually used to prompt for passwords. All of
   * the GSSAPI login information is provided in the properties and login.conf
   * file, so callbacks are ignored.
   * During login, callbacks are usually used to prompt for passwords.
   * All of the GSSAPI login information is provided in the properties
   * and login.conf file, so callbacks are ignored.
   *
   * @param callbacks An array of callbacks to process.
   * @throws UnsupportedCallbackException if an error occurs.
   * @param callbacks
   *          An array of callbacks to process.
   * @throws UnsupportedCallbackException
   *           if an error occurs.
   */
  public void handle(Callback[] callbacks)
  throws UnsupportedCallbackException {
  public void handle(Callback[] callbacks) throws UnsupportedCallbackException
  {
  }
  /**
   * Returns the fully qualified name either defined in the configuration, or,
   * determined by examining the system configuration.
   * Returns the fully qualified name either defined in the
   * configuration, or, determined by examining the system
   * configuration.
   *
   * @param configuration The configuration to check.
   * @param configuration
   *          The configuration to check.
   * @return The fully qualified hostname of the server.
   *
   * @throws UnknownHostException If the name cannot be determined from the
   *                              system configuration.
   * @throws UnknownHostException
   *           If the name cannot be determined from the system
   *           configuration.
   */
  private String getFQDN(GSSAPISASLMechanismHandlerCfg configuration)
  throws UnknownHostException {
      String serverName = configuration.getServerFqdn();
      if (serverName == null) {
              serverName = InetAddress.getLocalHost().getCanonicalHostName();
      }
      return serverName;
      throws UnknownHostException
  {
    String serverName = configuration.getServerFqdn();
    if (serverName == null)
    {
      serverName = InetAddress.getLocalHost().getCanonicalHostName();
    }
    return serverName;
  }
  /**
   * Create a login context or login using the principal and keytab information
   * specified in the configuration.
   * Create a login context or login using the principal and keytab
   * information specified in the configuration.
   *
   * @throws LoginException If a login context cannot be created.
   * @throws LoginException
   *           If a login context cannot be created.
   */
  private void login() throws LoginException {
      loginContext =
          new LoginContext(GSSAPISASLMechanismHandler.class.getName(), this);
      loginContext.login();
  private void login() throws LoginException
  {
    loginContext = new LoginContext(GSSAPISASLMechanismHandler.class.getName(),
        this);
    loginContext.login();
  }
  /**
   * Logout of the current login context.
   *
   */
  private void logout() {
      try {
          loginContext.logout();
      } catch (LoginException e) {
          if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
  private void logout()
  {
    try
    {
      loginContext.logout();
    }
    catch (LoginException e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
  }
  /**
   * Creates an login.conf file from information in the specified configuration.
   * This file is used during the login phase.
   * Creates an login.conf file from information in the specified
   * configuration. This file is used during the login phase.
   *
   * @param configuration The new configuration to use.
   * @param configuration
   *          The new configuration to use.
   * @return The filename of the new configuration file.
   *
   * @throws IOException If the configuration file cannot be created.
   * @throws IOException
   *           If the configuration file cannot be created.
   */
  private String
  configureLoginConfFile(GSSAPISASLMechanismHandlerCfg configuration)
  throws IOException {
      String configFileName;
      File tempFile = File.createTempFile("login", "conf");
      configFileName = tempFile.getAbsolutePath();
      tempFile.deleteOnExit();
      BufferedWriter w = new BufferedWriter(new FileWriter(tempFile, false));
      w.write(getClass().getName() + " {");
      w.newLine();
      w.write("  com.sun.security.auth.module.Krb5LoginModule required " +
      "storeKey=true useKeyTab=true ");
      String keyTabFile = configuration.getKeytab();
      if (keyTabFile != null) {
          w.write("keyTab=\"" + keyTabFile + "\" ");
      }
      StringBuilder principal= new StringBuilder();
      String principalName = configuration.getPrincipalName();
      String realm = configuration.getRealm();
      if(principalName != null) {
          principal.append("principal=\"" + principalName);
      } else {
          principal.append("principal=\"ldap/" + serverFQDN);
      }
      if (realm != null) {
          principal.append("@" + realm);
      }
      w.write(principal.toString());
      Message msg =  INFO_GSSAPI_PRINCIPAL_NAME.get(principal.toString());
      logError(msg);
      w.write("\";");
      w.newLine();
      w.write("};");
      w.newLine();
      w.flush();
      w.close();
      return configFileName;
  private String configureLoginConfFile(
      GSSAPISASLMechanismHandlerCfg configuration) throws IOException
  {
    String configFileName;
    File tempFile = File.createTempFile("login", "conf");
    configFileName = tempFile.getAbsolutePath();
    tempFile.deleteOnExit();
    BufferedWriter w = new BufferedWriter(new FileWriter(tempFile, false));
    w.write(getClass().getName() + " {");
    w.newLine();
    w.write("  com.sun.security.auth.module.Krb5LoginModule required "
        + "storeKey=true useKeyTab=true ");
    String keyTabFile = configuration.getKeytab();
    if (keyTabFile != null)
    {
      w.write("keyTab=\"" + keyTabFile + "\" ");
    }
    StringBuilder principal = new StringBuilder();
    String principalName = configuration.getPrincipalName();
    String realm = configuration.getRealm();
    if (principalName != null)
    {
      principal.append("principal=\"" + principalName);
    }
    else
    {
      principal.append("principal=\"ldap/" + serverFQDN);
    }
    if (realm != null)
    {
      principal.append("@" + realm);
    }
    w.write(principal.toString());
    Message msg = INFO_GSSAPI_PRINCIPAL_NAME.get(principal.toString());
    logError(msg);
    w.write("\";");
    w.newLine();
    w.write("};");
    w.newLine();
    w.flush();
    w.close();
    return configFileName;
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void finalizeSASLMechanismHandler() {
      logout();
      configuration.removeGSSAPIChangeListener(this);
      DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_GSSAPI);
  public void finalizeSASLMechanismHandler()
  {
    logout();
    configuration.removeGSSAPIChangeListener(this);
    DirectoryServer.deregisterSASLMechanismHandler(SASL_MECHANISM_GSSAPI);
  }
  /**
   * {@inheritDoc}
   */
  @Override()
  public void processSASLBind(BindOperation bindOp) {
      ClientConnection clientConnection = bindOp.getClientConnection();
      if (clientConnection == null) {
          Message message = ERR_SASLGSSAPI_NO_CLIENT_CONNECTION.get();
          bindOp.setAuthFailureReason(message);
          bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
          return;
  public void processSASLBind(BindOperation bindOp)
  {
    ClientConnection clientConnection = bindOp.getClientConnection();
    if (clientConnection == null)
    {
      Message message = ERR_SASLGSSAPI_NO_CLIENT_CONNECTION.get();
      bindOp.setAuthFailureReason(message);
      bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
      return;
    }
    ClientConnection clientConn = bindOp.getClientConnection();
    SASLContext saslContext = (SASLContext) clientConn.getSASLAuthStateInfo();
    if (saslContext == null)
    {
      try
      {
        saslContext = SASLContext.createSASLContext(saslProps, serverFQDN,
            SASL_MECHANISM_GSSAPI, identityMapper);
      }
      ClientConnection clientConn  = bindOp.getClientConnection();
      SASLContext saslContext = (SASLContext) clientConn.getSASLAuthStateInfo();
      if(saslContext == null) {
          try {
              saslContext = SASLContext.createSASLContext(saslProps, serverFQDN,
                                        SASL_MECHANISM_GSSAPI, identityMapper);
          } catch (SaslException ex) {
              if (debugEnabled()) {
                  TRACER.debugCaught(DebugLogLevel.ERROR, ex);
               }
              Message msg =
                  ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI,
                                                    getExceptionMessage(ex));
              clientConn.setSASLAuthStateInfo(null);
              bindOp.setAuthFailureReason(msg);
              bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
              return;
          }
      catch (SaslException ex)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, ex);
        }
        Message msg = ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI,
            getExceptionMessage(ex));
        clientConn.setSASLAuthStateInfo(null);
        bindOp.setAuthFailureReason(msg);
        bindOp.setResultCode(ResultCode.INVALID_CREDENTIALS);
        return;
      }
      saslContext.performAuthentication(loginContext, bindOp);
    }
    saslContext.performAuthentication(loginContext, bindOp);
  }
  /**
   * Retrieves the user account for the user associated with the provided
   * authorization ID.
   * Retrieves the user account for the user associated with the
   * provided authorization ID.
   *
   * @param  bindOperation  The bind operation from which the provided
   *                        authorization ID was derived.
   * @param  authzID        The authorization ID for which to retrieve the
   *                        associated user.
   *
   * @return  The user entry for the user with the specified authorization ID,
   *          or {@code null} if none is identified.
   *
   * @throws  DirectoryException  If a problem occurs while searching the
   *                              directory for the associated user, or if
   *                              multiple matching entries are found.
   * @param bindOperation
   *          The bind operation from which the provided authorization
   *          ID was derived.
   * @param authzID
   *          The authorization ID for which to retrieve the
   *          associated user.
   * @return The user entry for the user with the specified
   *         authorization ID, or {@code null} if none is identified.
   * @throws DirectoryException
   *           If a problem occurs while searching the directory for
   *           the associated user, or if multiple matching entries
   *           are found.
   */
  public Entry getUserForAuthzID(BindOperation bindOperation, String authzID)
         throws DirectoryException
      throws DirectoryException
  {
    return identityMapper.getEntryForID(authzID);
  }
@@ -397,11 +459,10 @@
   */
  @Override()
  public boolean isConfigurationAcceptable(
                      SASLMechanismHandlerCfg configuration,
                      List<Message> unacceptableReasons)
      SASLMechanismHandlerCfg configuration, List<Message> unacceptableReasons)
  {
    GSSAPISASLMechanismHandlerCfg config =
         (GSSAPISASLMechanismHandlerCfg) configuration;
      (GSSAPISASLMechanismHandlerCfg) configuration;
    return isConfigurationChangeAcceptable(config, unacceptableReasons);
  }
@@ -411,48 +472,52 @@
   * {@inheritDoc}
   */
  public boolean isConfigurationChangeAcceptable(
                      GSSAPISASLMechanismHandlerCfg configuration,
                      List<Message> unacceptableReasons)
      GSSAPISASLMechanismHandlerCfg configuration,
      List<Message> unacceptableReasons)
  {
    return true;
  }
  /**
   * {@inheritDoc}
   */
  public ConfigChangeResult applyConfigurationChange(
          GSSAPISASLMechanismHandlerCfg configuration) {
      ResultCode        resultCode          = ResultCode.SUCCESS;
      boolean           adminActionRequired = false;
      ArrayList<Message> messages            = new ArrayList<Message>();
      DN identityMapperDN = configuration.getIdentityMapperDN();
      IdentityMapper<?> newIdentityMapper =
          DirectoryServer.getIdentityMapper(identityMapperDN);
      identityMapper = newIdentityMapper;
      saslProps = new HashMap<String,String>();
      saslProps.put(Sasl.QOP, getQOP(configuration));
      saslProps.put(Sasl.REUSE, "false");
      this.configuration  = configuration;
      return new ConfigChangeResult(resultCode, adminActionRequired, messages);
      GSSAPISASLMechanismHandlerCfg configuration)
  {
    ResultCode resultCode = ResultCode.SUCCESS;
    boolean adminActionRequired = false;
    ArrayList<Message> messages = new ArrayList<Message>();
    DN identityMapperDN = configuration.getIdentityMapperDN();
    IdentityMapper<?> newIdentityMapper = DirectoryServer
        .getIdentityMapper(identityMapperDN);
    identityMapper = newIdentityMapper;
    saslProps = new HashMap<String, String>();
    saslProps.put(Sasl.QOP, getQOP(configuration));
    saslProps.put(Sasl.REUSE, "false");
    this.configuration = configuration;
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * Retrieves the QOP (quality-of-protection) from the specified
   * configuration.
   *
   * @param configuration The new configuration to use.
   * @param configuration
   *          The new configuration to use.
   * @return A string representing the quality-of-protection.
   */
  private String
  getQOP(GSSAPISASLMechanismHandlerCfg configuration) {
      QualityOfProtection QOP = configuration.getQualityOfProtection();
      if(QOP.equals(QualityOfProtection.CONFIDENTIALITY))
          return "auth-conf";
      else if(QOP.equals(QualityOfProtection.INTEGRITY))
          return "auth-int";
      else
          return "auth";
  private String getQOP(GSSAPISASLMechanismHandlerCfg configuration)
  {
    QualityOfProtection QOP = configuration.getQualityOfProtection();
    if (QOP.equals(QualityOfProtection.CONFIDENTIALITY))
      return "auth-conf";
    else if (QOP.equals(QualityOfProtection.INTEGRITY))
      return "auth-int";
    else
      return "auth";
  }
}