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 }