1 17 package org.alfresco.repo.security.authentication.ntlm; 18 19 import java.io.IOException ; 20 import java.net.InetAddress ; 21 import java.net.UnknownHostException ; 22 import java.security.NoSuchAlgorithmException ; 23 import java.security.Provider ; 24 import java.security.Security ; 25 import java.util.Enumeration ; 26 import java.util.Hashtable ; 27 28 import net.sf.acegisecurity.Authentication; 29 import net.sf.acegisecurity.AuthenticationServiceException; 30 import net.sf.acegisecurity.BadCredentialsException; 31 import net.sf.acegisecurity.CredentialsExpiredException; 32 import net.sf.acegisecurity.GrantedAuthority; 33 import net.sf.acegisecurity.GrantedAuthorityImpl; 34 35 import org.alfresco.error.AlfrescoRuntimeException; 36 import org.alfresco.filesys.server.auth.PasswordEncryptor; 37 import org.alfresco.filesys.server.auth.passthru.AuthenticateSession; 38 import org.alfresco.filesys.server.auth.passthru.PassthruServers; 39 import org.alfresco.filesys.smb.SMBException; 40 import org.alfresco.filesys.smb.SMBStatus; 41 import org.alfresco.model.ContentModel; 42 import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent; 43 import org.alfresco.repo.security.authentication.AuthenticationException; 44 import org.alfresco.repo.security.authentication.NTLMMode; 45 import org.alfresco.service.cmr.repository.NodeRef; 46 import org.alfresco.service.cmr.repository.NodeService; 47 import org.alfresco.service.cmr.security.PersonService; 48 import org.apache.commons.logging.Log; 49 import org.apache.commons.logging.LogFactory; 50 51 59 public class NTLMAuthenticationComponentImpl extends AbstractAuthenticationComponent 60 { 61 63 private static final Log logger = LogFactory.getLog("org.alfresco.passthru.auth"); 64 65 69 public static final String NTLMAuthorityGuest = "Guest"; 70 public static final String NTLMAuthorityAdministrator = "Administrator"; 71 72 74 private static final long DefaultSessionTimeout = 60000L; private static final long MinimumSessionTimeout = 5000L; 77 79 private PassthruServers m_passthruServers; 80 81 83 private PasswordEncryptor m_encryptor; 84 85 87 private boolean m_allowGuest; 88 89 94 private Hashtable <NTLMPassthruToken,AuthenticateSession> m_passthruSessions; 95 96 98 private long m_passthruSessTmo = DefaultSessionTimeout; 99 100 102 private PassthruReaperThread m_reaperThread; 103 104 106 private PersonService m_personService; 107 private NodeService m_nodeService; 108 109 112 class PassthruReaperThread extends Thread 113 { 114 116 private boolean m_ishutdown; 117 118 120 private long m_wakeupInterval = m_passthruSessTmo / 2; 121 122 125 PassthruReaperThread() 126 { 127 setDaemon(true); 128 setName("PassthruReaper"); 129 start(); 130 } 131 132 137 public final void setWakeup(long wakeup) 138 { 139 m_wakeupInterval = wakeup; 140 } 141 142 145 public void run() 146 { 147 149 m_ishutdown = false; 150 151 while ( m_ishutdown == false) 152 { 153 155 try 156 { 157 sleep( m_wakeupInterval); 158 } 159 catch ( InterruptedException ex) 160 { 161 } 162 163 165 if ( m_passthruSessions.size() > 0) 166 { 167 169 Enumeration <NTLMPassthruToken> tokenEnum = m_passthruSessions.keys(); 170 long timeNow = System.currentTimeMillis(); 171 172 while (tokenEnum.hasMoreElements()) 173 { 174 176 NTLMPassthruToken ntlmToken = tokenEnum.nextElement(); 177 178 if ( ntlmToken != null && ntlmToken.getAuthenticationExpireTime() < timeNow) 179 { 180 182 AuthenticateSession authSess = m_passthruSessions.get(ntlmToken); 183 if ( authSess != null) 184 { 185 try 186 { 187 189 authSess.CloseSession(); 190 } 191 catch ( Exception ex) 192 { 193 195 if(logger.isDebugEnabled()) 196 logger.debug("Error closing expired authentication session", ex); 197 } 198 } 199 200 202 m_passthruSessions.remove(ntlmToken); 203 204 206 if(logger.isDebugEnabled()) 207 logger.debug("Removed expired NTLM token " + ntlmToken); 208 } 209 } 210 } 211 } 212 213 215 if(logger.isDebugEnabled()) 216 logger.debug("Passthru reaper thread shutdown"); 217 } 218 219 222 public final void shutdownRequest() 223 { 224 m_ishutdown = true; 225 this.interrupt(); 226 } 227 } 228 229 232 public NTLMAuthenticationComponentImpl() { 233 234 236 m_passthruServers = new PassthruServers(); 237 238 240 m_encryptor = new PasswordEncryptor(); 241 242 244 m_passthruSessions = new Hashtable <NTLMPassthruToken,AuthenticateSession>(); 245 m_reaperThread = new PassthruReaperThread(); 246 } 247 248 253 public final boolean allowsGuest() 254 { 255 return m_allowGuest; 256 } 257 258 263 public void setDomain(String domain) { 264 265 267 if ( m_passthruServers.getTotalServerCount() > 0) 268 throw new AlfrescoRuntimeException("Passthru server list already configured"); 269 270 272 m_passthruServers.setDomain(domain); 273 } 274 275 280 public void setServers(String servers) { 281 282 284 if ( m_passthruServers.getTotalServerCount() > 0) 285 throw new AlfrescoRuntimeException("Passthru server list already configured"); 286 287 289 m_passthruServers.setServerList(servers); 290 } 291 292 297 public void setUseLocalServer(String useLocal) 298 { 299 301 if ( Boolean.parseBoolean(useLocal) == true) 302 { 303 305 if ( m_passthruServers.getTotalServerCount() > 0) 306 throw new AlfrescoRuntimeException("Passthru server list already configured"); 307 308 try 309 { 310 312 InetAddress [] localAddrs = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName()); 313 314 316 if ( localAddrs != null && localAddrs.length > 0) 317 { 318 StringBuilder addrStr = new StringBuilder (); 319 320 for ( InetAddress curAddr : localAddrs) 321 { 322 if ( curAddr.isLoopbackAddress() == false) 323 { 324 addrStr.append(curAddr.getHostAddress()); 325 addrStr.append(","); 326 } 327 } 328 329 if ( addrStr.length() > 0) 330 addrStr.setLength(addrStr.length() - 1); 331 332 334 m_passthruServers.setServerList(addrStr.toString()); 335 } 336 else 337 throw new AlfrescoRuntimeException("No local server address(es)"); 338 } 339 catch ( UnknownHostException ex) 340 { 341 throw new AlfrescoRuntimeException("Failed to get local address list"); 342 } 343 } 344 } 345 346 351 public void setGuestAccess(String guest) 352 { 353 m_allowGuest = Boolean.parseBoolean(guest); 354 } 355 356 361 public void setJCEProvider(String providerClass) 362 { 363 366 try 367 { 368 369 371 Object jceObj = Class.forName(providerClass).newInstance(); 372 if (jceObj instanceof java.security.Provider ) 373 { 374 375 377 Provider jceProvider = (Provider ) jceObj; 378 379 381 Security.addProvider(jceProvider); 382 383 385 if ( logger.isDebugEnabled()) 386 logger.debug("Using JCE provider " + providerClass); 387 } 388 else 389 { 390 throw new AlfrescoRuntimeException("JCE provider class is not a valid Provider class"); 391 } 392 } 393 catch (ClassNotFoundException ex) 394 { 395 throw new AlfrescoRuntimeException("JCE provider class " + providerClass + " not found"); 396 } 397 catch (Exception ex) 398 { 399 throw new AlfrescoRuntimeException("JCE provider class error", ex); 400 } 401 } 402 403 408 public void setSessionTimeout(String sessTmo) 409 { 410 412 try 413 { 414 416 long sessTmoMilli = Long.parseLong(sessTmo) * 1000L; 417 418 if ( sessTmoMilli < MinimumSessionTimeout) 419 throw new AlfrescoRuntimeException("Authentication session timeout too low, " + sessTmo); 420 421 423 m_passthruSessTmo = sessTmoMilli; 424 425 427 m_reaperThread.setWakeup( sessTmoMilli / 2); 428 } 429 catch(NumberFormatException ex) 430 { 431 throw new AlfrescoRuntimeException("Invalid authenication session timeout value"); 432 } 433 } 434 435 440 public final void setPersonService(PersonService personService) 441 { 442 m_personService = personService; 443 } 444 445 450 public final void setNodeService(NodeService nodeService) 451 { 452 m_nodeService = nodeService; 453 } 454 455 460 private final long getSessionTimeout() 461 { 462 return m_passthruSessTmo; 463 } 464 465 472 public void authenticate(String userName, char[] password) throws AuthenticationException 473 { 474 476 if ( logger.isDebugEnabled()) 477 logger.debug("Authenticate user=" + userName + " via local credentials"); 478 479 481 NTLMLocalToken authToken = new NTLMLocalToken(userName, new String (password)); 482 483 485 authenticate( authToken); 486 setCurrentUser( userName.toLowerCase()); 487 } 488 489 496 public Authentication authenticate(Authentication auth) throws AuthenticationException 497 { 498 500 if ( logger.isDebugEnabled()) 501 logger.debug("Authenticate " + auth + " via token"); 502 503 505 if( auth instanceof NTLMPassthruToken) 506 { 507 509 NTLMPassthruToken ntlmToken = (NTLMPassthruToken) auth; 510 511 513 authenticatePassthru(ntlmToken); 514 } 515 516 518 else if( auth instanceof NTLMLocalToken) 519 { 520 AuthenticateSession authSess = null; 521 522 try 523 { 524 525 527 NTLMLocalToken ntlmToken = (NTLMLocalToken) auth; 528 529 531 authSess = m_passthruServers.openSession(); 532 533 535 authenticateLocal(ntlmToken, authSess); 536 } 537 finally 538 { 539 541 if ( authSess != null) 542 { 543 try 544 { 545 authSess.CloseSession(); 546 } 547 catch ( Exception ex) 548 { 549 } 550 } 551 } 552 } 553 else 554 { 555 557 throw new AuthenticationException("Unsupported authentication token type"); 558 } 559 560 562 return getCurrentAuthentication(); 563 } 564 565 570 public NTLMMode getNTLMMode() 571 { 572 return NTLMMode.PASS_THROUGH; 573 } 574 575 581 public String getMD4HashedPassword(String userName) 582 { 583 585 throw new AlfrescoRuntimeException("MD4 passwords not supported"); 586 } 587 588 594 private void authenticateLocal(NTLMLocalToken ntlmToken, AuthenticateSession authSess) 595 { 596 try 597 { 598 600 String username = (String ) ntlmToken.getPrincipal(); 601 String plainPwd = (String ) ntlmToken.getCredentials(); 602 byte[] ntlm1Pwd = m_encryptor.generateEncryptedPassword( plainPwd, authSess.getEncryptionKey(), PasswordEncryptor.NTLM1); 603 604 608 authSess.doSessionSetup(username, null, ntlm1Pwd); 609 610 612 if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST")) 613 { 614 616 if ( allowsGuest()) 617 { 618 620 GrantedAuthority[] authorities = new GrantedAuthority[2]; 621 authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest); 622 authorities[1] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED"); 623 624 ntlmToken.setAuthorities(authorities); 625 } 626 else 627 { 628 630 throw new AuthenticationException("Guest logons disabled"); 631 } 632 } 633 else 634 { 635 637 GrantedAuthority[] authorities = new GrantedAuthority[1]; 638 authorities[0] = new GrantedAuthorityImpl("ROLE_AUTHENTICATED"); 639 640 ntlmToken.setAuthorities(authorities); 641 } 642 643 645 ntlmToken.setAuthenticated(true); 646 647 649 NodeRef userNode = m_personService.getPerson(username); 650 if ( userNode != null) 651 { 652 654 String personName = (String ) m_nodeService.getProperty(userNode, ContentModel.PROP_USERNAME); 655 setCurrentUser(personName); 656 657 659 if ( logger.isDebugEnabled()) 660 logger.debug("Setting current user using person " + personName + " (username " + username + ")"); 661 } 662 else 663 { 664 666 if ( m_personService.getUserNamesAreCaseSensitive() == false) 667 username = username.toLowerCase(); 668 setCurrentUser( username); 669 670 672 if ( logger.isDebugEnabled()) 673 logger.debug("Setting current user using username " + username); 674 } 675 676 678 if ( logger.isDebugEnabled()) 679 logger.debug("Authenticated token=" + ntlmToken); 680 } 681 catch (NoSuchAlgorithmException ex) 682 { 683 685 throw new AuthenticationServiceException("JCE provider error", ex); 686 } 687 catch (IOException ex) 688 { 689 691 throw new AuthenticationServiceException("I/O error", ex); 692 } 693 catch (SMBException ex) 694 { 695 697 if ( ex.getErrorClass() == SMBStatus.NTErr) 698 { 699 AuthenticationException authEx = null; 700 701 switch( ex.getErrorCode()) 702 { 703 case SMBStatus.NTLogonFailure: 704 authEx = new AuthenticationException("Logon failure"); 705 break; 706 case SMBStatus.NTAccountDisabled: 707 authEx = new AuthenticationException("Account disabled"); 708 break; 709 default: 710 authEx = new AuthenticationException("Logon failure"); 711 break; 712 } 713 714 throw authEx; 715 } 716 else 717 throw new AuthenticationException("Logon failure"); 718 } 719 } 720 721 726 private void authenticatePassthru(NTLMPassthruToken ntlmToken) 727 { 728 731 AuthenticateSession authSess = m_passthruSessions.get(ntlmToken); 732 733 if ( authSess == null) 734 { 735 738 if ( ntlmToken.getChallenge() != null) 739 throw new CredentialsExpiredException("Authentication session expired"); 740 741 743 authSess = m_passthruServers.openSession(); 744 745 ntlmToken.setAuthenticationExpireTime(System.currentTimeMillis() + getSessionTimeout()); 746 747 749 ntlmToken.setChallenge(new NTLMChallenge(authSess.getEncryptionKey())); 750 751 StringBuilder details = new StringBuilder (); 752 753 755 details.append(authSess.getDomain()); 756 details.append("\\"); 757 details.append(authSess.getPCShare().getNodeName()); 758 details.append(","); 759 details.append(authSess.getSession().getProtocolName()); 760 761 ntlmToken.setDetails(details.toString()); 762 763 765 m_passthruSessions.put(ntlmToken, authSess); 766 767 769 if ( logger.isDebugEnabled()) 770 logger.debug("Passthru stage 1 token " + ntlmToken); 771 } 772 else 773 { 774 try 775 { 776 778 byte[] lmPwd = null; 779 byte[] ntlmPwd = null; 780 781 if ( ntlmToken.getPasswordType() == PasswordEncryptor.LANMAN) 782 lmPwd = ntlmToken.getHashedPassword(); 783 else if ( ntlmToken.getPasswordType() == PasswordEncryptor.NTLM1) 784 ntlmPwd = ntlmToken.getHashedPassword(); 785 786 String username = (String ) ntlmToken.getPrincipal(); 787 788 authSess.doSessionSetup(username, lmPwd, ntlmPwd); 789 790 792 if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST")) 793 { 794 796 if ( allowsGuest()) 797 { 798 800 GrantedAuthority[] authorities = new GrantedAuthority[1]; 801 authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest); 802 803 ntlmToken.setAuthorities(authorities); 804 } 805 else 806 { 807 809 throw new BadCredentialsException("Guest logons disabled"); 810 } 811 } 812 813 815 ntlmToken.setAuthenticated(true); 816 817 819 NodeRef userNode = m_personService.getPerson(username); 820 if ( userNode != null) 821 { 822 824 String personName = (String ) m_nodeService.getProperty(userNode, ContentModel.PROP_USERNAME); 825 setCurrentUser(personName); 826 827 829 if ( logger.isDebugEnabled()) 830 logger.debug("Setting current user using person " + personName + " (username " + username + ")"); 831 } 832 else 833 { 834 836 if ( m_personService.getUserNamesAreCaseSensitive() == false) 837 username = username.toLowerCase(); 838 setCurrentUser( username); 839 840 842 if ( logger.isDebugEnabled()) 843 logger.debug("Setting current user using username " + username); 844 } 845 } 846 catch (IOException ex) 847 { 848 850 throw new AuthenticationServiceException("I/O error", ex); 851 } 852 catch (SMBException ex) 853 { 854 856 if ( logger.isDebugEnabled()) 857 logger.debug("Passthru exception, " + ex); 858 859 861 if ( ex.getErrorClass() == SMBStatus.NTErr) 862 { 863 AuthenticationException authEx = null; 864 865 switch( ex.getErrorCode()) 866 { 867 case SMBStatus.NTLogonFailure: 868 authEx = new AuthenticationException("Logon failure"); 869 break; 870 case SMBStatus.NTAccountDisabled: 871 authEx = new AuthenticationException("Account disabled"); 872 break; 873 default: 874 authEx = new AuthenticationException("Logon failure"); 875 break; 876 } 877 878 throw authEx; 879 } 880 else 881 throw new BadCredentialsException("Logon failure"); 882 } 883 finally 884 { 885 887 if ( authSess != null) 888 { 889 try 890 { 891 893 m_passthruSessions.remove(ntlmToken); 894 895 897 authSess.CloseSession(); 898 } 899 catch (Exception ex) 900 { 901 } 902 } 903 } 904 } 905 } 906 907 913 public boolean exists(String userName) 914 { 915 throw new UnsupportedOperationException (); 916 } 917 918 @Override 919 protected boolean implementationAllowsGuestLogin() 920 { 921 return allowsGuest(); 922 } 923 924 925 } 926 | Popular Tags |