Package com.sun.security.auth.module

Source Code of com.sun.security.auth.module.JndiLoginModule

/*
* @(#)JndiLoginModule.java  1.12 05/11/17
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/

package com.sun.security.auth.module;

import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import javax.naming.*;
import javax.naming.directory.*;

import java.io.IOException;
import java.util.Map;
import java.util.LinkedList;
import java.util.ResourceBundle;

import com.sun.security.auth.UnixPrincipal;
import com.sun.security.auth.UnixNumericUserPrincipal;
import com.sun.security.auth.UnixNumericGroupPrincipal;

import sun.security.util.AuthResources;

/**
* <p> The module prompts for a username and password
* and then verifies the password against the password stored in
* a directory service configured under JNDI.
*
* <p> This <code>LoginModule</code> interoperates with
* any conformant JNDI service provider.  To direct this
* <code>LoginModule</code> to use a specific JNDI service provider,
* two options must be specified in the login <code>Configuration</code>
* for this <code>LoginModule</code>.
* <pre>
*  user.provider.url=<b>name_service_url</b>
*  group.provider.url=<b>name_service_url</b>
* </pre>
*
* <b>name_service_url</b> specifies
* the directory service and path where this <code>LoginModule</code>
* can access the relevant user and group information.  Because this
* <code>LoginModule</code> only performs one-level searches to
* find the relevant user information, the <code>URL</code>
* must point to a directory one level above where the user and group
* information is stored in the directory service.
* For example, to instruct this <code>LoginModule</code>
* to contact a NIS server, the following URLs must be specified:
* <pre>
*    user.provider.url="nis://<b>NISServerHostName</b>/<b>NISDomain</b>/user"
*    group.provider.url="nis://<b>NISServerHostName</b>/<b>NISDomain</b>/system/group"
* </pre>
*
* <b>NISServerHostName</b> specifies the server host name of the
* NIS server (for example, <i>nis.sun.com</i>, and <b>NISDomain</b>
* specifies the domain for that NIS server (for example, <i>jaas.sun.com</i>.
* To contact an LDAP server, the following URLs must be specified:
* <pre>
*    user.provider.url="ldap://<b>LDAPServerHostName</b>/<b>LDAPName</b>"
*    group.provider.url="ldap://<b>LDAPServerHostName</b>/<b>LDAPName</b>"
* </pre>
*
* <b>LDAPServerHostName</b> specifies the server host name of the
* LDAP server, which may include a port number
* (for example, <i>ldap.sun.com:389</i>),
* and <b>LDAPName</b> specifies the entry name in the LDAP directory
* (for example, <i>ou=People,o=Sun,c=US</i> and <i>ou=Groups,o=Sun,c=US</i>
* for user and group information, respectively).
*
* <p> The format in which the user's information must be stored in
* the directory service is specified in RFC 2307.  Specifically,
* this <code>LoginModule</code> will search for the user's entry in the
* directory service using the user's <i>uid</i> attribute,
* where <i>uid=<b>username</b></i>.  If the search succeeds,
* this <code>LoginModule</code> will then
* obtain the user's encrypted password from the retrieved entry
* using the <i>userPassword</i> attribute.
* This <code>LoginModule</code> assumes that the password is stored
* as a byte array, which when converted to a <code>String</code>,
* has the following format:
* <pre>
*  "{crypt}<b>encrypted_password</b>"
* </pre>
*
* The LDAP directory server must be configured
* to permit read access to the userPassword attribute.
* If the user entered a valid username and password,
* this <code>LoginModule</code> associates a
* <code>UnixPrincipal</code>, <code>UnixNumericUserPrincipal</code>,
* and the relevant UnixNumericGroupPrincipals with the
* <code>Subject</code>.
*
* <p> This LoginModule also recognizes the following <code>Configuration</code>
* options:
* <pre>
*    debug          if, true, debug messages are output to System.out.
*
*    useFirstPass   if, true, this LoginModule retrieves the
*                   username and password from the module's shared state,
*                   using "javax.security.auth.login.name" and
*                   "javax.security.auth.login.password" as the respective
*                   keys.  The retrieved values are used for authentication.
*                   If authentication fails, no attempt for a retry is made,
*                   and the failure is reported back to the calling
*                   application.
*
*    tryFirstPass   if, true, this LoginModule retrieves the
*                   the username and password from the module's shared state,
*                   using "javax.security.auth.login.name" and
*                   "javax.security.auth.login.password" as the respective
*                   keys.  The retrieved values are used for authentication.
*                   If authentication fails, the module uses the
*                   CallbackHandler to retrieve a new username and password,
*                   and another attempt to authenticate is made.
*                   If the authentication fails, the failure is reported
*                   back to the calling application.
*
*    storePass      if, true, this LoginModule stores the username and password
*                   obtained from the CallbackHandler in the module's
*                   shared state, using "javax.security.auth.login.name" and
*                   "javax.security.auth.login.password" as the respective
*                   keys.  This is not performed if existing values already
*                   exist for the username and password in the shared state,
*                   or if authentication fails.
*
*    clearPass     if, true, this <code>LoginModule</code> clears the
*                  username and password stored in the module's shared state
*                  after both phases of authentication (login and commit)
*                  have completed.
* </pre>
*
* @version 1.12, 11/17/05
*/
public class JndiLoginModule implements LoginModule {

