/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* 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.
*/
/**
* <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"
* </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>
*
*/
/** JNDI Provider */
// configurable options
private boolean debug = false;
private boolean strongDebug = false;
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 char[] password;
// the user (assume it is a UnixPrincipal)
new LinkedList<>();
// initial state
/**
* 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>.
*/
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
// initialize any configured options
}
/**
* <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.
*/
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) {
}
// 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) {
"tryFirstPass succeeded");
}
return true;
} catch (LoginException le) {
// authentication failed -- try again below by prompting
cleanState();
if (debug) {
"tryFirstPass failed with:" +
}
}
} else if (useFirstPass) {
try {
// attempt the authentication by getting the
// username and password from shared state
attemptAuthentication(true);
// authentication succeeded
succeeded = true;
if (debug) {
"useFirstPass succeeded");
}
return true;
} catch (LoginException le) {
// authentication failed
cleanState();
if (debug) {
"useFirstPass failed");
}
throw le;
}
}
// attempt the authentication by prompting for the username and pwd
try {
attemptAuthentication(false);
// authentication succeeded
succeeded = true;
if (debug) {
"regular authentication succeeded");
}
return true;
} catch (LoginException le) {
cleanState();
if (debug) {
"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.
*/
if (succeeded == false) {
return false;
} else {
if (subject.isReadOnly()) {
cleanState();
throw new LoginException ("Subject is Readonly");
}
// add Principals to the Subject
(supplementaryGroups.get(i)))
}
if (debug) {
"added UnixPrincipal,");
}
}
// 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.
*
* failed, and true otherwise.
*/
if (debug)
"aborted authentication failed");
if (succeeded == false) {
return false;
} else if (succeeded == true && commitSucceeded == false) {
// Clean out state
succeeded = false;
cleanState();
UIDPrincipal = null;
GIDPrincipal = 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.
*
* <p>
*
* @exception LoginException if the logout fails.
*
* @return true in all cases since this <code>LoginModule</code>
* should not be ignored.
*/
if (subject.isReadOnly()) {
cleanState();
throw new LoginException ("Subject is Readonly");
}
}
// clean out state
cleanState();
succeeded = false;
commitSucceeded = false;
UIDPrincipal = null;
GIDPrincipal = null;
if (debug) {
"logged out Subject");
}
return true;
}
/**
* Attempt authentication
*
* <p>
*
* @param getPasswdFromSharedState boolean that tells this method whether
* to retrieve the password from the sharedState.
*/
throws LoginException {
// first get the username and password
try {
// get the user's passwd entry from the user provider URL
/*
SearchControls controls = new SearchControls
(SearchControls.ONELEVEL_SCOPE,
0,
5000,
new String[] { USER_PWD },
false,
false);
*/
controls);
// 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.
// check the password
if (verifyPassword
// authentication succeeded
if (debug)
"attemptAuthentication() succeeded");
} else {
// authentication failed
if (debug)
"attemptAuthentication() failed");
throw new FailedLoginException("Login incorrect");
}
// save input as shared state only if
// authentication succeeded
if (storePass &&
}
// create the user principal
// get the UID
}
// get the GID
(gidNumber, true);
}
// get the supplementary groups from the group provider URL
new UnixNumericGroupPrincipal(suppGid, false);
"user: '" + username +
"' has Supplementary Group: " +
suppGid);
}
}
}
} else {
// bad username
if (debug) {
}
throw new FailedLoginException("User not found");
}
} catch (NamingException ne) {
// bad username
if (debug) {
}
throw new FailedLoginException("User not found");
// password stored in incorrect format
if (debug) {
"password incorrectly encoded");
}
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.
*/
throws LoginException {
if (getPasswdFromSharedState) {
// use the password saved by the first module in the stack
return;
}
// prompt for a username and password
if (callbackHandler == null)
throw new LoginException("Error: no CallbackHandler available " +
"to garner authentication information from the user");
false);
try {
} catch (UnsupportedCallbackException uce) {
" not available to garner authentication information " +
"from the user");
}
// print debugging information
if (strongDebug) {
"user entered username: " +
username);
"user entered password: ");
}
}
/**
*/
if (encryptedPassword == null)
return false;
try {
oldCrypt);
return false;
return false;
}
// 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() {
password[i] = ' ';
}
if (clearPass) {
}
}
}