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