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
26package com.sun.security.auth.module;
27
28import javax.security.auth.*;
29import javax.security.auth.callback.*;
30import javax.security.auth.login.*;
31import javax.security.auth.spi.*;
32import javax.naming.*;
33import javax.naming.directory.*;
34
35import java.util.Map;
36import java.util.LinkedList;
37
38import com.sun.security.auth.UnixPrincipal;
39import com.sun.security.auth.UnixNumericUserPrincipal;
40import com.sun.security.auth.UnixNumericGroupPrincipal;
41import 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 */
152public 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")
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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