    static final java.util.ResourceBundle rb =
        java.util.ResourceBundle.getBundle("sun.security.util.AuthResources");

    /** JNDI Provider */
    public final String USER_PROVIDER = "user.provider.url";
    public final String GROUP_PROVIDER = "group.provider.url";

    // configurable options
    private boolean debug = false;
    private boolean strongDebug = false;
    private String userProvider;
    private String groupProvider;
    private boolean useFirstPass = false;
    private boolean tryFirstPass = false;
    private boolean storePass = false;
    private boolean clearPass = false;

    // the authentication status
    private boolean succeeded = false;
    private boolean commitSucceeded = false;

    // username, password, and JNDI context
    private String username;
    private char[] password;
    DirContext ctx;

    // the user (assume it is a UnixPrincipal)
    private UnixPrincipal userPrincipal;
    private UnixNumericUserPrincipal UIDPrincipal;
    private UnixNumericGroupPrincipal GIDPrincipal;
    private LinkedList supplementaryGroups = new LinkedList();

    // initial state
    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map sharedState;
    private Map options;

    private static final String CRYPT = "{crypt}";
    private static final String USER_PWD = "userPassword";
    private static final String USER_UID = "uidNumber";
    private static final String USER_GID = "gidNumber";
    private static final String GROUP_ID = "gidNumber";
    private static final String NAME = "javax.security.auth.login.name";
    private static final String PWD = "javax.security.auth.login.password";

    /**
     * Initialize this <code>LoginModule</code>.
     *
     * <p>
     *
     * @param subject the <code>Subject</code> to be authenticated. <p>
     *
     * @param callbackHandler a <code>CallbackHandler</code> for communicating
     *      with the end user (prompting for usernames and
     *      passwords, for example). <p>
     *
     * @param sharedState shared <code>LoginModule</code> state. <p>
     *
     * @param options options specified in the login
     *      <code>Configuration</code> for this particular
     *      <code>LoginModule</code>.
     */
    public void initialize(Subject subject, CallbackHandler callbackHandler,
         Map<String,?> sharedState,
         Map<String,?> options) {

  this.subject = subject;
  this.callbackHandler = callbackHandler;
  this.sharedState = sharedState;
  this.options = options;

  // initialize any configured options
  debug = "true".equalsIgnoreCase((String)options.get("debug"));
  strongDebug =
    "true".equalsIgnoreCase((String)options.get("strongDebug"));
  userProvider = (String)options.get(USER_PROVIDER);
  groupProvider = (String)options.get(GROUP_PROVIDER);
  tryFirstPass =
    "true".equalsIgnoreCase((String)options.get("tryFirstPass"));
  useFirstPass =
    "true".equalsIgnoreCase((String)options.get("useFirstPass"));
  storePass =
    "true".equalsIgnoreCase((String)options.get("storePass"));
  clearPass =
    "true".equalsIgnoreCase((String)options.get("clearPass"));
    }

