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