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