/* | 
|
 * 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 java.io.IOException;  | 
|
import java.security.AccessController;  | 
|
import java.security.Principal;  | 
|
import java.security.PrivilegedAction;  | 
|
import java.security.PrivilegedActionException;  | 
|
import java.security.PrivilegedExceptionAction;  | 
|
import java.util.Collections;  | 
|
import java.util.HashMap;  | 
|
import java.util.Map;  | 
|
import java.util.Properties;  | 
|
import javax.management.remote.JMXPrincipal;  | 
|
import javax.management.remote.JMXAuthenticator;  | 
|
import javax.security.auth.AuthPermission;  | 
|
import javax.security.auth.Subject;  | 
|
import javax.security.auth.callback.*;  | 
|
import javax.security.auth.login.AppConfigurationEntry;  | 
|
import javax.security.auth.login.Configuration;  | 
|
import javax.security.auth.login.LoginContext;  | 
|
import javax.security.auth.login.LoginException;  | 
|
import javax.security.auth.spi.LoginModule;  | 
|
import com.sun.jmx.remote.util.ClassLogger;  | 
|
import com.sun.jmx.remote.util.EnvHelp;  | 
|
/** | 
|
 * <p>This class represents a | 
|
 * <a href="{@docRoot}/../guide/security/jaas/JAASRefGuide.html">JAAS</a> | 
|
 * based implementation of the {@link JMXAuthenticator} interface.</p> | 
|
 * | 
|
 * <p>Authentication is performed by passing the supplied user's credentials | 
|
 * to one or more authentication mechanisms ({@link LoginModule}) for | 
|
 * verification. An authentication mechanism acquires the user's credentials | 
|
 * by calling {@link NameCallback} and/or {@link PasswordCallback}. | 
|
 * If authentication is successful then an authenticated {@link Subject} | 
|
 * filled in with a {@link Principal} is returned.  Authorization checks | 
|
 * will then be performed based on this <code>Subject</code>.</p> | 
|
 * | 
|
 * <p>By default, a single file-based authentication mechanism | 
|
 * {@link FileLoginModule} is configured (<code>FileLoginConfig</code>).</p> | 
|
 * | 
|
 * <p>To override the default configuration use the | 
|
 * <code>com.sun.management.jmxremote.login.config</code> management property | 
|
 * described in the JRE/lib/management/management.properties file. | 
|
 * Set this property to the name of a JAAS configuration entry and ensure that | 
|
 * the entry is loaded by the installed {@link Configuration}. In addition, | 
|
 * ensure that the authentication mechanisms specified in the entry acquire | 
|
 * the user's credentials by calling {@link NameCallback} and | 
|
 * {@link PasswordCallback} and that they return a {@link Subject} filled-in | 
|
 * with a {@link Principal}, for those users that are successfully | 
|
 * authenticated.</p> | 
|
*/  | 
|
public final class JMXPluggableAuthenticator implements JMXAuthenticator {  | 
|
    /** | 
|
     * Creates an instance of <code>JMXPluggableAuthenticator</code> | 
|
     * and initializes it with a {@link LoginContext}. | 
|
     * | 
|
     * @param env the environment containing configuration properties for the | 
|
     *            authenticator. Can be null, which is equivalent to an empty | 
|
     *            Map. | 
|
     * @exception SecurityException if the authentication mechanism cannot be | 
|
     *            initialized. | 
|
*/  | 
|
public JMXPluggableAuthenticator(Map<?, ?> env) {  | 
|
String loginConfigName = null;  | 
|
String passwordFile = null;  | 
|
        if (env != null) { | 
|
loginConfigName = (String) env.get(LOGIN_CONFIG_PROP);  | 
|
passwordFile = (String) env.get(PASSWORD_FILE_PROP);  | 
|
}  | 
|
        try { | 
|
if (loginConfigName != null) {  | 
|
                // use the supplied JAAS login configuration | 
|
loginContext =  | 
|
new LoginContext(loginConfigName, new JMXCallbackHandler());  | 
|
            } else { | 
|
                // use the default JAAS login configuration (file-based) | 
|
SecurityManager sm = System.getSecurityManager();  | 
|
if (sm != null) {  | 
|
sm.checkPermission(  | 
|
new AuthPermission("createLoginContext." +  | 
|
LOGIN_CONFIG_NAME));  | 
|
}  | 
|
final String pf = passwordFile;  | 
|
                try { | 
|
loginContext = AccessController.doPrivileged(  | 
|
new PrivilegedExceptionAction<LoginContext>() {  | 
|
public LoginContext run() throws LoginException {  | 
|
return new LoginContext(  | 
|
LOGIN_CONFIG_NAME,  | 
|
null,  | 
|
new JMXCallbackHandler(),  | 
|
new FileLoginConfig(pf));  | 
|
}  | 
|
});  | 
|
} catch (PrivilegedActionException pae) {  | 
|
throw (LoginException) pae.getException();  | 
|
}  | 
|
}  | 
|
} catch (LoginException le) {  | 
|
authenticationFailure("authenticate", le);  | 
|
} catch (SecurityException se) {  | 
|
authenticationFailure("authenticate", se);  | 
|
}  | 
|
}  | 
|
    /** | 
|
     * Authenticate the <code>MBeanServerConnection</code> client | 
|
     * with the given client credentials. | 
|
     * | 
|
     * @param credentials the user-defined credentials to be passed in | 
|
     * to the server in order to authenticate the user before creating | 
|
     * the <code>MBeanServerConnection</code>.  This parameter must | 
|
     * be a two-element <code>String[]</code> containing the client's | 
|
     * username and password in that order. | 
|
     * | 
|
     * @return the authenticated subject containing a | 
|
     * <code>JMXPrincipal(username)</code>. | 
|
     * | 
|
     * @exception SecurityException if the server cannot authenticate the user | 
|
     * with the provided credentials. | 
|
*/  | 
|
public Subject authenticate(Object credentials) {  | 
|
// Verify that credentials is of type String[].  | 
|
        // | 
|
if (!(credentials instanceof String[])) {  | 
|
            // Special case for null so we get a more informative message | 
|
if (credentials == null)  | 
|
authenticationFailure("authenticate", "Credentials required");  | 
|
final String message =  | 
|
                "Credentials should be String[] instead of " + | 
|
credentials.getClass().getName();  | 
|
authenticationFailure("authenticate", message);  | 
|
}  | 
|
// Verify that the array contains two elements.  | 
|
        // | 
|
final String[] aCredentials = (String[]) credentials;  | 
|
if (aCredentials.length != 2) {  | 
|
final String message =  | 
|
                "Credentials should have 2 elements not " + | 
|
aCredentials.length;  | 
|
authenticationFailure("authenticate", message);  | 
|
}  | 
|
// Verify that username exists and the associated  | 
|
// password matches the one supplied by the client.  | 
|
        // | 
|
username = aCredentials[0];  | 
|
password = aCredentials[1];  | 
|
        if (username == null || password == null) { | 
|
final String message = "Username or password is null";  | 
|
authenticationFailure("authenticate", message);  | 
|
}  | 
|
        // Perform authentication | 
|
        try { | 
|
loginContext.login();  | 
|
final Subject subject = loginContext.getSubject();  | 
|
AccessController.doPrivileged(new PrivilegedAction<Void>() {  | 
|
public Void run() {  | 
|
subject.setReadOnly();  | 
|
return null;  | 
|
}  | 
|
});  | 
|
return subject;  | 
|
} catch (LoginException le) {  | 
|
authenticationFailure("authenticate", le);  | 
|
}  | 
|
return null;  | 
|
}  | 
|
private static void authenticationFailure(String method, String message)  | 
|
throws SecurityException {  | 
|
final String msg = "Authentication failed! " + message;  | 
|
final SecurityException e = new SecurityException(msg);  | 
|
logException(method, msg, e);  | 
|
throw e;  | 
|
}  | 
|
private static void authenticationFailure(String method,  | 
|
Exception exception)  | 
|
throws SecurityException {  | 
|
String msg;  | 
|
SecurityException se;  | 
|
if (exception instanceof SecurityException) {  | 
|
msg = exception.getMessage();  | 
|
se = (SecurityException) exception;  | 
|
        } else { | 
|
msg = "Authentication failed! " + exception.getMessage();  | 
|
final SecurityException e = new SecurityException(msg);  | 
|
EnvHelp.initCause(e, exception);  | 
|
se = e;  | 
|
}  | 
|
logException(method, msg, se);  | 
|
throw se;  | 
|
}  | 
|
private static void logException(String method,  | 
|
String message,  | 
|
Exception e) {  | 
|
        if (logger.traceOn()) { | 
|
logger.trace(method, message);  | 
|
}  | 
|
        if (logger.debugOn()) { | 
|
logger.debug(method, e);  | 
|
}  | 
|
}  | 
|
private LoginContext loginContext;  | 
|
private String username;  | 
|
private String password;  | 
|
private static final String LOGIN_CONFIG_PROP =  | 
|
        "jmx.remote.x.login.config"; | 
|
private static final String LOGIN_CONFIG_NAME = "JMXPluggableAuthenticator";  | 
|
private static final String PASSWORD_FILE_PROP =  | 
|
        "jmx.remote.x.password.file"; | 
|
private static final ClassLogger logger =  | 
|
new ClassLogger("javax.management.remote.misc", LOGIN_CONFIG_NAME);  | 
|
/** | 
|
 * This callback handler supplies the username and password (which was | 
|
 * originally supplied by the JMX user) to the JAAS login module performing | 
|
 * the authentication. No interactive user prompting is required because the | 
|
 * credentials are already available to this class (via its enclosing class). | 
|
*/  | 
|
private final class JMXCallbackHandler implements CallbackHandler {  | 
|
    /** | 
|
     * Sets the username and password in the appropriate Callback object. | 
|
*/  | 
|
public void handle(Callback[] callbacks)  | 
|
throws IOException, UnsupportedCallbackException {  | 
|
for (int i = 0; i < callbacks.length; i++) {  | 
|
if (callbacks[i] instanceof NameCallback) {  | 
|
((NameCallback)callbacks[i]).setName(username);  | 
|
} else if (callbacks[i] instanceof PasswordCallback) {  | 
|
((PasswordCallback)callbacks[i])  | 
|
.setPassword(password.toCharArray());  | 
|
            } else { | 
|
throw new UnsupportedCallbackException  | 
|
(callbacks[i], "Unrecognized Callback");  | 
|
}  | 
|
}  | 
|
}  | 
|
}  | 
|
/** | 
|
 * This class defines the JAAS configuration for file-based authentication. | 
|
 * It is equivalent to the following textual configuration entry: | 
|
 * <pre> | 
|
 *     JMXPluggableAuthenticator { | 
|
 *         com.sun.jmx.remote.security.FileLoginModule required; | 
|
 *     }; | 
|
 * </pre> | 
|
*/  | 
|
private static class FileLoginConfig extends Configuration {  | 
|
    // The JAAS configuration for file-based authentication | 
|
private AppConfigurationEntry[] entries;  | 
|
    // The classname of the login module for file-based authentication | 
|
private static final String FILE_LOGIN_MODULE =  | 
|
FileLoginModule.class.getName();  | 
|
    // The option that identifies the password file to use | 
|
private static final String PASSWORD_FILE_OPTION = "passwordFile";  | 
|
    /** | 
|
     * Creates an instance of <code>FileLoginConfig</code> | 
|
     * | 
|
     * @param passwordFile A filepath that identifies the password file to use. | 
|
     *                     If null then the default password file is used. | 
|
*/  | 
|
public FileLoginConfig(String passwordFile) {  | 
|
Map<String, String> options;  | 
|
if (passwordFile != null) {  | 
|
options = new HashMap<String, String>(1);  | 
|
options.put(PASSWORD_FILE_OPTION, passwordFile);  | 
|
        } else { | 
|
options = Collections.emptyMap();  | 
|
}  | 
|
entries = new AppConfigurationEntry[] {  | 
|
new AppConfigurationEntry(FILE_LOGIN_MODULE,  | 
|
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,  | 
|
options)  | 
|
};  | 
|
}  | 
|
    /** | 
|
     * Gets the JAAS configuration for file-based authentication | 
|
*/  | 
|
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {  | 
|
return name.equals(LOGIN_CONFIG_NAME) ? entries : null;  | 
|
}  | 
|
    /** | 
|
     * Refreshes the configuration. | 
|
*/  | 
|
    public void refresh() { | 
|
// the configuration is fixed  | 
|
}  | 
|
}  | 
|
}  |