001: /*
002: * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.security.auth.module;
027:
028: import javax.security.auth.*;
029: import javax.security.auth.callback.*;
030: import javax.security.auth.login.*;
031: import javax.security.auth.spi.*;
032: import javax.naming.*;
033: import javax.naming.directory.*;
034:
035: import java.io.IOException;
036: import java.util.Map;
037: import java.util.LinkedList;
038: import java.util.ResourceBundle;
039:
040: import com.sun.security.auth.UnixPrincipal;
041: import com.sun.security.auth.UnixNumericUserPrincipal;
042: import com.sun.security.auth.UnixNumericGroupPrincipal;
043:
044: import sun.security.util.AuthResources;
045:
046: /**
047: * <p> The module prompts for a username and password
048: * and then verifies the password against the password stored in
049: * a directory service configured under JNDI.
050: *
051: * <p> This <code>LoginModule</code> interoperates with
052: * any conformant JNDI service provider. To direct this
053: * <code>LoginModule</code> to use a specific JNDI service provider,
054: * two options must be specified in the login <code>Configuration</code>
055: * for this <code>LoginModule</code>.
056: * <pre>
057: * user.provider.url=<b>name_service_url</b>
058: * group.provider.url=<b>name_service_url</b>
059: * </pre>
060: *
061: * <b>name_service_url</b> specifies
062: * the directory service and path where this <code>LoginModule</code>
063: * can access the relevant user and group information. Because this
064: * <code>LoginModule</code> only performs one-level searches to
065: * find the relevant user information, the <code>URL</code>
066: * must point to a directory one level above where the user and group
067: * information is stored in the directory service.
068: * For example, to instruct this <code>LoginModule</code>
069: * to contact a NIS server, the following URLs must be specified:
070: * <pre>
071: * user.provider.url="nis://<b>NISServerHostName</b>/<b>NISDomain</b>/user"
072: * group.provider.url="nis://<b>NISServerHostName</b>/<b>NISDomain</b>/system/group"
073: * </pre>
074: *
075: * <b>NISServerHostName</b> specifies the server host name of the
076: * NIS server (for example, <i>nis.sun.com</i>, and <b>NISDomain</b>
077: * specifies the domain for that NIS server (for example, <i>jaas.sun.com</i>.
078: * To contact an LDAP server, the following URLs must be specified:
079: * <pre>
080: * user.provider.url="ldap://<b>LDAPServerHostName</b>/<b>LDAPName</b>"
081: * group.provider.url="ldap://<b>LDAPServerHostName</b>/<b>LDAPName</b>"
082: * </pre>
083: *
084: * <b>LDAPServerHostName</b> specifies the server host name of the
085: * LDAP server, which may include a port number
086: * (for example, <i>ldap.sun.com:389</i>),
087: * and <b>LDAPName</b> specifies the entry name in the LDAP directory
088: * (for example, <i>ou=People,o=Sun,c=US</i> and <i>ou=Groups,o=Sun,c=US</i>
089: * for user and group information, respectively).
090: *
091: * <p> The format in which the user's information must be stored in
092: * the directory service is specified in RFC 2307. Specifically,
093: * this <code>LoginModule</code> will search for the user's entry in the
094: * directory service using the user's <i>uid</i> attribute,
095: * where <i>uid=<b>username</b></i>. If the search succeeds,
096: * this <code>LoginModule</code> will then
097: * obtain the user's encrypted password from the retrieved entry
098: * using the <i>userPassword</i> attribute.
099: * This <code>LoginModule</code> assumes that the password is stored
100: * as a byte array, which when converted to a <code>String</code>,
101: * has the following format:
102: * <pre>
103: * "{crypt}<b>encrypted_password</b>"
104: * </pre>
105: *
106: * The LDAP directory server must be configured
107: * to permit read access to the userPassword attribute.
108: * If the user entered a valid username and password,
109: * this <code>LoginModule</code> associates a
110: * <code>UnixPrincipal</code>, <code>UnixNumericUserPrincipal</code>,
111: * and the relevant UnixNumericGroupPrincipals with the
112: * <code>Subject</code>.
113: *
114: * <p> This LoginModule also recognizes the following <code>Configuration</code>
115: * options:
116: * <pre>
117: * debug if, true, debug messages are output to System.out.
118: *
119: * useFirstPass if, true, this LoginModule retrieves the
120: * username and password from the module's shared state,
121: * using "javax.security.auth.login.name" and
122: * "javax.security.auth.login.password" as the respective
123: * keys. The retrieved values are used for authentication.
124: * If authentication fails, no attempt for a retry is made,
125: * and the failure is reported back to the calling
126: * application.
127: *
128: * tryFirstPass if, true, this LoginModule retrieves the
129: * the username and password from the module's shared state,
130: * using "javax.security.auth.login.name" and
131: * "javax.security.auth.login.password" as the respective
132: * keys. The retrieved values are used for authentication.
133: * If authentication fails, the module uses the
134: * CallbackHandler to retrieve a new username and password,
135: * and another attempt to authenticate is made.
136: * If the authentication fails, the failure is reported
137: * back to the calling application.
138: *
139: * storePass if, true, this LoginModule stores the username and password
140: * obtained from the CallbackHandler in the module's
141: * shared state, using "javax.security.auth.login.name" and
142: * "javax.security.auth.login.password" as the respective
143: * keys. This is not performed if existing values already
144: * exist for the username and password in the shared state,
145: * or if authentication fails.
146: *
147: * clearPass if, true, this <code>LoginModule</code> clears the
148: * username and password stored in the module's shared state
149: * after both phases of authentication (login and commit)
150: * have completed.
151: * </pre>
152: *
153: * @version 1.19, 05/05/07
154: */
155: public class JndiLoginModule implements LoginModule {
156:
157: static final java.util.ResourceBundle rb = java.util.ResourceBundle
158: .getBundle("sun.security.util.AuthResources");
159:
160: /** JNDI Provider */
161: public final String USER_PROVIDER = "user.provider.url";
162: public final String GROUP_PROVIDER = "group.provider.url";
163:
164: // configurable options
165: private boolean debug = false;
166: private boolean strongDebug = false;
167: private String userProvider;
168: private String groupProvider;
169: private boolean useFirstPass = false;
170: private boolean tryFirstPass = false;
171: private boolean storePass = false;
172: private boolean clearPass = false;
173:
174: // the authentication status
175: private boolean succeeded = false;
176: private boolean commitSucceeded = false;
177:
178: // username, password, and JNDI context
179: private String username;
180: private char[] password;
181: DirContext ctx;
182:
183: // the user (assume it is a UnixPrincipal)
184: private UnixPrincipal userPrincipal;
185: private UnixNumericUserPrincipal UIDPrincipal;
186: private UnixNumericGroupPrincipal GIDPrincipal;
187: private LinkedList<UnixNumericGroupPrincipal> supplementaryGroups = new LinkedList<UnixNumericGroupPrincipal>();
188:
189: // initial state
190: private Subject subject;
191: private CallbackHandler callbackHandler;
192: private Map sharedState;
193: private Map<String, ?> options;
194:
195: private static final String CRYPT = "{crypt}";
196: private static final String USER_PWD = "userPassword";
197: private static final String USER_UID = "uidNumber";
198: private static final String USER_GID = "gidNumber";
199: private static final String GROUP_ID = "gidNumber";
200: private static final String NAME = "javax.security.auth.login.name";
201: private static final String PWD = "javax.security.auth.login.password";
202:
203: /**
204: * Initialize this <code>LoginModule</code>.
205: *
206: * <p>
207: *
208: * @param subject the <code>Subject</code> to be authenticated. <p>
209: *
210: * @param callbackHandler a <code>CallbackHandler</code> for communicating
211: * with the end user (prompting for usernames and
212: * passwords, for example). <p>
213: *
214: * @param sharedState shared <code>LoginModule</code> state. <p>
215: *
216: * @param options options specified in the login
217: * <code>Configuration</code> for this particular
218: * <code>LoginModule</code>.
219: */
220: public void initialize(Subject subject,
221: CallbackHandler callbackHandler,
222: Map<String, ?> sharedState, Map<String, ?> options) {
223:
224: this .subject = subject;
225: this .callbackHandler = callbackHandler;
226: this .sharedState = sharedState;
227: this .options = options;
228:
229: // initialize any configured options
230: debug = "true".equalsIgnoreCase((String) options.get("debug"));
231: strongDebug = "true".equalsIgnoreCase((String) options
232: .get("strongDebug"));
233: userProvider = (String) options.get(USER_PROVIDER);
234: groupProvider = (String) options.get(GROUP_PROVIDER);
235: tryFirstPass = "true".equalsIgnoreCase((String) options
236: .get("tryFirstPass"));
237: useFirstPass = "true".equalsIgnoreCase((String) options
238: .get("useFirstPass"));
239: storePass = "true".equalsIgnoreCase((String) options
240: .get("storePass"));
241: clearPass = "true".equalsIgnoreCase((String) options
242: .get("clearPass"));
243: }
244:
245: /**
246: * <p> Prompt for username and password.
247: * Verify the password against the relevant name service.
248: *
249: * <p>
250: *
251: * @return true always, since this <code>LoginModule</code>
252: * should not be ignored.
253: *
254: * @exception FailedLoginException if the authentication fails. <p>
255: *
256: * @exception LoginException if this <code>LoginModule</code>
257: * is unable to perform the authentication.
258: */
259: public boolean login() throws LoginException {
260:
261: if (userProvider == null) {
262: throw new LoginException(
263: "Error: Unable to locate JNDI user provider");
264: }
265: if (groupProvider == null) {
266: throw new LoginException(
267: "Error: Unable to locate JNDI group provider");
268: }
269:
270: if (debug) {
271: System.out.println("\t\t[JndiLoginModule] user provider: "
272: + userProvider);
273: System.out.println("\t\t[JndiLoginModule] group provider: "
274: + groupProvider);
275: }
276:
277: // attempt the authentication
278: if (tryFirstPass) {
279:
280: try {
281: // attempt the authentication by getting the
282: // username and password from shared state
283: attemptAuthentication(true);
284:
285: // authentication succeeded
286: succeeded = true;
287: if (debug) {
288: System.out.println("\t\t[JndiLoginModule] "
289: + "tryFirstPass succeeded");
290: }
291: return true;
292: } catch (LoginException le) {
293: // authentication failed -- try again below by prompting
294: cleanState();
295: if (debug) {
296: System.out.println("\t\t[JndiLoginModule] "
297: + "tryFirstPass failed with:"
298: + le.toString());
299: }
300: }
301:
302: } else if (useFirstPass) {
303:
304: try {
305: // attempt the authentication by getting the
306: // username and password from shared state
307: attemptAuthentication(true);
308:
309: // authentication succeeded
310: succeeded = true;
311: if (debug) {
312: System.out.println("\t\t[JndiLoginModule] "
313: + "useFirstPass succeeded");
314: }
315: return true;
316: } catch (LoginException le) {
317: // authentication failed
318: cleanState();
319: if (debug) {
320: System.out.println("\t\t[JndiLoginModule] "
321: + "useFirstPass failed");
322: }
323: throw le;
324: }
325: }
326:
327: // attempt the authentication by prompting for the username and pwd
328: try {
329: attemptAuthentication(false);
330:
331: // authentication succeeded
332: succeeded = true;
333: if (debug) {
334: System.out.println("\t\t[JndiLoginModule] "
335: + "regular authentication succeeded");
336: }
337: return true;
338: } catch (LoginException le) {
339: cleanState();
340: if (debug) {
341: System.out.println("\t\t[JndiLoginModule] "
342: + "regular authentication failed");
343: }
344: throw le;
345: }
346: }
347:
348: /**
349: * Abstract method to commit the authentication process (phase 2).
350: *
351: * <p> This method is called if the LoginContext's
352: * overall authentication succeeded
353: * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
354: * succeeded).
355: *
356: * <p> If this LoginModule's own authentication attempt
357: * succeeded (checked by retrieving the private state saved by the
358: * <code>login</code> method), then this method associates a
359: * <code>UnixPrincipal</code>
360: * with the <code>Subject</code> located in the
361: * <code>LoginModule</code>. If this LoginModule's own
362: * authentication attempted failed, then this method removes
363: * any state that was originally saved.
364: *
365: * <p>
366: *
367: * @exception LoginException if the commit fails
368: *
369: * @return true if this LoginModule's own login and commit
370: * attempts succeeded, or false otherwise.
371: */
372: public boolean commit() throws LoginException {
373:
374: if (succeeded == false) {
375: return false;
376: } else {
377: if (subject.isReadOnly()) {
378: cleanState();
379: throw new LoginException("Subject is Readonly");
380: }
381: // add Principals to the Subject
382: if (!subject.getPrincipals().contains(userPrincipal))
383: subject.getPrincipals().add(userPrincipal);
384: if (!subject.getPrincipals().contains(UIDPrincipal))
385: subject.getPrincipals().add(UIDPrincipal);
386: if (!subject.getPrincipals().contains(GIDPrincipal))
387: subject.getPrincipals().add(GIDPrincipal);
388: for (int i = 0; i < supplementaryGroups.size(); i++) {
389: if (!subject.getPrincipals().contains(
390: supplementaryGroups.get(i)))
391: subject.getPrincipals().add(
392: supplementaryGroups.get(i));
393: }
394:
395: if (debug) {
396: System.out.println("\t\t[JndiLoginModule]: "
397: + "added UnixPrincipal,");
398: System.out.println("\t\t\t\tUnixNumericUserPrincipal,");
399: System.out
400: .println("\t\t\t\tUnixNumericGroupPrincipal(s),");
401: System.out.println("\t\t\t to Subject");
402: }
403: }
404: // in any case, clean out state
405: cleanState();
406: commitSucceeded = true;
407: return true;
408: }
409:
410: /**
411: * <p> This method is called if the LoginContext's
412: * overall authentication failed.
413: * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
414: * did not succeed).
415: *
416: * <p> If this LoginModule's own authentication attempt
417: * succeeded (checked by retrieving the private state saved by the
418: * <code>login</code> and <code>commit</code> methods),
419: * then this method cleans up any state that was originally saved.
420: *
421: * <p>
422: *
423: * @exception LoginException if the abort fails.
424: *
425: * @return false if this LoginModule's own login and/or commit attempts
426: * failed, and true otherwise.
427: */
428: public boolean abort() throws LoginException {
429: if (debug)
430: System.out.println("\t\t[JndiLoginModule]: "
431: + "aborted authentication failed");
432:
433: if (succeeded == false) {
434: return false;
435: } else if (succeeded == true && commitSucceeded == false) {
436:
437: // Clean out state
438: succeeded = false;
439: cleanState();
440:
441: userPrincipal = null;
442: UIDPrincipal = null;
443: GIDPrincipal = null;
444: supplementaryGroups = new LinkedList<UnixNumericGroupPrincipal>();
445: } else {
446: // overall authentication succeeded and commit succeeded,
447: // but someone else's commit failed
448: logout();
449: }
450: return true;
451: }
452:
453: /**
454: * Logout a user.
455: *
456: * <p> This method removes the Principals
457: * that were added by the <code>commit</code> method.
458: *
459: * <p>
460: *
461: * @exception LoginException if the logout fails.
462: *
463: * @return true in all cases since this <code>LoginModule</code>
464: * should not be ignored.
465: */
466: public boolean logout() throws LoginException {
467: if (subject.isReadOnly()) {
468: cleanState();
469: throw new LoginException("Subject is Readonly");
470: }
471: subject.getPrincipals().remove(userPrincipal);
472: subject.getPrincipals().remove(UIDPrincipal);
473: subject.getPrincipals().remove(GIDPrincipal);
474: for (int i = 0; i < supplementaryGroups.size(); i++) {
475: subject.getPrincipals().remove(supplementaryGroups.get(i));
476: }
477:
478: // clean out state
479: cleanState();
480: succeeded = false;
481: commitSucceeded = false;
482:
483: userPrincipal = null;
484: UIDPrincipal = null;
485: GIDPrincipal = null;
486: supplementaryGroups = new LinkedList<UnixNumericGroupPrincipal>();
487:
488: if (debug) {
489: System.out.println("\t\t[JndiLoginModule]: "
490: + "logged out Subject");
491: }
492: return true;
493: }
494:
495: /**
496: * Attempt authentication
497: *
498: * <p>
499: *
500: * @param getPasswdFromSharedState boolean that tells this method whether
501: * to retrieve the password from the sharedState.
502: */
503: private void attemptAuthentication(boolean getPasswdFromSharedState)
504: throws LoginException {
505:
506: String encryptedPassword = null;
507:
508: // first get the username and password
509: getUsernamePassword(getPasswdFromSharedState);
510:
511: try {
512:
513: // get the user's passwd entry from the user provider URL
514: InitialContext iCtx = new InitialContext();
515: ctx = (DirContext) iCtx.lookup(userProvider);
516:
517: /*
518: SearchControls controls = new SearchControls
519: (SearchControls.ONELEVEL_SCOPE,
520: 0,
521: 5000,
522: new String[] { USER_PWD },
523: false,
524: false);
525: */
526:
527: SearchControls controls = new SearchControls();
528: NamingEnumeration<SearchResult> ne = ctx.search("", "(uid="
529: + username + ")", controls);
530: if (ne.hasMore()) {
531: SearchResult result = ne.next();
532: Attributes attributes = result.getAttributes();
533:
534: // get the password
535:
536: // this module works only if the LDAP directory server
537: // is configured to permit read access to the userPassword
538: // attribute. The directory administrator need to grant
539: // this access.
540: //
541: // A workaround would be to make the server do authentication
542: // by setting the Context.SECURITY_PRINCIPAL
543: // and Context.SECURITY_CREDENTIALS property.
544: // However, this would make it not work with systems that
545: // don't do authentication at the server (like NIS).
546: //
547: // Setting the SECURITY_* properties and using "simple"
548: // authentication for LDAP is recommended only for secure
549: // channels. For nonsecure channels, SSL is recommended.
550:
551: Attribute pwd = attributes.get(USER_PWD);
552: String encryptedPwd = new String((byte[]) pwd.get(),
553: "UTF8");
554: encryptedPassword = encryptedPwd.substring(CRYPT
555: .length());
556:
557: // check the password
558: if (verifyPassword(encryptedPassword, new String(
559: password)) == true) {
560:
561: // authentication succeeded
562: if (debug)
563: System.out.println("\t\t[JndiLoginModule] "
564: + "attemptAuthentication() succeeded");
565:
566: } else {
567: // authentication failed
568: if (debug)
569: System.out.println("\t\t[JndiLoginModule] "
570: + "attemptAuthentication() failed");
571: throw new FailedLoginException("Login incorrect");
572: }
573:
574: // save input as shared state only if
575: // authentication succeeded
576: if (storePass && !sharedState.containsKey(NAME)
577: && !sharedState.containsKey(PWD)) {
578: sharedState.put(NAME, username);
579: sharedState.put(PWD, password);
580: }
581:
582: // create the user principal
583: userPrincipal = new UnixPrincipal(username);
584:
585: // get the UID
586: Attribute uid = attributes.get(USER_UID);
587: String uidNumber = (String) uid.get();
588: UIDPrincipal = new UnixNumericUserPrincipal(uidNumber);
589: if (debug && uidNumber != null) {
590: System.out.println("\t\t[JndiLoginModule] "
591: + "user: '" + username + "' has UID: "
592: + uidNumber);
593: }
594:
595: // get the GID
596: Attribute gid = attributes.get(USER_GID);
597: String gidNumber = (String) gid.get();
598: GIDPrincipal = new UnixNumericGroupPrincipal(gidNumber,
599: true);
600: if (debug && gidNumber != null) {
601: System.out.println("\t\t[JndiLoginModule] "
602: + "user: '" + username + "' has GID: "
603: + gidNumber);
604: }
605:
606: // get the supplementary groups from the group provider URL
607: ctx = (DirContext) iCtx.lookup(groupProvider);
608: ne = ctx.search("", new BasicAttributes("memberUid",
609: username));
610:
611: while (ne.hasMore()) {
612: result = ne.next();
613: attributes = result.getAttributes();
614:
615: gid = attributes.get(GROUP_ID);
616: String suppGid = (String) gid.get();
617: if (!gidNumber.equals(suppGid)) {
618: UnixNumericGroupPrincipal suppPrincipal = new UnixNumericGroupPrincipal(
619: suppGid, false);
620: supplementaryGroups.add(suppPrincipal);
621: if (debug && suppGid != null) {
622: System.out.println("\t\t[JndiLoginModule] "
623: + "user: '" + username
624: + "' has Supplementary Group: "
625: + suppGid);
626: }
627: }
628: }
629:
630: } else {
631: // bad username
632: if (debug) {
633: System.out
634: .println("\t\t[JndiLoginModule]: User not found");
635: }
636: throw new FailedLoginException("User not found");
637: }
638: } catch (NamingException ne) {
639: // bad username
640: if (debug) {
641: System.out
642: .println("\t\t[JndiLoginModule]: User not found");
643: ne.printStackTrace();
644: }
645: throw new FailedLoginException("User not found");
646: } catch (java.io.UnsupportedEncodingException uee) {
647: // password stored in incorrect format
648: if (debug) {
649: System.out.println("\t\t[JndiLoginModule]: "
650: + "password incorrectly encoded");
651: uee.printStackTrace();
652: }
653: throw new LoginException("Login failure due to incorrect "
654: + "password encoding in the password database");
655: }
656:
657: // authentication succeeded
658: }
659:
660: /**
661: * Get the username and password.
662: * This method does not return any value.
663: * Instead, it sets global name and password variables.
664: *
665: * <p> Also note that this method will set the username and password
666: * values in the shared state in case subsequent LoginModules
667: * want to use them via use/tryFirstPass.
668: *
669: * <p>
670: *
671: * @param getPasswdFromSharedState boolean that tells this method whether
672: * to retrieve the password from the sharedState.
673: */
674: private void getUsernamePassword(boolean getPasswdFromSharedState)
675: throws LoginException {
676:
677: if (getPasswdFromSharedState) {
678: // use the password saved by the first module in the stack
679: username = (String) sharedState.get(NAME);
680: password = (char[]) sharedState.get(PWD);
681: return;
682: }
683:
684: // prompt for a username and password
685: if (callbackHandler == null)
686: throw new LoginException(
687: "Error: no CallbackHandler available "
688: + "to garner authentication information from the user");
689:
690: String protocol = userProvider.substring(0, userProvider
691: .indexOf(":"));
692:
693: Callback[] callbacks = new Callback[2];
694: callbacks[0] = new NameCallback(protocol + " "
695: + rb.getString("username: "));
696: callbacks[1] = new PasswordCallback(protocol + " "
697: + rb.getString("password: "), false);
698:
699: try {
700: callbackHandler.handle(callbacks);
701: username = ((NameCallback) callbacks[0]).getName();
702: char[] tmpPassword = ((PasswordCallback) callbacks[1])
703: .getPassword();
704: password = new char[tmpPassword.length];
705: System.arraycopy(tmpPassword, 0, password, 0,
706: tmpPassword.length);
707: ((PasswordCallback) callbacks[1]).clearPassword();
708:
709: } catch (java.io.IOException ioe) {
710: throw new LoginException(ioe.toString());
711: } catch (UnsupportedCallbackException uce) {
712: throw new LoginException(
713: "Error: "
714: + uce.getCallback().toString()
715: + " not available to garner authentication information "
716: + "from the user");
717: }
718:
719: // print debugging information
720: if (strongDebug) {
721: System.out.println("\t\t[JndiLoginModule] "
722: + "user entered username: " + username);
723: System.out.print("\t\t[JndiLoginModule] "
724: + "user entered password: ");
725: for (int i = 0; i < password.length; i++)
726: System.out.print(password[i]);
727: System.out.println();
728: }
729: }
730:
731: /**
732: * Verify a password against the encrypted passwd from /etc/shadow
733: */
734: private boolean verifyPassword(String encryptedPassword,
735: String password) {
736:
737: if (encryptedPassword == null)
738: return false;
739:
740: Crypt c = new Crypt();
741: try {
742: byte oldCrypt[] = encryptedPassword.getBytes("UTF8");
743: byte newCrypt[] = c.crypt(password.getBytes("UTF8"),
744: oldCrypt);
745: if (newCrypt.length != oldCrypt.length)
746: return false;
747: for (int i = 0; i < newCrypt.length; i++) {
748: if (oldCrypt[i] != newCrypt[i])
749: return false;
750: }
751: } catch (java.io.UnsupportedEncodingException uee) {
752: // cannot happen, but return false just to be safe
753: return false;
754: }
755: return true;
756: }
757:
758: /**
759: * Clean out state because of a failed authentication attempt
760: */
761: private void cleanState() {
762: username = null;
763: if (password != null) {
764: for (int i = 0; i < password.length; i++)
765: password[i] = ' ';
766: password = null;
767: }
768: ctx = null;
769:
770: if (clearPass) {
771: sharedState.remove(NAME);
772: sharedState.remove(PWD);
773: }
774: }
775: }
|