    /**
     * <p> Prompt for username and password.
     * Verify the password against the relevant name service.
     *
     * <p>
     *
     * @return true always, since this <code>LoginModule</code>
     *    should not be ignored.
     *
     * @exception FailedLoginException if the authentication fails. <p>
     *
     * @exception LoginException if this <code>LoginModule</code>
     *    is unable to perform the authentication.
     */
    public boolean login() throws LoginException {

  if (userProvider == null) {
      throw new LoginException
    ("Error: Unable to locate JNDI user provider");
  }
  if (groupProvider == null) {
      throw new LoginException
    ("Error: Unable to locate JNDI group provider");
  }

  if (debug) {
      System.out.println("\t\t[JndiLoginModule] user provider: " +
        userProvider);
      System.out.println("\t\t[JndiLoginModule] group provider: " +
        groupProvider);
  }

  // attempt the authentication
  if (tryFirstPass) {

      try {
    // attempt the authentication by getting the
    // username and password from shared state
    attemptAuthentication(true);

    // authentication succeeded
    succeeded = true;
    if (debug) {
        System.out.println("\t\t[JndiLoginModule] " +
        "tryFirstPass succeeded");
    }
    return true;
      } catch (LoginException le) {
    // authentication failed -- try again below by prompting
    cleanState();
    if (debug) {
        System.out.println("\t\t[JndiLoginModule] " +
        "tryFirstPass failed with:" +
        le.toString());
    }
      }

  } else if (useFirstPass) {

      try {
    // attempt the authentication by getting the
    // username and password from shared state
    attemptAuthentication(true);

    // authentication succeeded
    succeeded = true;
    if (debug) {
        System.out.println("\t\t[JndiLoginModule] " +
        "useFirstPass succeeded");
    }
    return true;
      } catch (LoginException le) {
    // authentication failed
    cleanState();
    if (debug) {
        System.out.println("\t\t[JndiLoginModule] " +
        "useFirstPass failed");
    }
    throw le;
      }
  }

  // attempt the authentication by prompting for the username and pwd
  try {
      attemptAuthentication(false);

      // authentication succeeded
     succeeded = true;
      if (debug) {
    System.out.println("\t\t[JndiLoginModule] " +
        "regular authentication succeeded");
      }
      return true;
  } catch (LoginException le) {
      cleanState();
      if (debug) {
    System.out.println("\t\t[JndiLoginModule] " +
        "regular authentication failed");
      }
      throw le;
  }
    }

