1 7 8 package com.sun.security.auth.module; 9 10 import java.io.IOException ; 11 import java.security.AccessController ; 12 import java.net.SocketPermission ; 13 import java.security.Principal ; 14 import java.security.PrivilegedAction ; 15 import java.util.Arrays ; 16 import java.util.Hashtable ; 17 import java.util.Iterator ; 18 import java.util.Map ; 19 import java.util.ResourceBundle ; 20 import java.util.regex.Matcher ; 21 import java.util.regex.Pattern ; 22 import java.util.Set ; 23 24 import javax.naming.*; 25 import javax.naming.directory.*; 26 import javax.naming.ldap.*; 27 import javax.security.auth.*; 28 import javax.security.auth.callback.*; 29 import javax.security.auth.login.*; 30 import javax.security.auth.spi.*; 31 32 import com.sun.security.auth.LdapPrincipal; 33 import com.sun.security.auth.UserPrincipal; 34 35 import sun.security.util.AuthResources; 36 37 292 public class LdapLoginModule implements LoginModule { 293 294 private static final ResourceBundle rb = 296 (ResourceBundle ) AccessController.doPrivileged( 297 new PrivilegedAction () { 298 public Object run() { 299 return ResourceBundle.getBundle( 300 "sun.security.util.AuthResources"); 301 } 302 } 303 ); 304 305 private static final String USERNAME_KEY = "javax.security.auth.login.name"; 307 private static final String PASSWORD_KEY = 308 "javax.security.auth.login.password"; 309 310 private static final String USER_PROVIDER = "userProvider"; 312 private static final String USER_FILTER = "userFilter"; 313 private static final String AUTHC_IDENTITY = "authIdentity"; 314 private static final String AUTHZ_IDENTITY = "authzIdentity"; 315 316 private static final String USERNAME_TOKEN = "{USERNAME}"; 318 private static final Pattern USERNAME_PATTERN = 319 Pattern.compile("\\{USERNAME\\}"); 320 321 private String userProvider; 323 private String userFilter; 324 private String authcIdentity; 325 private String authzIdentity; 326 private String authzIdentityAttr = null; 327 private boolean useSSL = true; 328 private boolean authFirst = false; 329 private boolean authOnly = false; 330 private boolean useFirstPass = false; 331 private boolean tryFirstPass = false; 332 private boolean storePass = false; 333 private boolean clearPass = false; 334 private boolean debug = false; 335 336 private boolean succeeded = false; 338 private boolean commitSucceeded = false; 339 340 private String username; 342 private char[] password; 343 344 private LdapPrincipal ldapPrincipal; 346 private UserPrincipal userPrincipal; 347 private UserPrincipal authzPrincipal; 348 349 private Subject subject; 351 private CallbackHandler callbackHandler; 352 private Map sharedState; 353 private Map options; 354 private LdapContext ctx; 355 private Matcher identityMatcher = null; 356 private Matcher filterMatcher = null; 357 private Hashtable ldapEnvironment; 358 private SearchControls constraints = null; 359 360 371 public void initialize(Subject subject, CallbackHandler callbackHandler, 372 Map <String , ?> sharedState, Map <String , ?> options) { 373 374 this.subject = subject; 375 this.callbackHandler = callbackHandler; 376 this.sharedState = sharedState; 377 this.options = options; 378 379 ldapEnvironment = new Hashtable (9); 380 ldapEnvironment.put(Context.INITIAL_CONTEXT_FACTORY, 381 "com.sun.jndi.ldap.LdapCtxFactory"); 382 383 Set keys = options.keySet(); 385 String key; 386 for (Iterator i = keys.iterator(); i.hasNext(); ) { 387 key = (String ) i.next(); 388 if (key.indexOf(".") > -1) { 389 ldapEnvironment.put(key, options.get(key)); 390 } 391 } 392 393 395 userProvider = (String )options.get(USER_PROVIDER); 396 if (userProvider != null) { 397 ldapEnvironment.put(Context.PROVIDER_URL, userProvider); 398 } 399 400 authcIdentity = (String )options.get(AUTHC_IDENTITY); 401 if (authcIdentity != null && 402 (authcIdentity.indexOf(USERNAME_TOKEN) != -1)) { 403 identityMatcher = USERNAME_PATTERN.matcher(authcIdentity); 404 } 405 406 userFilter = (String )options.get(USER_FILTER); 407 if (userFilter != null) { 408 if (userFilter.indexOf(USERNAME_TOKEN) != -1) { 409 filterMatcher = USERNAME_PATTERN.matcher(userFilter); 410 } 411 constraints = new SearchControls(); 412 constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); 413 constraints.setReturningAttributes(new String [0]); constraints.setReturningObjFlag(true); } 416 417 authzIdentity = (String )options.get(AUTHZ_IDENTITY); 418 if (authzIdentity != null && 419 authzIdentity.startsWith("{") && authzIdentity.endsWith("}")) { 420 if (constraints != null) { 421 authzIdentityAttr = 422 authzIdentity.substring(1, authzIdentity.length() - 1); 423 constraints.setReturningAttributes( 424 new String []{authzIdentityAttr}); 425 } 426 authzIdentity = null; } 428 429 if (authcIdentity != null) { 431 if (userFilter != null) { 432 authFirst = true; } else { 434 authOnly = true; } 436 } 437 438 if ("false".equalsIgnoreCase((String )options.get("useSSL"))) { 439 useSSL = false; 440 ldapEnvironment.remove(Context.SECURITY_PROTOCOL); 441 } else { 442 ldapEnvironment.put(Context.SECURITY_PROTOCOL, "ssl"); 443 } 444 445 tryFirstPass = 446 "true".equalsIgnoreCase((String )options.get("tryFirstPass")); 447 448 useFirstPass = 449 "true".equalsIgnoreCase((String )options.get("useFirstPass")); 450 451 storePass = "true".equalsIgnoreCase((String )options.get("storePass")); 452 453 clearPass = "true".equalsIgnoreCase((String )options.get("clearPass")); 454 455 debug = "true".equalsIgnoreCase((String )options.get("debug")); 456 457 if (debug) { 458 if (authFirst) { 459 System.out.println("\t\t[LdapLoginModule] " + 460 "authentication-first mode; " + 461 (useSSL ? "SSL enabled" : "SSL disabled")); 462 } else if (authOnly) { 463 System.out.println("\t\t[LdapLoginModule] " + 464 "authentication-only mode; " + 465 (useSSL ? "SSL enabled" : "SSL disabled")); 466 } else { 467 System.out.println("\t\t[LdapLoginModule] " + 468 "search-first mode; " + 469 (useSSL ? "SSL enabled" : "SSL disabled")); 470 } 471 } 472 } 473 474 486 public boolean login() throws LoginException { 487 488 if (userProvider == null) { 489 throw new LoginException 490 ("Unable to locate the LDAP directory service"); 491 } 492 493 if (debug) { 494 System.out.println("\t\t[LdapLoginModule] user provider: " + 495 userProvider); 496 } 497 498 if (tryFirstPass) { 500 501 try { 502 attemptAuthentication(true); 505 506 succeeded = true; 508 if (debug) { 509 System.out.println("\t\t[LdapLoginModule] " + 510 "tryFirstPass succeeded"); 511 } 512 return true; 513 514 } catch (LoginException le) { 515 cleanState(); 517 if (debug) { 518 System.out.println("\t\t[LdapLoginModule] " + 519 "tryFirstPass failed: " + le.toString()); 520 } 521 } 522 523 } else if (useFirstPass) { 524 525 try { 526 attemptAuthentication(true); 529 530 succeeded = true; 532 if (debug) { 533 System.out.println("\t\t[LdapLoginModule] " + 534 "useFirstPass succeeded"); 535 } 536 return true; 537 538 } catch (LoginException le) { 539 cleanState(); 541 if (debug) { 542 System.out.println("\t\t[LdapLoginModule] " + 543 "useFirstPass failed"); 544 } 545 throw le; 546 } 547 } 548 549 try { 551 attemptAuthentication(false); 552 553 succeeded = true; 555 if (debug) { 556 System.out.println("\t\t[LdapLoginModule] " + 557 "authentication succeeded"); 558 } 559 return true; 560 561 } catch (LoginException le) { 562 cleanState(); 563 if (debug) { 564 System.out.println("\t\t[LdapLoginModule] " + 565 "authentication failed"); 566 } 567 throw le; 568 } 569 } 570 571 592 public boolean commit() throws LoginException { 593 594 if (succeeded == false) { 595 return false; 596 } else { 597 if (subject.isReadOnly()) { 598 cleanState(); 599 throw new LoginException ("Subject is read-only"); 600 } 601 Set principals = subject.getPrincipals(); 603 if (! principals.contains(ldapPrincipal)) { 604 principals.add(ldapPrincipal); 605 } 606 if (debug) { 607 System.out.println("\t\t[LdapLoginModule] " + 608 "added LdapPrincipal \"" + 609 ldapPrincipal + 610 "\" to Subject"); 611 } 612 613 if (! principals.contains(userPrincipal)) { 614 principals.add(userPrincipal); 615 } 616 if (debug) { 617 System.out.println("\t\t[LdapLoginModule] " + 618 "added UserPrincipal \"" + 619 userPrincipal + 620 "\" to Subject"); 621 } 622 623 if (authzPrincipal != null && 624 (! principals.contains(authzPrincipal))) { 625 principals.add(authzPrincipal); 626 627 if (debug) { 628 System.out.println("\t\t[LdapLoginModule] " + 629 "added UserPrincipal \"" + 630 authzPrincipal + 631 "\" to Subject"); 632 } 633 } 634 } 635 cleanState(); 637 commitSucceeded = true; 638 return true; 639 } 640 641 657 public boolean abort() throws LoginException { 658 if (debug) 659 System.out.println("\t\t[LdapLoginModule] " + 660 "aborted authentication"); 661 662 if (succeeded == false) { 663 return false; 664 } else if (succeeded == true && commitSucceeded == false) { 665 666 succeeded = false; 668 cleanState(); 669 670 ldapPrincipal = null; 671 userPrincipal = null; 672 authzPrincipal = null; 673 } else { 674 logout(); 677 } 678 return true; 679 } 680 681 691 public boolean logout() throws LoginException { 692 if (subject.isReadOnly()) { 693 cleanState(); 694 throw new LoginException ("Subject is read-only"); 695 } 696 Set principals = subject.getPrincipals(); 697 principals.remove(ldapPrincipal); 698 principals.remove(userPrincipal); 699 if (authzIdentity != null) { 700 principals.remove(authzPrincipal); 701 } 702 703 cleanState(); 705 succeeded = false; 706 commitSucceeded = false; 707 708 ldapPrincipal = null; 709 userPrincipal = null; 710 authzPrincipal = null; 711 712 if (debug) { 713 System.out.println("\t\t[LdapLoginModule] logged out Subject"); 714 } 715 return true; 716 } 717 718 725 private void attemptAuthentication(boolean getPasswdFromSharedState) 726 throws LoginException { 727 728 getUsernamePassword(getPasswdFromSharedState); 730 731 if (password == null || password.length == 0) { 732 throw (LoginException) 733 new FailedLoginException("No password was supplied"); 734 } 735 736 String dn = ""; 737 738 if (authFirst || authOnly) { 739 740 String id = replaceUsernameToken(identityMatcher, authcIdentity); 741 742 ldapEnvironment.put(Context.SECURITY_CREDENTIALS, password); 744 ldapEnvironment.put(Context.SECURITY_PRINCIPAL, id); 745 746 if (debug) { 747 System.out.println("\t\t[LdapLoginModule] " + 748 "attempting to authenticate user: " + username); 749 } 750 751 try { 752 ctx = new InitialLdapContext(ldapEnvironment, null); 754 755 } catch (NamingException e) { 756 throw (LoginException) 757 new FailedLoginException("Cannot bind to LDAP server") 758 .initCause(e); 759 } 760 761 763 if (userFilter != null) { 765 dn = findUserDN(ctx); 766 } else { 767 dn = id; 768 } 769 770 } else { 771 772 try { 773 ctx = new InitialLdapContext(ldapEnvironment, null); 775 776 } catch (NamingException e) { 777 throw (LoginException) 778 new FailedLoginException("Cannot connect to LDAP server") 779 .initCause(e); 780 } 781 782 dn = findUserDN(ctx); 784 785 try { 786 787 ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple"); 789 ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, dn); 790 ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password); 791 792 if (debug) { 793 System.out.println("\t\t[LdapLoginModule] " + 794 "attempting to authenticate user: " + username); 795 } 796 ctx.reconnect(null); 798 799 801 } catch (NamingException e) { 802 throw (LoginException) 803 new FailedLoginException("Cannot bind to LDAP server") 804 .initCause(e); 805 } 806 } 807 808 if (storePass && 810 !sharedState.containsKey(USERNAME_KEY) && 811 !sharedState.containsKey(PASSWORD_KEY)) { 812 sharedState.put(USERNAME_KEY, username); 813 sharedState.put(PASSWORD_KEY, password); 814 } 815 816 userPrincipal = new UserPrincipal(username); 818 if (authzIdentity != null) { 819 authzPrincipal = new UserPrincipal(authzIdentity); 820 } 821 822 try { 823 824 ldapPrincipal = new LdapPrincipal(dn); 825 826 } catch (InvalidNameException e) { 827 if (debug) { 828 System.out.println("\t\t[LdapLoginModule] " + 829 "cannot create LdapPrincipal: bad DN"); 830 } 831 throw (LoginException) 832 new FailedLoginException("Cannot create LdapPrincipal") 833 .initCause(e); 834 } 835 } 836 837 847 private String findUserDN(LdapContext ctx) throws LoginException { 848 849 String userDN = ""; 850 851 if (userFilter != null) { 853 if (debug) { 854 System.out.println("\t\t[LdapLoginModule] " + 855 "searching for entry belonging to user: " + username); 856 } 857 } else { 858 if (debug) { 859 System.out.println("\t\t[LdapLoginModule] " + 860 "cannot search for entry belonging to user: " + username); 861 } 862 throw (LoginException) 863 new FailedLoginException("Cannot find user's LDAP entry"); 864 } 865 866 try { 867 NamingEnumeration results = ctx.search("", 868 replaceUsernameToken(filterMatcher, userFilter), constraints); 869 870 if (results.hasMore()) { 873 SearchResult entry = (SearchResult) results.next(); 874 875 userDN = ((Context)entry.getObject()).getNameInNamespace(); 879 880 if (debug) { 881 System.out.println("\t\t[LdapLoginModule] found entry: " + 882 userDN); 883 } 884 885 if (authzIdentityAttr != null) { 887 Attribute attr = 888 entry.getAttributes().get(authzIdentityAttr); 889 if (attr != null) { 890 Object val = attr.get(); 891 if (val instanceof String ) { 892 authzIdentity = (String ) val; 893 } 894 } 895 } 896 897 results.close(); 898 899 } else { 900 if (debug) { 902 System.out.println("\t\t[LdapLoginModule] user's entry " + 903 "not found"); 904 } 905 } 906 907 } catch (NamingException e) { 908 } 910 911 if (userDN.equals("")) { 912 throw (LoginException) 913 new FailedLoginException("Cannot find user's LDAP entry"); 914 } else { 915 return userDN; 916 } 917 } 918 919 925 private String replaceUsernameToken(Matcher matcher, String string) { 926 return matcher != null ? matcher.replaceAll(username) : string; 927 } 928 929 942 private void getUsernamePassword(boolean getPasswdFromSharedState) 943 throws LoginException { 944 945 if (getPasswdFromSharedState) { 946 username = (String )sharedState.get(USERNAME_KEY); 948 password = (char[])sharedState.get(PASSWORD_KEY); 949 return; 950 } 951 952 if (callbackHandler == null) 954 throw new LoginException("No CallbackHandler available " + 955 "to acquire authentication information from the user"); 956 957 Callback[] callbacks = new Callback[2]; 958 callbacks[0] = new NameCallback(rb.getString("username: ")); 959 callbacks[1] = new PasswordCallback(rb.getString("password: "), false); 960 961 try { 962 callbackHandler.handle(callbacks); 963 username = ((NameCallback)callbacks[0]).getName(); 964 char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword(); 965 password = new char[tmpPassword.length]; 966 System.arraycopy(tmpPassword, 0, 967 password, 0, tmpPassword.length); 968 ((PasswordCallback)callbacks[1]).clearPassword(); 969 970 } catch (java.io.IOException ioe) { 971 throw new LoginException(ioe.toString()); 972 973 } catch (UnsupportedCallbackException uce) { 974 throw new LoginException("Error: " + uce.getCallback().toString() + 975 " not available to acquire authentication information" + 976 " from the user"); 977 } 978 } 979 980 983 private void cleanState() { 984 username = null; 985 if (password != null) { 986 Arrays.fill(password, ' '); 987 password = null; 988 } 989 try { 990 if (ctx != null) { 991 ctx.close(); 992 } 993 } catch (NamingException e) { 994 } 996 ctx = null; 997 998 if (clearPass) { 999 sharedState.remove(USERNAME_KEY); 1000 sharedState.remove(PASSWORD_KEY); 1001 } 1002 } 1003} 1004 | Popular Tags |