KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > repo > security > authentication > ntlm > NTLMAuthenticationProvider


1 /*
2  * Copyright (C) 2006 Alfresco, Inc.
3  *
4  * Licensed under the Mozilla Public License version 1.1
5  * with a permitted attribution clause. You may obtain a
6  * copy of the License at
7  *
8  * http://www.alfresco.org/legal/license.txt
9  *
10  * Unless required by applicable law or agreed to in writing,
11  * software distributed under the License is distributed on an
12  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific
14  * language governing permissions and limitations under the
15  * License.
16  */

17 package org.alfresco.repo.security.authentication.ntlm;
18
19 import java.io.IOException JavaDoc;
20 import java.net.InetAddress JavaDoc;
21 import java.net.UnknownHostException JavaDoc;
22 import java.security.NoSuchAlgorithmException JavaDoc;
23 import java.security.Provider JavaDoc;
24 import java.security.Security JavaDoc;
25 import java.util.Enumeration JavaDoc;
26 import java.util.Hashtable JavaDoc;
27
28 import org.alfresco.error.AlfrescoRuntimeException;
29 import org.alfresco.filesys.server.auth.PasswordEncryptor;
30 import org.alfresco.filesys.server.auth.passthru.AuthenticateSession;
31 import org.alfresco.filesys.server.auth.passthru.PassthruServers;
32 import org.alfresco.filesys.smb.SMBException;
33 import org.alfresco.filesys.smb.SMBStatus;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37 import net.sf.acegisecurity.*;
38 import net.sf.acegisecurity.providers.*;
39
40 /**
41  * NTLM Authentication Provider
42  *
43  * @author GKSpencer
44  */