    /**
     * Abstract method to commit the authentication process (phase 2).
     *
     * <p> This method is called if the LoginContext's
     * overall authentication succeeded
     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
     * succeeded).
     *
     * <p> If this LoginModule's own authentication attempt
     * succeeded (checked by retrieving the private state saved by the
     * <code>login</code> method), then this method associates a
     * <code>UnixPrincipal</code>
     * with the <code>Subject</code> located in the
     * <code>LoginModule</code>.  If this LoginModule's own
     * authentication attempted failed, then this method removes
     * any state that was originally saved.
     *
     * <p>
     *
     * @exception LoginException if the commit fails
     *
     * @return true if this LoginModule's own login and commit
     *    attempts succeeded, or false otherwise.
     */
    public boolean commit() throws LoginException {

  if (succeeded == false) {
      return false;
  } else {
      if (subject.isReadOnly()) {
    cleanState();
    throw new LoginException ("Subject is Readonly");
      }
      // add Principals to the Subject
      if (!subject.getPrincipals().contains(userPrincipal))
    subject.getPrincipals().add(userPrincipal);
      if (!subject.getPrincipals().contains(UIDPrincipal))
    subject.getPrincipals().add(UIDPrincipal);
      if (!subject.getPrincipals().contains(GIDPrincipal))
    subject.getPrincipals().add(GIDPrincipal);
      for (int i = 0; i < supplementaryGroups.size(); i++) {
    if (!subject.getPrincipals().contains
        ((UnixNumericGroupPrincipal)supplementaryGroups.get(i)))
        subject.getPrincipals().add((UnixNumericGroupPrincipal)
            supplementaryGroups.get(i));
      }
     
      if (debug) {
    System.out.println("\t\t[JndiLoginModule]: " +
           "added UnixPrincipal,");
    System.out.println("\t\t\t\tUnixNumericUserPrincipal,");
    System.out.println("\t\t\t\tUnixNumericGroupPrincipal(s),");
    System.out.println("\t\t\t to Subject");
      }
  }
  // in any case, clean out state
  cleanState();
  commitSucceeded = true;
  return true;
    }

    /**
     * <p> This method is called if the LoginContext's
     * overall authentication failed.
     * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
     * did not succeed).
     *
     * <p> If this LoginModule's own authentication attempt
     * succeeded (checked by retrieving the private state saved by the
     * <code>login</code> and <code>commit</code> methods),
     * then this method cleans up any state that was originally saved.
     *
     * <p>
     *
     * @exception LoginException if the abort fails.
     *
     * @return false if this LoginModule's own login and/or commit attempts
     *    failed, and true otherwise.
     */
    public boolean abort() throws LoginException {
  if (debug)
      System.out.println("\t\t[JndiLoginModule]: " +
    "aborted authentication failed");

  if (succeeded == false) {
      return false;
  } else if (succeeded == true && commitSucceeded == false) {

      // Clean out state
      succeeded = false;
      cleanState();

      userPrincipal = null;
      UIDPrincipal = null;
      GIDPrincipal = null;
      supplementaryGroups = new LinkedList();
  } else {
      // overall authentication succeeded and commit succeeded,
      // but someone else's commit failed
      logout();
  }
  return true;
    }

    /**
     * Logout a user.
     *
     * <p> This method removes the Principals
     * that were added by the <code>commit</code> method.
     *
     * <p>
     *
     * @exception LoginException if the logout fails.
     *
     * @return true in all cases since this <code>LoginModule</code>
     *    should not be ignored.
     */
    public boolean logout() throws LoginException {
  if (subject.isReadOnly()) {
      cleanState();
      throw new LoginException ("Subject is Readonly");
  }
  subject.getPrincipals().remove(userPrincipal);
  subject.getPrincipals().remove(UIDPrincipal);
  subject.getPrincipals().remove(GIDPrincipal);
  for (int i = 0; i < supplementaryGroups.size(); i++) {
      subject.getPrincipals().remove
    ((UnixNumericGroupPrincipal)supplementaryGroups.get(i));
  }
   
   
  // clean out state
  cleanState();
  succeeded = false;
  commitSucceeded = false;

  userPrincipal = null;
  UIDPrincipal = null;
  GIDPrincipal = null;
  supplementaryGroups = new LinkedList();

  if (debug) {
      System.out.println("\t\t[JndiLoginModule]: " +
    "logged out Subject");
  }
  return true;
    }

