      1 /*
      2  * Copyright (c) 2000, 2020, 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      * Creates a {@code JndiLoginModule}.
    200      */
    201     public JndiLoginModule() {}
    202
    203     /**
    204      * Initialize this {@code LoginModule}.
    205      *
    206      * @param subject the {@code Subject} to be authenticated.
    207      *
    208      * @param callbackHandler a {@code CallbackHandler} for communicating
    209      *                  with the end user (prompting for usernames and
    210      *                  passwords, for example).
    211      *
    212      * @param sharedState shared {@code LoginModule} state.
    213      *
    214      * @param options options specified in the login
    215      *                  {@code Configuration} for this particular
    216      *                  {@code LoginModule}.
    217      */
    218     // Unchecked warning from (Map<String, Object>)sharedState is safe
    219     // since javax.security.auth.login.LoginContext passes a raw HashMap.
    220     // Unchecked warnings from options.get(String) are safe since we are
    221     // passing known keys.
    222     @SuppressWarnings("unchecked")
    223     public void initialize(Subject subject, CallbackHandler callbackHandler,
    224                            Map<String,?> sharedState,
    225                            Map<String,?> options) {
    226
    227         this.subject = subject;
    228         this.callbackHandler = callbackHandler;
    229         this.sharedState = (Map<String, Object>)sharedState;
    230         this.options = options;
    231
    232         // initialize any configured options
    233         debug = "true".equalsIgnoreCase((String)options.get("debug"));
    234         strongDebug =
    235                 "true".equalsIgnoreCase((String)options.get("strongDebug"));
    236         userProvider = (String)options.get(USER_PROVIDER);
    237         groupProvider = (String)options.get(GROUP_PROVIDER);
    238         tryFirstPass =
    239                 "true".equalsIgnoreCase((String)options.get("tryFirstPass"));
    240         useFirstPass =
    241                 "true".equalsIgnoreCase((String)options.get("useFirstPass"));
    242         storePass =
    243                 "true".equalsIgnoreCase((String)options.get("storePass"));
    244         clearPass =
    245                 "true".equalsIgnoreCase((String)options.get("clearPass"));
    246     }
    247
    248     /**
    249      * Prompt for username and password.
    250      * Verify the password against the relevant name service.
    251      *
    252      * @return true always, since this {@code LoginModule}
    253      *          should not be ignored.
    254      *
    255      * @exception FailedLoginException if the authentication fails.
    256      *
    257      * @exception LoginException if this {@code LoginModule}
    258      *          is unable to perform the authentication.
    259      */
    260     public boolean login() throws LoginException {
    261
    262         if (userProvider == null) {
    263             throw new LoginException
    264                 ("Error: Unable to locate JNDI user provider");
    265         }
    266         if (groupProvider == null) {
    267             throw new LoginException
    268                 ("Error: Unable to locate JNDI group provider");
    269         }
    270
    271         if (debug) {
    272             System.out.println("\t\t[JndiLoginModule] user provider: " +
    273                                 userProvider);
    274             System.out.println("\t\t[JndiLoginModule] group provider: " +
    275                                 groupProvider);
    276         }
    277
    278         // attempt the authentication
    279         if (tryFirstPass) {
    280
    281             try {
    282                 // attempt the authentication by getting the
    283                 // username and password from shared state
    284                 attemptAuthentication(true);
    285
    286                 // authentication succeeded
    287                 succeeded = true;
    288                 if (debug) {
    289                     System.out.println("\t\t[JndiLoginModule] " +
    290                                 "tryFirstPass succeeded");
    291                 }
    292                 return true;
    293             } catch (LoginException le) {
    294                 // authentication failed -- try again below by prompting
    295                 cleanState();
    296                 if (debug) {
    297                     System.out.println("\t\t[JndiLoginModule] " +
    298                                 "tryFirstPass failed with:" +
    299                                 le.toString());
    300                 }
    301             }
    302
    303         } else if (useFirstPass) {
    304
    305             try {
    306                 // attempt the authentication by getting the
    307                 // username and password from shared state
    308                 attemptAuthentication(true);
    309
    310                 // authentication succeeded
    311                 succeeded = true;
    312                 if (debug) {
    313                     System.out.println("\t\t[JndiLoginModule] " +
    314                                 "useFirstPass succeeded");
    315                 }
    316                 return true;
    317             } catch (LoginException le) {
    318                 // authentication failed
    319                 cleanState();
    320                 if (debug) {
    321                     System.out.println("\t\t[JndiLoginModule] " +
    322                                 "useFirstPass failed");
    323                 }
    324                 throw le;
    325             }
    326         }
    327
    328         // attempt the authentication by prompting for the username and pwd
    329         try {
    330             attemptAuthentication(false);
    331
    332             // authentication succeeded
    333            succeeded = true;
    334             if (debug) {
    335                 System.out.println("\t\t[JndiLoginModule] " +
    336                                 "regular authentication succeeded");
    337             }
    338             return true;
    339         } catch (LoginException le) {
    340             cleanState();
    341             if (debug) {
    342                 System.out.println("\t\t[JndiLoginModule] " +
    343                                 "regular authentication failed");
    344             }
    345             throw le;
    346         }
    347     }
    348
    349     /**
    350      * Abstract method to commit the authentication process (phase 2).
    351      *
    352      * <p> This method is called if the LoginContext's
    353      * overall authentication succeeded
    354      * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
    355      * succeeded).
    356      *
    357      * <p> If this LoginModule's own authentication attempt
    358      * succeeded (checked by retrieving the private state saved by the
    359      * {@code login} method), then this method associates a
    360      * {@code UnixPrincipal}
    361      * with the {@code Subject} located in the
    362      * {@code LoginModule}.  If this LoginModule's own
    363      * authentication attempted failed, then this method removes
    364      * any state that was originally saved.
    365      *
    366      * @exception LoginException if the commit fails
    367      *
    368      * @return true if this LoginModule's own login and commit
    369      *          attempts succeeded, or false otherwise.
    370      */
    371     public boolean commit() throws LoginException {
    372
    373         if (succeeded == false) {
    374             return false;
    375         } else {
    376             if (subject.isReadOnly()) {
    377                 cleanState();
    378                 throw new LoginException ("Subject is Readonly");
    379             }
    380             // add Principals to the Subject
    381             if (!subject.getPrincipals().contains(userPrincipal))
    382                 subject.getPrincipals().add(userPrincipal);
    383             if (!subject.getPrincipals().contains(UIDPrincipal))
    384                 subject.getPrincipals().add(UIDPrincipal);
    385             if (!subject.getPrincipals().contains(GIDPrincipal))
    386                 subject.getPrincipals().add(GIDPrincipal);
    387             for (int i = 0; i < supplementaryGroups.size(); i++) {
    388                 if (!subject.getPrincipals().contains
    389                         (supplementaryGroups.get(i)))
    390                     subject.getPrincipals().add(supplementaryGroups.get(i));
    391             }
    392
    393             if (debug) {
    394                 System.out.println("\t\t[JndiLoginModule]: " +
    395                                    "added UnixPrincipal,");
    396                 System.out.println("\t\t\t\tUnixNumericUserPrincipal,");
    397                 System.out.println("\t\t\t\tUnixNumericGroupPrincipal(s),");
    398                 System.out.println("\t\t\t to Subject");
    399             }
    400         }
    401         // in any case, clean out state
    402         cleanState();
    403         commitSucceeded = true;
    404         return true;
    405     }
    406
    407     /**
    408      * This method is called if the LoginContext's
    409      * overall authentication failed.
    410      * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
    411      * did not succeed).
    412      *
    413      * <p> If this LoginModule's own authentication attempt
    414      * succeeded (checked by retrieving the private state saved by the
    415      * {@code login} and {@code commit} methods),
    416      * then this method cleans up any state that was originally saved.
    417      *
    418      * @exception LoginException if the abort fails.
    419      *
    420      * @return false if this LoginModule's own login and/or commit attempts
    421      *          failed, and true otherwise.
    422      */
    423     public boolean abort() throws LoginException {
    424         if (debug)
    425             System.out.println("\t\t[JndiLoginModule]: " +
    426                 "aborted authentication failed");
    427
    428         if (succeeded == false) {
    429             return false;
    430         } else if (succeeded == true && commitSucceeded == false) {
    431
    432             // Clean out state
    433             succeeded = false;
    434             cleanState();
    435
    436             userPrincipal = null;
    437             UIDPrincipal = null;
    438             GIDPrincipal = null;
    439             supplementaryGroups = new LinkedList<UnixNumericGroupPrincipal>();
    440         } else {
    441             // overall authentication succeeded and commit succeeded,
    442             // but someone else's commit failed
    443             logout();
    444         }
    445         return true;
    446     }
    447
    448     /**
    449      * Logout a user.
    450      *
    451      * <p> This method removes the Principals
    452      * that were added by the {@code commit} method.
    453      *
    454      * @exception LoginException if the logout fails.
    455      *
    456      * @return true in all cases since this {@code LoginModule}
    457      *          should not be ignored.
    458      */
    459     public boolean logout() throws LoginException {
    460         if (subject.isReadOnly()) {
    461             cleanState();
    462             throw new LoginException ("Subject is Readonly");
    463         }
    464         subject.getPrincipals().remove(userPrincipal);
    465         subject.getPrincipals().remove(UIDPrincipal);
    466         subject.getPrincipals().remove(GIDPrincipal);
    467         for (int i = 0; i < supplementaryGroups.size(); i++) {
    468             subject.getPrincipals().remove(supplementaryGroups.get(i));
    469         }
    470
    471
    472         // clean out state
    473         cleanState();
    474         succeeded = false;
    475         commitSucceeded = false;
    476
    477         userPrincipal = null;
    478         UIDPrincipal = null;
    479         GIDPrincipal = null;
    480         supplementaryGroups = new LinkedList<UnixNumericGroupPrincipal>();
    481
    482         if (debug) {
    483             System.out.println("\t\t[JndiLoginModule]: " +
    484                 "logged out Subject");
    485         }
    486         return true;
    487     }
    488
    489     /**
    490      * Attempt authentication
    491      *
    492      * @param getPasswdFromSharedState boolean that tells this method whether
    493      *          to retrieve the password from the sharedState.
    494      */
    495     private void attemptAuthentication(boolean getPasswdFromSharedState)
    496     throws LoginException {
    497
    498         String encryptedPassword = null;
    499
    500         // first get the username and password
    501         getUsernamePassword(getPasswdFromSharedState);
    502
    503         try {
    504
    505             // get the user's passwd entry from the user provider URL
    506             InitialContext iCtx = new InitialContext();
    507             ctx = (DirContext)iCtx.lookup(userProvider);
    508
    509             /*
    510             SearchControls controls = new SearchControls
    511                                         (SearchControls.ONELEVEL_SCOPE,
    512                                         0,
    513                                         5000,
    514                                         new String[] { USER_PWD },
    515                                         false,
    516                                         false);
    517             */
    518
    519             SearchControls controls = new SearchControls();
    520             NamingEnumeration<SearchResult> ne = ctx.search("",
    521                                         "(uid=" + username + ")",
    522                                         controls);
    523             if (ne.hasMore()) {
    524                 SearchResult result = ne.next();
    525                 Attributes attributes = result.getAttributes();
    526
    527                 // get the password
    528
    529                 // this module works only if the LDAP directory server
    530                 // is configured to permit read access to the userPassword
    531                 // attribute. The directory administrator need to grant
    532                 // this access.
    533                 //
    534                 // A workaround would be to make the server do authentication
    535                 // by setting the Context.SECURITY_PRINCIPAL
    536                 // and Context.SECURITY_CREDENTIALS property.
    537                 // However, this would make it not work with systems that
    538                 // don't do authentication at the server (like NIS).
    539                 //
    540                 // Setting the SECURITY_* properties and using "simple"
    541                 // authentication for LDAP is recommended only for secure
    542                 // channels. For nonsecure channels, SSL is recommended.
    543
    544                 Attribute pwd = attributes.get(USER_PWD);
    545                 String encryptedPwd = new String((byte[])pwd.get(), "UTF8");
    546                 encryptedPassword = encryptedPwd.substring(CRYPT.length());
    547
    548                 // check the password
    549                 if (verifyPassword
    550                     (encryptedPassword, new String(password)) == true) {
    551
    552                     // authentication succeeded
    553                     if (debug)
    554                         System.out.println("\t\t[JndiLoginModule] " +
    555                                 "attemptAuthentication() succeeded");
    556
    557                 } else {
    558                     // authentication failed
    559                     if (debug)
    560                         System.out.println("\t\t[JndiLoginModule] " +
    561                                 "attemptAuthentication() failed");
    562                     throw new FailedLoginException("Login incorrect");
    563                 }
    564
    565                 // save input as shared state only if
    566                 // authentication succeeded
    567                 if (storePass &&
    568                     !sharedState.containsKey(NAME) &&
    569                     !sharedState.containsKey(PWD)) {
    570                     sharedState.put(NAME, username);
    571                     sharedState.put(PWD, password);
    572                 }
    573
    574                 // create the user principal
    575                 userPrincipal = new UnixPrincipal(username);
    576
    577                 // get the UID
    578                 Attribute uid = attributes.get(USER_UID);
    579                 String uidNumber = (String)uid.get();
    580                 UIDPrincipal = new UnixNumericUserPrincipal(uidNumber);
    581                 if (debug && uidNumber != null) {
    582                     System.out.println("\t\t[JndiLoginModule] " +
    583                                 "user: '" + username + "' has UID: " +
    584                                 uidNumber);
    585                 }
    586
    587                 // get the GID
    588                 Attribute gid = attributes.get(USER_GID);
    589                 String gidNumber = (String)gid.get();
    590                 GIDPrincipal = new UnixNumericGroupPrincipal
    591                                 (gidNumber, true);
    592                 if (debug && gidNumber != null) {
    593                     System.out.println("\t\t[JndiLoginModule] " +
    594                                 "user: '" + username + "' has GID: " +
    595                                 gidNumber);
    596                 }
    597
    598                 // get the supplementary groups from the group provider URL
    599                 ctx = (DirContext)iCtx.lookup(groupProvider);
    600                 ne = ctx.search("", new BasicAttributes("memberUid", username));
    601
    602                 while (ne.hasMore()) {
    603                     result = ne.next();
    604                     attributes = result.getAttributes();
    605
    606                     gid = attributes.get(GROUP_ID);
    607                     String suppGid = (String)gid.get();
    608                     if (!gidNumber.equals(suppGid)) {
    609                         UnixNumericGroupPrincipal suppPrincipal =
    610                             new UnixNumericGroupPrincipal(suppGid, false);
    611                         supplementaryGroups.add(suppPrincipal);
    612                         if (debug && suppGid != null) {
    613                             System.out.println("\t\t[JndiLoginModule] " +
    614                                 "user: '" + username +
    615                                 "' has Supplementary Group: " +
    616                                 suppGid);
    617                         }
    618                     }
    619                 }
    620
    621             } else {
    622                 // bad username
    623                 if (debug) {
    624                     System.out.println("\t\t[JndiLoginModule]: User not found");
    625                 }
    626                 throw new FailedLoginException("User not found");
    627             }
    628         } catch (NamingException ne) {
    629             // bad username
    630             if (debug) {
    631                 System.out.println("\t\t[JndiLoginModule]:  User not found");
    632                 ne.printStackTrace();
    633             }
    634             throw new FailedLoginException("User not found");
    635         } catch (java.io.UnsupportedEncodingException uee) {
    636             // password stored in incorrect format
    637             if (debug) {
    638                 System.out.println("\t\t[JndiLoginModule]:  " +
    639                                 "password incorrectly encoded");
    640                 uee.printStackTrace();
    641             }
    642             throw new LoginException("Login failure due to incorrect " +
    643                                 "password encoding in the password database");
    644         }
    645
    646         // authentication succeeded
    647     }
    648
    649     /**
    650      * Get the username and password.
    651      * This method does not return any value.
    652      * Instead, it sets global name and password variables.
    653      *
    654      * <p> Also note that this method will set the username and password
    655      * values in the shared state in case subsequent LoginModules
    656      * want to use them via use/tryFirstPass.
    657      *
    658      * @param getPasswdFromSharedState boolean that tells this method whether
    659      *          to retrieve the password from the sharedState.
    660      */
    661     private void getUsernamePassword(boolean getPasswdFromSharedState)
    662     throws LoginException {
    663
    664         if (getPasswdFromSharedState) {
    665             // use the password saved by the first module in the stack
    666             username = (String)sharedState.get(NAME);
    667             password = (char[])sharedState.get(PWD);
    668             return;
    669         }
    670
    671         // prompt for a username and password
    672         if (callbackHandler == null)
    673             throw new LoginException("Error: no CallbackHandler available " +
    674                 "to garner authentication information from the user");
    675
    676         String protocol = userProvider.substring(0, userProvider.indexOf(':'));
    677
    678         Callback[] callbacks = new Callback[2];
    679         callbacks[0] = new NameCallback(protocol + " "
    680                                             + getAuthResourceString("username."));
    681         callbacks[1] = new PasswordCallback(protocol + " " +
    682                                                 getAuthResourceString("password."),
    683                                             false);
    684
    685         try {
    686             callbackHandler.handle(callbacks);
    687             username = ((NameCallback)callbacks[0]).getName();
    688             char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
    689             password = new char[tmpPassword.length];
    690             System.arraycopy(tmpPassword, 0,
    691                                 password, 0, tmpPassword.length);
    692             ((PasswordCallback)callbacks[1]).clearPassword();
    693
    694         } catch (java.io.IOException ioe) {
    695             throw new LoginException(ioe.toString());
    696         } catch (UnsupportedCallbackException uce) {
    697             throw new LoginException("Error: " + uce.getCallback().toString() +
    698                         " not available to garner authentication information " +
    699                         "from the user");
    700         }
    701
    702         // print debugging information
    703         if (strongDebug) {
    704             System.out.println("\t\t[JndiLoginModule] " +
    705                                 "user entered username: " +
    706                                 username);
    707             System.out.print("\t\t[JndiLoginModule] " +
    708                                 "user entered password: ");
    709             for (int i = 0; i < password.length; i++)
    710                 System.out.print(password[i]);
    711             System.out.println();
    712         }
    713     }
    714
    715     /**
    716      * Verify a password against the encrypted passwd from /etc/shadow
    717      */
    718     private boolean verifyPassword(String encryptedPassword, String password) {
    719
    720         if (encryptedPassword == null)
    721             return false;
    722
    723         Crypt c = new Crypt();
    724         try {
    725             byte[] oldCrypt = encryptedPassword.getBytes("UTF8");
    726             byte[] newCrypt = c.crypt(password.getBytes("UTF8"),
    727                                       oldCrypt);
    728             if (newCrypt.length != oldCrypt.length)
    729                 return false;
    730             for (int i = 0; i < newCrypt.length; i++) {
    731                 if (oldCrypt[i] != newCrypt[i])
    732                     return false;
    733             }
    734         } catch (java.io.UnsupportedEncodingException uee) {
    735             // cannot happen, but return false just to be safe
    736             return false;
    737         }
    738         return true;
    739     }
    740
    741     /**
    742      * Clean out state because of a failed authentication attempt
    743      */
    744     private void cleanState() {
    745         username = null;
    746         if (password != null) {
    747             for (int i = 0; i < password.length; i++)
    748                 password[i] = ' ';
    749             password = null;
    750         }
    751         ctx = null;
    752
    753         if (clearPass) {
    754             sharedState.remove(NAME);
    755             sharedState.remove(PWD);
    756         }
    757     }
    758 }
    --- EOF ---