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