45 public class NTLMAuthenticationProvider implements AuthenticationProvider
46 {
47     private static final Log logger = LogFactory.getLog("org.alfresco.acegi");
48     
49     // Constants
50
//
51
// Standard authorities
52

53     public static final String JavaDoc NTLMAuthorityGuest = "Guest";
54     public static final String JavaDoc NTLMAuthorityAdministrator = "Administrator";
55     
56     // Active session timeout
57

58     private static final long DefaultSessionTimeout = 60000L; // 1 minute
59
private static final long MinimumSessionTimeout = 5000L; // 5 seconds
60

61     // Passthru authentication servers
62

63     private PassthruServers m_passthruServers;
64     
65     // Password encryptor for generating password hash for local authentication
66

67     private PasswordEncryptor m_encryptor;
68     
69     // Allow guest access
70

71     private boolean m_allowGuest;
72     
73     // Table of currently active passthru authentications and the associated authentication session
74
//
75
// If the two authentication stages are not completed within a reasonable time the authentication
76
// session will be closed by the reaper thread.
77

78     private Hashtable JavaDoc<NTLMPassthruToken,AuthenticateSession> m_passthruSessions;
79     
80     // Active authentication session timeout, in milliseconds
81

82     private long m_passthruSessTmo = DefaultSessionTimeout;
83     
84     // Authentication session reaper thread
85

86     private PassthruReaperThread m_reaperThread;
87     
88     /**
89      * Passthru Session Repear Thread
90      */

91     class PassthruReaperThread extends Thread JavaDoc
92     {
93         // Thread shutdown request flag
94

95         private boolean m_ishutdown;
96
97         // Reaper wakeup interval, in milliseconds
98

99         private long m_wakeupInterval = m_passthruSessTmo / 2;
100         
101         /**
102          * Default constructor
103          */

104         PassthruReaperThread()
105         {
106             setDaemon(true);
107             setName("PassthruReaper");
108             start();
109         }
110         
111         /**
112          * Set the wakeup interval
113          *
114          * @param wakeup long
115          */

116         public final void setWakeup(long wakeup)
117         {
118             m_wakeupInterval = wakeup;
119         }
120         
121         /**
122          * Main thread code
123          */

124         public void run()
125         {
126             // Loop until shutdown
127

128             m_ishutdown = false;
129             
130             while ( m_ishutdown == false)
131             {
132                 // Sleep for a while
133

134                 try
135                 {
136                     sleep( m_wakeupInterval);
137                 }
138                 catch ( InterruptedException JavaDoc ex)
139                 {
140                 }
141                 
142                 // Check if there are any active sessions to check
143

144                 if ( m_passthruSessions.size() > 0)
145                 {
146                     // Enumerate the active sessions
147

148                     Enumeration JavaDoc<NTLMPassthruToken> tokenEnum = m_passthruSessions.keys();
149                     long timeNow = System.currentTimeMillis();
150                     
151                     while (tokenEnum.hasMoreElements())
152                     {
153                         // Get the current NTLM token and check if it has expired
154

155                         NTLMPassthruToken ntlmToken = tokenEnum.nextElement();
156                         
157                         if ( ntlmToken != null && ntlmToken.getAuthenticationExpireTime() < timeNow)
158                         {
159                             // Authentication token has expired, close the associated authentication session
160

161                             AuthenticateSession authSess = m_passthruSessions.get(ntlmToken);
162                             if ( authSess != null)
163                             {
164                                 try
165                                 {
166                                     // Close the authentication session
167

168                                     authSess.CloseSession();
169                                 }
170                                 catch ( Exception JavaDoc ex)
171                                 {
172                                     // Debug
173

174                                     if(logger.isDebugEnabled())
175                                         logger.debug("Error closing expired authentication session", ex);
176                                 }
177                             }
178                             
179                             // Remove the expired token from the active list
180

181                             m_passthruSessions.remove(ntlmToken);
182                             
183                             // Debug
184

185                             if(logger.isDebugEnabled())
186                                 logger.debug("Removed expired NTLM token " + ntlmToken);
187                         }
188                     }
189                 }
190             }
191             
192             // Debug
193

194             if(logger.isDebugEnabled())
195                 logger.debug("Passthru reaper thread shutdown");
196         }
197         
198         /**
199          * Shutdown the reaper thread
200          */

201         public final void shutdownRequest()
202         {
203             m_ishutdown = true;
204             this.interrupt();
205         }
206     }
207     
208     /**
209      * Class constructor
210      */

211     public NTLMAuthenticationProvider() {
212         
213         // Create the passthru authentication server list
214

215         m_passthruServers = new PassthruServers();
216         
217         // Create the password encryptor for local password hashing
218

219         m_encryptor = new PasswordEncryptor();
220         
221         // Create the active session list and reaper thread
222

223         m_passthruSessions = new Hashtable JavaDoc<NTLMPassthruToken,AuthenticateSession>();
224         m_reaperThread = new PassthruReaperThread();
225     }
226     
227     /**
228      * Authenticate a user
229      *
230      * @param auth Authentication
231      * @return Authentication
232      * @exception AuthenticationException
233      */

234     public Authentication authenticate(Authentication auth) throws AuthenticationException
235     {
236         // DEBUG
237

238         if ( logger.isDebugEnabled())
239             logger.debug("Authenticate " + auth);
240         
241         // Check if the token is for passthru authentication
242

243         if( auth instanceof NTLMPassthruToken)
244         {
245             // Access the NTLM passthru token
246

247             NTLMPassthruToken ntlmToken = (NTLMPassthruToken) auth;
248             
249             // Authenticate using passthru
250

251             authenticatePassthru(ntlmToken);
252         }
253
254         // Check for a local authentication token
255

256         else if( auth instanceof NTLMLocalToken)
257         {
258             AuthenticateSession authSess = null;
259             
260             try
261             {
262
263                 // Access the NTLM token
264

265                 NTLMLocalToken ntlmToken = (NTLMLocalToken) auth;
266     
267                 // Open a session to an authentication server
268

269                 authSess = m_passthruServers.openSession();
270                 
271                 // Authenticate using the credentials supplied
272

273                 authenticateLocal(ntlmToken, authSess);
274             }
275             finally
276             {
277                 // Make sure the authentication session is closed
278

279                 if ( authSess != null)
280                 {
281                     try
282                     {
283                         authSess.CloseSession();
284                     }
285                     catch ( Exception JavaDoc ex)
286                     {
287                     }
288                 }
289             }
290         }
291
292         // Return the updated authentication token
293

294         return auth;
295     }
296
297     /**
298      * Determine if this provider supports the specified authentication token
299      *
300      * @param authentication Class
301      */

302     public boolean supports(Class JavaDoc authentication)
303     {
304         // Check if the authentication is an NTLM authentication token
305

306         if ( NTLMPassthruToken.class.isAssignableFrom(authentication))
307             return true;
308         return NTLMLocalToken.class.isAssignableFrom(authentication);
309     }
310     
311     /**
312      * Determine if guest logons are allowed
313      *
314      * @return boolean
315      */

316     public final boolean allowsGuest()
317     {
318         return m_allowGuest;
319     }
320     
321     /**
322      * Set the domain to authenticate against
323      *
324      * @param domain String
325      */

326     public final void setDomain(String JavaDoc domain) {
327         
328         // Check if the passthru server list is already configured
329

330         if ( m_passthruServers.getTotalServerCount() > 0)
331             throw new AlfrescoRuntimeException("Passthru server list already configured");
332         
333         // Configure the passthru authentication server list using the domain controllers
334

335         m_passthruServers.setDomain(domain);
336     }
337     
338     /**
339      * Set the server(s) to authenticate against
340      *
341      * @param servers String
342      */

343     public final void setServers(String JavaDoc servers) {
344         
345         // Check if the passthru server list is already configured
346

347         if ( m_passthruServers.getTotalServerCount() > 0)
348             throw new AlfrescoRuntimeException("Passthru server list already configured");
349         
350         // Configure the passthru authenticaiton list using a list of server names/addresses
351

352         m_passthruServers.setServerList(servers);
353     }
354     
355     /**
356      * Use the local server as the authentication server
357      *
358      * @param useLocal String
359      */

360     public final void setUseLocalServer(String JavaDoc useLocal)
361     {
362         // Check if the local server should be used for authentication
363

364         if ( Boolean.parseBoolean(useLocal) == true)
365         {
366             // Check if the passthru server list is already configured
367

368             if ( m_passthruServers.getTotalServerCount() > 0)
369                 throw new AlfrescoRuntimeException("Passthru server list already configured");
370
371             try
372             {
373                 // Get the list of local network addresses
374

375                 InetAddress JavaDoc[] localAddrs = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName());
376                 
377                 // Build the list of local addresses
378

379                 if ( localAddrs != null && localAddrs.length > 0)
380                 {
381                     StringBuilder JavaDoc addrStr = new StringBuilder JavaDoc();
382                 
383                     for ( InetAddress JavaDoc curAddr : localAddrs)
384                     {
385                         if ( curAddr.isLoopbackAddress() == false)
386                         {
387                             addrStr.append(curAddr.getHostAddress());
388                             addrStr.append(",");
389                         }
390                     }
391                     
392                     if ( addrStr.length() > 0)
393                         addrStr.setLength(addrStr.length() - 1);
394                     
395                     // Set the server list using the local address list
396

397                     m_passthruServers.setServerList(addrStr.toString());
398                 }
399                 else
400                     throw new AlfrescoRuntimeException("No local server address(es)");
401             }
402             catch ( UnknownHostException JavaDoc ex)
403             {
404                 throw new AlfrescoRuntimeException("Failed to get local address list");
405             }
406         }
407     }
408     
409     /**
410      * Allow guest access
411      *
412      * @param guest String
413      */

