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