570 lines · 20.6 KB · Java
RawDownload
/* * Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.jmx.remote.security; import com.sun.jmx.mbeanserver.GetPropertyAction;import com.sun.jmx.mbeanserver.Util;import java.io.BufferedInputStream;import java.io.File;import java.io.FileInputStream;import java.io.FilePermission;import java.io.IOException;import java.security.AccessControlException;import java.security.AccessController;import java.util.Arrays;import java.util.Hashtable;import java.util.Map;import java.util.Properties; import javax.security.auth.*;import javax.security.auth.callback.*;import javax.security.auth.login.*;import javax.security.auth.spi.*;import javax.management.remote.JMXPrincipal; import com.sun.jmx.remote.util.ClassLogger;import com.sun.jmx.remote.util.EnvHelp;import sun.management.jmxremote.ConnectorBootstrap; /** * This {@link LoginModule} performs file-based authentication. * * <p> A supplied username and password is verified against the * corresponding user credentials stored in a designated password file. * If successful then a new {@link JMXPrincipal} is created with the * user's name and it is associated with the current {@link Subject}. * Such principals may be identified and granted management privileges in * the access control file for JMX remote management or in a Java security * policy. * * <p> The password file comprises a list of key-value pairs as specified in * {@link Properties}. The key represents a user's name and the value is its * associated cleartext password. By default, the following password file is * used: * <pre> *     ${java.home}/lib/management/jmxremote.password * </pre> * A different password file can be specified via the <code>passwordFile</code> * configuration option. * * <p> This module recognizes the following <code>Configuration</code> options: * <dl> * <dt> <code>passwordFile</code> </dt> * <dd> the path to an alternative password file. It is used instead of *      the default password file.</dd> * * <dt> <code>useFirstPass</code> </dt> * <dd> if <code>true</code>, this module 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.</dd> * * <dt> <code>tryFirstPass</code> </dt> * <dd> if <code>true</code>, this module 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, *      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.</dd> * * <dt> <code>storePass</code> </dt> * <dd> if <code>true</code>, this module 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.</dd> * * <dt> <code>clearPass</code> </dt> * <dd> if <code>true</code>, this module clears the username and password *      stored in the module's shared state after both phases of authentication *      (login and commit) have completed.</dd> * </dl> */public class FileLoginModule implements LoginModule {     // Location of the default password file    private static final String DEFAULT_PASSWORD_FILE_NAME =        AccessController.doPrivileged(new GetPropertyAction("java.home")) +        File.separatorChar + "lib" +        File.separatorChar + "management" + File.separatorChar +        ConnectorBootstrap.DefaultValues.PASSWORD_FILE_NAME;     // Key to retrieve the stored username    private static final String USERNAME_KEY =        "javax.security.auth.login.name";     // Key to retrieve the stored password    private static final String PASSWORD_KEY =        "javax.security.auth.login.password";     // Log messages    private static final ClassLogger logger =        new ClassLogger("javax.management.remote.misc", "FileLoginModule");     // Configurable options    private boolean useFirstPass = false;    private boolean tryFirstPass = false;    private boolean storePass = false;    private boolean clearPass = false;     // Authentication status    private boolean succeeded = false;    private boolean commitSucceeded = false;     // Supplied username and password    private String username;    private char[] password;    private JMXPrincipal user;     // Initial state    private Subject subject;    private CallbackHandler callbackHandler;    private Map<String, Object> sharedState;    private Map<String, ?> options;    private String passwordFile;    private String passwordFileDisplayName;    private boolean userSuppliedPasswordFile;    private boolean hasJavaHomePermission;    private Properties userCredentials;     /**     * Initialize this <code>LoginModule</code>.     *     * @param subject the <code>Subject</code> to be authenticated.     * @param callbackHandler a <code>CallbackHandler</code> to acquire the     *                  user's name and password.     * @param sharedState shared <code>LoginModule</code> state.     * @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 = Util.cast(sharedState);        this.options = options;         // initialize any configured options        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"));         passwordFile = (String)options.get("passwordFile");        passwordFileDisplayName = passwordFile;        userSuppliedPasswordFile = true;         // set the location of the password file        if (passwordFile == null) {            passwordFile = DEFAULT_PASSWORD_FILE_NAME;            userSuppliedPasswordFile = false;            try {                System.getProperty("java.home");                hasJavaHomePermission = true;                passwordFileDisplayName = passwordFile;            } catch (SecurityException e) {                hasJavaHomePermission = false;                passwordFileDisplayName =                        ConnectorBootstrap.DefaultValues.PASSWORD_FILE_NAME;            }        }    }     /**     * Begin user authentication (Authentication Phase 1).     *     * <p> Acquire the user's name and password and verify them against     * the corresponding credentials from the password file.     *     * @return true always, since this <code>LoginModule</code>     *          should not be ignored.     * @exception FailedLoginException if the authentication fails.     * @exception LoginException if this <code>LoginModule</code>     *          is unable to perform the authentication.     */    public boolean login() throws LoginException {         try {            loadPasswordFile();        } catch (IOException ioe) {            LoginException le = new LoginException(                    "Error: unable to load the password file: " +                    passwordFileDisplayName);            throw EnvHelp.initCause(le, ioe);        }         if (userCredentials == null) {            throw new LoginException                ("Error: unable to locate the users' credentials.");        }         if (logger.debugOn()) {            logger.debug("login",                    "Using password file: " + passwordFileDisplayName);        }         // 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 (logger.debugOn()) {                    logger.debug("login",                        "Authentication using cached password has succeeded");                }                return true;             } catch (LoginException le) {                // authentication failed -- try again below by prompting                cleanState();                logger.debug("login",                    "Authentication using cached password has failed");            }         } else if (useFirstPass) {             try {                // attempt the authentication by getting the                // username and password from shared state                attemptAuthentication(true);                 // authentication succeeded                succeeded = true;                if (logger.debugOn()) {                    logger.debug("login",                        "Authentication using cached password has succeeded");                }                return true;             } catch (LoginException le) {                // authentication failed                cleanState();                logger.debug("login",                    "Authentication using cached password has failed");                 throw le;            }        }         if (logger.debugOn()) {            logger.debug("login", "Acquiring password");        }         // attempt the authentication using the supplied username and password        try {            attemptAuthentication(false);             // authentication succeeded            succeeded = true;            if (logger.debugOn()) {                logger.debug("login", "Authentication has succeeded");            }            return true;         } catch (LoginException le) {            cleanState();            logger.debug("login", "Authentication has failed");             throw le;        }    }     /**     * Complete user authentication (Authentication Phase 2).     *     * <p> This method is called if the LoginContext's     * overall authentication has succeeded     * (all the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL     * LoginModules have 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>JMXPrincipal</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.     *     * @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 read-only");            }            // add Principals to the Subject            if (!subject.getPrincipals().contains(user)) {                subject.getPrincipals().add(user);            }             if (logger.debugOn()) {                logger.debug("commit",                    "Authentication has completed successfully");            }        }        // in any case, clean out state        cleanState();        commitSucceeded = true;        return true;    }     /**     * Abort user authentication (Authentication Phase 2).     *     * <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.     *     * @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 (logger.debugOn()) {            logger.debug("abort",                "Authentication has not completed successfully");        }         if (succeeded == false) {            return false;        } else if (succeeded == true && commitSucceeded == false) {             // Clean out state            succeeded = false;            cleanState();            user = null;        } 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.     *     * @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 read-only");        }        subject.getPrincipals().remove(user);         // clean out state        cleanState();        succeeded = false;        commitSucceeded = false;        user = null;         if (logger.debugOn()) {            logger.debug("logout", "Subject is being logged out");        }         return true;    }     /**     * Attempt authentication     *     * @param usePasswdFromSharedState a flag to tell this method whether     *          to retrieve the password from the sharedState.     */    @SuppressWarnings("unchecked")  // sharedState used as Map<String,Object>    private void attemptAuthentication(boolean usePasswdFromSharedState)        throws LoginException {         // get the username and password        getUsernamePassword(usePasswdFromSharedState);         String localPassword;         // userCredentials is initialized in login()        if (((localPassword = userCredentials.getProperty(username)) == null) ||            (! localPassword.equals(new String(password)))) {             // username not found or passwords do not match            if (logger.debugOn()) {                logger.debug("login", "Invalid username or password");            }            throw new FailedLoginException("Invalid username or password");        }         // Save the username and password in the shared state        // only if authentication succeeded        if (storePass &&            !sharedState.containsKey(USERNAME_KEY) &&            !sharedState.containsKey(PASSWORD_KEY)) {            sharedState.put(USERNAME_KEY, username);            sharedState.put(PASSWORD_KEY, password);        }         // Create a new user principal        user = new JMXPrincipal(username);         if (logger.debugOn()) {            logger.debug("login",                "User '" + username + "' successfully validated");        }    }     /*     * Read the password file.     */    private void loadPasswordFile() throws IOException {        FileInputStream fis;        try {            fis = new FileInputStream(passwordFile);        } catch (SecurityException e) {            if (userSuppliedPasswordFile || hasJavaHomePermission) {                throw e;            } else {                final FilePermission fp =                        new FilePermission(passwordFileDisplayName, "read");                AccessControlException ace = new AccessControlException(                        "access denied " + fp.toString());                ace.setStackTrace(e.getStackTrace());                throw ace;            }        }        try {            final BufferedInputStream bis = new BufferedInputStream(fis);            try {                userCredentials = new Properties();                userCredentials.load(bis);            } finally {                bis.close();            }        } finally {            fis.close();        }

Lines 1–500 of 570.