    /**
     * Attempt authentication
     *
     * <p>
     *
     * @param getPasswdFromSharedState boolean that tells this method whether
     *    to retrieve the password from the sharedState.
     */
    private void attemptAuthentication(boolean getPasswdFromSharedState)
    throws LoginException {

  String encryptedPassword = null;

  // first get the username and password
  getUsernamePassword(getPasswdFromSharedState);
 
  try {

      // get the user's passwd entry from the user provider URL
      InitialContext iCtx = new InitialContext();
      ctx = (DirContext)iCtx.lookup(userProvider);

      /*
      SearchControls controls = new SearchControls
          (SearchControls.ONELEVEL_SCOPE,
          0,
          5000,
          new String[] { USER_PWD },
          false,
          false);
      */

      SearchControls controls = new SearchControls();
      NamingEnumeration ne = ctx.search("",
          "(uid=" + username + ")",
          controls);
      if (ne.hasMore()) {
    SearchResult result = (SearchResult)ne.next();
    Attributes attributes = result.getAttributes();

    // get the password

    // this module works only if the LDAP directory server
    // is configured to permit read access to the userPassword
    // attribute. The directory administrator need to grant
    // this access.
    //
    // A workaround would be to make the server do authentication
    // by setting the Context.SECURITY_PRINCIPAL
    // and Context.SECURITY_CREDENTIALS property.
    // However, this would make it not work with systems that
    // don't do authentication at the server (like NIS).
    //
    // Setting the SECURITY_* properties and using "simple"
    // authentication for LDAP is recommended only for secure
    // channels. For nonsecure channels, SSL is recommended.

    Attribute pwd = attributes.get(USER_PWD);
    String encryptedPwd = new String((byte[])pwd.get(), "UTF8");
    encryptedPassword = encryptedPwd.substring(CRYPT.length());

    // check the password
    if (verifyPassword
        (encryptedPassword, new String(password)) == true) {

        // authentication succeeded
        if (debug)
      System.out.println("\t\t[JndiLoginModule] " +
        "attemptAuthentication() succeeded");

    } else {
        // authentication failed
        if (debug)
      System.out.println("\t\t[JndiLoginModule] " +
        "attemptAuthentication() failed");
        throw new FailedLoginException("Login incorrect");
    }

    // save input as shared state only if
    // authentication succeeded
    if (storePass &&
        !sharedState.containsKey(NAME) &&
        !sharedState.containsKey(PWD)) {
        sharedState.put(NAME, username);
        sharedState.put(PWD, password);
    }

    // create the user principal
    userPrincipal = new UnixPrincipal(username);

    // get the UID
    Attribute uid = attributes.get(USER_UID);
    String uidNumber = (String)uid.get();
    UIDPrincipal = new UnixNumericUserPrincipal(uidNumber);
    if (debug && uidNumber != null) {
        System.out.println("\t\t[JndiLoginModule] " +
        "user: '" + username + "' has UID: " +
        uidNumber);
    }

    // get the GID
    Attribute gid = attributes.get(USER_GID);
    String gidNumber = (String)gid.get();
    GIDPrincipal = new UnixNumericGroupPrincipal
        (gidNumber, true);
    if (debug && gidNumber != null) {
        System.out.println("\t\t[JndiLoginModule] " +
        "user: '" + username + "' has GID: " +
        gidNumber);
    }

    // get the supplementary groups from the group provider URL
    ctx = (DirContext)iCtx.lookup(groupProvider);
    ne = ctx.search("", new BasicAttributes("memberUid", username));

    while (ne.hasMore()) {
        result = (SearchResult)ne.next();
        attributes = result.getAttributes();

        gid = attributes.get(GROUP_ID);
        String suppGid = (String)gid.get();
        if (!gidNumber.equals(suppGid)) {
      UnixNumericGroupPrincipal suppPrincipal =
          new UnixNumericGroupPrincipal(suppGid, false);
      supplementaryGroups.add(suppPrincipal);
      if (debug && suppGid != null) {
          System.out.println("\t\t[JndiLoginModule] " +
        "user: '" + username +
        "' has Supplementary Group: " +
        suppGid);
      }
        }
    }

      } else {
    // bad username
    if (debug) {
        System.out.println("\t\t[JndiLoginModule]: User not found");
    }
    throw new FailedLoginException("User not found");
      }
  } catch (NamingException ne) {
      // bad username
      if (debug) {
    System.out.println("\t\t[JndiLoginModule]:  User not found");
    ne.printStackTrace();
      }
      throw new FailedLoginException("User not found");
  } catch (java.io.UnsupportedEncodingException uee) {
      // password stored in incorrect format
      if (debug) {
    System.out.println("\t\t[JndiLoginModule]:  " +
        "password incorrectly encoded");
    uee.printStackTrace();
      }
      throw new LoginException("Login failure due to incorrect " +
        "password encoding in the password database");
  }

  // authentication succeeded
    }
    /**
     * Get the username and password.
     * This method does not return any value.
     * Instead, it sets global name and password variables.
     *
     * <p> Also note that this method will set the username and password
     * values in the shared state in case subsequent LoginModules
     * want to use them via use/tryFirstPass.
     *
     * <p>
     *
     * @param getPasswdFromSharedState boolean that tells this method whether
     *    to retrieve the password from the sharedState.
     */
    private void getUsernamePassword(boolean getPasswdFromSharedState)
    throws LoginException {

  if (getPasswdFromSharedState) {
      // use the password saved by the first module in the stack
      username = (String)sharedState.get(NAME);
      password = (char[])sharedState.get(PWD);
      return;
  }

  // prompt for a username and password
        if (callbackHandler == null)
      throw new LoginException("Error: no CallbackHandler available " +
    "to garner authentication information from the user");

  String protocol = userProvider.substring(0, userProvider.indexOf(":"));

  Callback[] callbacks = new Callback[2];
  callbacks[0] = new NameCallback(protocol + " "
              + rb.getString("username: "));
  callbacks[1] = new PasswordCallback(protocol + " " +
                   rb.getString("password: "),
              false);

  try {
      callbackHandler.handle(callbacks);
      username = ((NameCallback)callbacks[0]).getName();
      char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
      password = new char[tmpPassword.length];
      System.arraycopy(tmpPassword, 0,
        password, 0, tmpPassword.length);
      ((PasswordCallback)callbacks[1]).clearPassword();

  } catch (java.io.IOException ioe) {
      throw new LoginException(ioe.toString());
  } catch (UnsupportedCallbackException uce) {
      throw new LoginException("Error: " + uce.getCallback().toString() +
      " not available to garner authentication information " +
      "from the user");
  }

  // print debugging information
  if (strongDebug) {
      System.out.println("\t\t[JndiLoginModule] " +
        "user entered username: " +
        username);
      System.out.print("\t\t[JndiLoginModule] " +
        "user entered password: ");
      for (int i = 0; i < password.length; i++)
    System.out.print(password[i]);
      System.out.println();
  }
    }

    /**
     * Verify a password against the encrypted passwd from /etc/shadow
     */
    private boolean verifyPassword(String encryptedPassword, String password) {

  if (encryptedPassword == null)
      return false;

  Crypt c = new Crypt();
  try {
      byte oldCrypt[] = encryptedPassword.getBytes("UTF8");
      byte newCrypt[] = c.crypt(password.getBytes("UTF8"),
              oldCrypt);
      if (newCrypt.length != oldCrypt.length)
          return false;
      for (int i = 0; i < newCrypt.length; i++) {
          if (oldCrypt[i] != newCrypt[i])
        return false;
      }
  } catch (java.io.UnsupportedEncodingException uee) {
      // cannot happen, but return false just to be safe
      return false;
  }
  return true;
    }

    /**
     * Clean out state because of a failed authentication attempt
     */
    private void cleanState() {
  username = null;
  if (password != null) {
      for (int i = 0; i < password.length; i++)
    password[i] = ' ';
      password = null;
  }
  ctx = null;

  if (clearPass) {
      sharedState.remove(NAME);
      sharedState.remove(PWD);
  }
    }
}
TOP

Related Classes of com.sun.security.auth.module.JndiLoginModule

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.