414     public final void setGuestAccess(String JavaDoc guest)
415     {
416         m_allowGuest = Boolean.parseBoolean(guest);
417     }
418     
419     /**
420      * Set the JCE provider
421      *
422      * @param providerClass String
423      */

424     public final void setJCEProvider(String JavaDoc providerClass)
425     {
426         // Set the JCE provider, required to provide various encryption/hashing algorithms not available
427
// in the standard Sun JDK/JRE
428

429         try
430         {
431
432             // Load the JCE provider class and validate
433

434             Object JavaDoc jceObj = Class.forName(providerClass).newInstance();
435             if (jceObj instanceof java.security.Provider JavaDoc)
436             {
437
438                 // Inform listeners, validate the configuration change
439

440                 Provider JavaDoc jceProvider = (Provider JavaDoc) jceObj;
441
442                 // Add the JCE provider
443

444                 Security.addProvider(jceProvider);
445                 
446                 // Debug
447

448                 if ( logger.isDebugEnabled())
449                     logger.debug("Using JCE provider " + providerClass);
450             }
451             else
452             {
453                 throw new AlfrescoRuntimeException("JCE provider class is not a valid Provider class");
454             }
455         }
456         catch (ClassNotFoundException JavaDoc ex)
457         {
458             throw new AlfrescoRuntimeException("JCE provider class " + providerClass + " not found");
459         }
460         catch (Exception JavaDoc ex)
461         {
462             throw new AlfrescoRuntimeException("JCE provider class error", ex);
463         }
464     }
465     
466     /**
467      * Set the authentication session timeout, in seconds
468      *
469      * @param sessTmo String
470      */

