/* Copyright 2005 Cenqua Pty Ltd. All Rights Reserved. See LICENSE.TXT in the distribution. */
package com.cenqua.shaj;
import com.cenqua.shaj.log.Log;
/**
* Checks passwords against the local PAM service.
*
* <p>PAM (Pluggable Authentication Modules) is common authentication mechanism
* on many Unix systems (e.g. Linux, Solaris, OS-X).</p>
*
* <p>
* Shaj needs to be told which <i>service name</i>
* to use when conversing with PAM (this is the <code>domain</code> argument).
* You can create a new service name in your PAM configuration
* (typically /etc/pam.conf or /etc/pam.d/), or tell Shaj
* to use an existing service name (such as other, <code>login</code> or <code>xscreensaver</code>).
* </p>
*
* <p>This class contains some static methods that can be used if you
* wish to call to PAM directly.</p>
*
* <p>Some platform-specific advice for configuring PAM follows (assuming you want to create
* a PAM service named <code>shaj</code>:)</p>
*
* <p><b>Linux</b>:
* On many Linux distributions, you may need to create a <cpde>/etc/pam.d/shaj</code> file containing:</p>
* <pre>auth required pam_stack.so service=system-auth</pre>
*
* <p><b>Mac OS-X</b>:
* On a default OS-X installation, you may need to create a <cpde>/etc/pam.d/shaj</code> file containing:</p>
*<pre>auth sufficient pam_securityserver.so
*auth required pam_deny.so</pre>
*
* <p><b>Solaris</b>:
* If your are using the default <code>pam_unix_auth</code> PAM configuration on Solaris,
* then you may need to add a line like this to your <code>/etc/pam.conf</code> file:</p>
*<pre>shaj auth requisite pam_authtok_get.so.1
*shaj auth required pam_unix_auth.so.1</pre>
*
* <p>If you test this and it does not work, it is probably because when using <code>pam_unix_auth</code> on Solaris,
* the process doing the password check needs read access to /etc/shadow.
* Giving the process Shaj is running in read access to this file may solve this problem, but using permissions
* other than 0400 for <code>/etc/shadow</code> is not recommended.
* You should discuss this with your system administrators first, and possibly change to a PAM module other than
* <code>pam_unix_auth</code>.
*/
public class PAMAuthenticator extends Authenticator {
static {
Shaj.init();
}
/** lock object for calling getgrent(3) (which is not reentrant) */
private static final Object GETGRENT_LOCK = new Object();
private static native boolean isSupportedImpl(Log log);
private static native boolean checkPasswordImpl(String service, String username, String password, Log log);
private static native boolean checkGroupMembershipImpl(String username, String group, Log log);
/**
* Determines if this Authenticator can be used on the underlying platform.
* @return true if this platform supports PAM authentication.
*/
public static boolean isSupported() {
return isSupported(Log.Factory.getInstance());
}
private static boolean isSupported(final Log log) {
if (!Shaj.sInitOkay) {
return false;
}
return isSupportedImpl(log);
}
@Override
public boolean checkPassword(final String domain, final String username, final String password, final Log log) {
return checkPAMPassword(domain, username, password, log);
}
@Override
public boolean checkGroupMembership(final String domain, final String username, final String group, final Log log) {
return checkPAMGroupMembership(username, group, log);
}
/**
* Checks a user's password in PAM.
*
* @param service the PAM service to use.
* May be <code>null</code> (in which case "other" is used).
* @param username the username
* @param password the password to verify
* @param log where to log errors/debugging
* @return true if the password matches the username
* @throws IllegalArgumentException if <code>username</code>
* or <code>password</code> are <code>null</code>.
* @throws IllegalStateException if Shaj did not load correctly (if {@link Shaj#init()} returns false).
*/
public static boolean checkPAMPassword(String service, final String username, final String password, final Log log) {
if (service == null) {
service = "other";
}
if (username == null) {
throw new IllegalArgumentException("username cannot be null");
}
if (password == null) {
throw new IllegalArgumentException("password cannot be null");
}
if (!Shaj.sInitOkay) {
throw new IllegalStateException("native library not loaded");
}
return checkPasswordImpl(service, username, password, log);
}
/**
* Tests a user for membership in a unix group.
*
* <p><b>Note</b> The <code>getgrent(3)</code> system call is used to
* test group membership, not PAM as the name of this method might suggest.
* (PAM has no group-membership testing functions.)
* </p>
*
* @param username the username to test for membership
* @param group the group to look in
* @param log where to log errors/debugging
* @return true if the user is a member of the group
* @throws IllegalArgumentException if <code>username</code>
* or <code>group</code> are <code>null</code>.
* @throws IllegalStateException if Shaj did not load correctly (if {@link Shaj#init()} returns false).
*/
public static boolean checkPAMGroupMembership(final String username, final String group, final Log log) {
if (username == null) {
throw new IllegalArgumentException("username cannot be null");
}
if (group == null) {
throw new IllegalArgumentException("group cannot be null");
}
if (!Shaj.sInitOkay) {
throw new IllegalStateException("native library not loaded");
}
synchronized (GETGRENT_LOCK) {
return checkGroupMembershipImpl(username, group, log);
}
}
}