471     public final void setSessionTimeout(String JavaDoc sessTmo)
472     {
473         // Convert to an integer value and range check the timeout value
474

475         try
476         {
477             // Convert to an integer value
478

479             long sessTmoMilli = Long.parseLong(sessTmo) * 1000L;
480             
481             if ( sessTmoMilli < MinimumSessionTimeout)
482                 throw new AlfrescoRuntimeException("Authentication session timeout too low, " + sessTmo);
483             
484             // Set the authentication session timeout value
485

486             m_passthruSessTmo = sessTmoMilli;
487             
488             // Set the reaper thread wakeup interval
489

490             m_reaperThread.setWakeup( sessTmoMilli / 2);
491         }
492         catch(NumberFormatException JavaDoc ex)
493         {
494             throw new AlfrescoRuntimeException("Invalid authenication session timeout value");
495         }
496     }
497     
498     /**
499      * Return the authentication session timeout, in milliseconds
500      *
501      * @return long
502      */

503     private final long getSessionTimeout()
504     {
505         return m_passthruSessTmo;
506     }
507     
508     /**
509      * Authenticate a user using local credentials
510      *
511      * @param ntlmToken NTLMLocalToken
512      * @param authSess AuthenticateSession
513      */

514     private void authenticateLocal(NTLMLocalToken ntlmToken, AuthenticateSession authSess)
515     {
516         try
517         {
518             // Get the plaintext password and generate an NTLM1 password hash
519

520             String JavaDoc username = (String JavaDoc) ntlmToken.getPrincipal();
521             String JavaDoc plainPwd = (String JavaDoc) ntlmToken.getCredentials();
522             byte[] ntlm1Pwd = m_encryptor.generateEncryptedPassword( plainPwd, authSess.getEncryptionKey(), PasswordEncryptor.NTLM1);
523             
524             // Send the logon request to the authentication server
525
//
526
// Note: Only use the stronger NTLM hash, we do not send the LM hash
527

528             authSess.doSessionSetup(username, null, ntlm1Pwd);
529             
530             // Check if the session has logged on as a guest
531

532             if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST"))
533             {
534                 // If guest access is enabled add a guest authority to the token
535

536                 if ( allowsGuest())
537                 {
538                     // Set the guest authority
539

540                     GrantedAuthority[] authorities = new GrantedAuthority[1];
541                     authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest);
542                     
543                     ntlmToken.setAuthorities(authorities);
544                 }
545                 else
546                 {
547                     // Guest access not allowed
548

549                     throw new BadCredentialsException("Guest logons disabled");
550                 }
551             }
552             
553             // Indicate that the token is authenticated
554

555             ntlmToken.setAuthenticated(true);
556         }
557         catch (NoSuchAlgorithmException JavaDoc ex)
558         {
559             // JCE provider does not have the required encryption/hashing algorithms
560

561             throw new AuthenticationServiceException("JCE provider error", ex);
562         }
563         catch (IOException JavaDoc ex)
564         {
565             // Error connecting to the authentication server
566

567             throw new AuthenticationServiceException("I/O error", ex);
568         }
569         catch (SMBException ex)
570         {
571             // Check the returned status code to determine why the logon failed and throw an appropriate exception
572

573             if ( ex.getErrorClass() == SMBStatus.NTErr)
574             {
575                 AuthenticationException authEx = null;
576                 
577                 switch( ex.getErrorCode())
578                 {
579                 case SMBStatus.NTLogonFailure:
580                     authEx = new BadCredentialsException("Logon failure");
581                     break;
582                 case SMBStatus.NTAccountDisabled:
583                     authEx = new DisabledException("Account disabled");
584                     break;
585                 default:
586                     authEx = new BadCredentialsException("Logon failure");
587                 break;
588                 }
589                 
590                 throw authEx;
591             }
592             else
593                 throw new BadCredentialsException("Logon failure");
594         }
595     }
596     
597     /**
598      * Authenticate using passthru authentication with a client
599      *
600      * @param ntlmToken NTLMPassthruToken
601      */

602     private void authenticatePassthru(NTLMPassthruToken ntlmToken)
603     {
604         // Check if the token has an authentication session, if not then it is either a new token
605
// or the session has been timed out
606

607         AuthenticateSession authSess = m_passthruSessions.get(ntlmToken);
608         
609         if ( authSess == null)
610         {
611             // Check if the token has a challenge, if it does then the associated session has been
612
// timed out
613

614             if ( ntlmToken.getChallenge() != null)
615                 throw new CredentialsExpiredException("Authentication session expired");
616             
617             // Open an authentication session for the new token and add to the active session list
618

619             authSess = m_passthruServers.openSession();
620             
621             ntlmToken.setAuthenticationExpireTime(System.currentTimeMillis() + getSessionTimeout());
622             
623             // Get the challenge from the initial session negotiate stage
624

625             ntlmToken.setChallenge(new NTLMChallenge(authSess.getEncryptionKey()));
626
627             StringBuilder JavaDoc details = new StringBuilder JavaDoc();
628
629             // Build a details string with the authentication session details
630

631             details.append(authSess.getDomain());
632             details.append("\\");
633             details.append(authSess.getPCShare().getNodeName());
634             details.append(",");
635             details.append(authSess.getSession().getProtocolName());
636             
637             ntlmToken.setDetails(details.toString());
638
639             // Put the token/session into the active session list
640

641             m_passthruSessions.put(ntlmToken, authSess);
642             
643             // Debug
644

645             if ( logger.isDebugEnabled())
646                 logger.debug("Passthru stage 1 token " + ntlmToken);
647         }
648         else
649         {
650             try
651             {
652                 // Stage two of the authentication, send the hashed password to the authentication server
653

654                 byte[] lmPwd = null;
655                 byte[] ntlmPwd = null;
656                 
657                 if ( ntlmToken.getPasswordType() == PasswordEncryptor.LANMAN)
658                     lmPwd = ntlmToken.getHashedPassword();
659                 else if ( ntlmToken.getPasswordType() == PasswordEncryptor.NTLM1)
660                     ntlmPwd = ntlmToken.getHashedPassword();
661                 
662                 String JavaDoc username = (String JavaDoc) ntlmToken.getPrincipal();
663                 
664                 authSess.doSessionSetup(username, lmPwd, ntlmPwd);
665                 
666                 // Check if the session has logged on as a guest
667

668                 if ( authSess.isGuest() || username.equalsIgnoreCase("GUEST"))
669                 {
670                     // If guest access is enabled add a guest authority to the token
671

672                     if ( allowsGuest())
673                     {
674                         // Set the guest authority
675

676                         GrantedAuthority[] authorities = new GrantedAuthority[1];
677                         authorities[0] = new GrantedAuthorityImpl(NTLMAuthorityGuest);
678                         
679                         ntlmToken.setAuthorities(authorities);
680                     }
681                     else
682                     {
683                         // Guest access not allowed
684

685                         throw new BadCredentialsException("Guest logons disabled");
686                     }
687                 }
688                 
689                 // Indicate that the token is authenticated
690

691                 ntlmToken.setAuthenticated(true);
692             }
693             catch (IOException JavaDoc ex)
694             {
695                 // Error connecting to the authentication server
696

697                 throw new AuthenticationServiceException("I/O error", ex);
698             }
699             catch (SMBException ex)
700             {
701                 // Check the returned status code to determine why the logon failed and throw an appropriate exception
702

703                 if ( ex.getErrorClass() == SMBStatus.NTErr)
704                 {
705                     AuthenticationException authEx = null;
706                     
707                     switch( ex.getErrorCode())
708                     {
709                     case SMBStatus.NTLogonFailure:
710                         authEx = new BadCredentialsException("Logon failure");
711                         break;
712                     case SMBStatus.NTAccountDisabled:
713                         authEx = new DisabledException("Account disabled");
714                         break;
715                     default:
716                         authEx = new BadCredentialsException("Logon failure");
717                     break;
718                     }
719                     
720                     throw authEx;
721                 }
722                 else
723                     throw new BadCredentialsException("Logon failure");
724             }
725             finally
726             {
727                 // Make sure the authentication session is closed
728

729                 if ( authSess != null)
730                 {
731                     try
732                     {
733                         // Remove the session from the active list
734

735                         m_passthruSessions.remove(ntlmToken);
736                         
737                         // Close the session to the authentication server
738

739                         authSess.CloseSession();
740                     }
741                     catch (Exception JavaDoc ex)
742                     {
743                     }
744                 }
745             }
746         }
747     }
748 }
749
Popular Tags