/** * Process a type 3 NTLM message * * @param type3Msg Type3NTLMMessage * @param req HttpServletRequest * @param res HttpServletResponse * @exception IOException * @exception ServletException */ protected boolean processType3( Type3NTLMMessage type3Msg, ServletContext context, HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { Log logger = getLogger(); if (logger.isDebugEnabled()) logger.debug("Received type3 " + type3Msg); // Get the existing NTLM details NTLMLogonDetails ntlmDetails = null; SessionUser user = null; user = getSessionUser(context, req, res, true); HttpSession session = req.getSession(); ntlmDetails = (NTLMLogonDetails) session.getAttribute(NTLM_AUTH_DETAILS); // Get the NTLM logon details String userName = type3Msg.getUserName(); String workstation = type3Msg.getWorkstation(); String domain = type3Msg.getDomain(); // ALF-10997 fix, normalize the userName // the system runAs is acceptable because we are resolving a username i.e. it's a system-wide // operation that does not need permission checks final String userName_f = userName; String normalized = transactionService .getRetryingTransactionHelper() .doInTransaction( new RetryingTransactionHelper.RetryingTransactionCallback<String>() { public String execute() throws Throwable { return AuthenticationUtil.runAs( new AuthenticationUtil.RunAsWork<String>() { public String doWork() throws Exception { String normalized = personService.getUserIdentifier(userName_f); return normalized; } }, AuthenticationUtil.SYSTEM_USER_NAME); } }, true); if (normalized != null) { userName = normalized; } boolean authenticated = false; // Check if we are using cached details for the authentication if (user != null && ntlmDetails != null && ntlmDetails.hasNTLMHashedPassword()) { // Check if the received NTLM hashed password matches the cached password byte[] ntlmPwd = type3Msg.getNTLMHash(); byte[] cachedPwd = ntlmDetails.getNTLMHashedPassword(); if (ntlmPwd != null) { authenticated = Arrays.equals(cachedPwd, ntlmPwd); } if (logger.isDebugEnabled()) logger.debug("Using cached NTLM hash, authenticated = " + authenticated); onValidate(context, req, res, new NTLMCredentials(userName, ntlmPwd)); // Allow the user to access the requested page return true; } else { WebCredentials credentials; // Check if we are using local MD4 password hashes or passthru authentication if (nltmAuthenticator.getNTLMMode() == NTLMMode.MD4_PROVIDER) { // Check if guest logons are allowed and this is a guest logon if (m_allowGuest && userName.equalsIgnoreCase(authenticationComponent.getGuestUserName())) { credentials = new GuestCredentials(); // Indicate that the user has been authenticated authenticated = true; if (getLogger().isDebugEnabled()) getLogger().debug("Guest logon"); } else { // Get the stored MD4 hashed password for the user, or null if the user does not exist String md4hash = getMD4Hash(userName); if (md4hash != null) { authenticated = validateLocalHashedPassword(type3Msg, ntlmDetails, authenticated, md4hash); credentials = new NTLMCredentials(ntlmDetails.getUserName(), ntlmDetails.getNTLMHashedPassword()); } else { // Check if unknown users should be logged on as guest if (m_mapUnknownUserToGuest) { // Reset the user name to be the guest user userName = authenticationComponent.getGuestUserName(); authenticated = true; credentials = new GuestCredentials(); if (logger.isDebugEnabled()) logger.debug("User " + userName + " logged on as guest, no Alfresco account"); } else { if (logger.isDebugEnabled()) logger.debug("User " + userName + " does not have Alfresco account"); // Bypass NTLM authentication and display the logon screen, // as user account does not exist in Alfresco credentials = new UnknownCredentials(); authenticated = false; } } } } else { credentials = new NTLMCredentials(type3Msg.getUserName(), type3Msg.getNTLMHash()); // Determine if the client sent us NTLMv1 or NTLMv2 if (type3Msg.hasFlag(NTLM.Flag128Bit) && type3Msg.hasFlag(NTLM.FlagNTLM2Key) || (type3Msg.getNTLMHash() != null && type3Msg.getNTLMHash().length > 24)) { // Cannot accept NTLMv2 if we are using passthru auth if (logger.isErrorEnabled()) logger.error( "Client " + workstation + " using NTLMv2 logon, not valid with passthru authentication"); } else { if (ntlmDetails == null) { if (logger.isWarnEnabled()) logger.warn( "Authentication failed: NTLM details can not be retrieved from session. Client must support cookies."); restartLoginChallenge(context, req, res); return false; } // Passthru mode, send the hashed password details to the passthru authentication server NTLMPassthruToken authToken = (NTLMPassthruToken) ntlmDetails.getAuthenticationToken(); authToken.setUserAndPassword( type3Msg.getUserName(), type3Msg.getNTLMHash(), PasswordEncryptor.NTLM1); try { // Run the second stage of the passthru authentication nltmAuthenticator.authenticate(authToken); authenticated = true; // Check if the user has been logged on as guest if (authToken.isGuestLogon()) { userName = authenticationComponent.getGuestUserName(); } // Set the authentication context authenticationComponent.setCurrentUser(userName); } catch (BadCredentialsException ex) { if (logger.isDebugEnabled()) logger.debug("Authentication failed, " + ex.getMessage()); } catch (AuthenticationException ex) { if (logger.isDebugEnabled()) logger.debug("Authentication failed, " + ex.getMessage()); } finally { // Clear the authentication token from the NTLM details ntlmDetails.setAuthenticationToken(null); } } } // Check if the user has been authenticated, if so then setup the user environment if (authenticated == true) { boolean userInit = false; if (user == null) { try { user = createUserEnvironment(session, userName); userInit = true; } catch (AuthenticationException ex) { if (logger.isDebugEnabled()) logger.debug("Failed to validate user " + userName, ex); onValidateFailed(context, req, res, session, credentials); return false; } } onValidate(context, req, res, credentials); // Update the NTLM logon details in the session String srvName = getServerName(); if (ntlmDetails == null) { // No cached NTLM details ntlmDetails = new NTLMLogonDetails(userName, workstation, domain, false, srvName); ntlmDetails.setNTLMHashedPassword(type3Msg.getNTLMHash()); session.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails); if (logger.isDebugEnabled()) logger.debug("No cached NTLM details, created"); } else { // Update the cached NTLM details ntlmDetails.setDetails(userName, workstation, domain, false, srvName); ntlmDetails.setNTLMHashedPassword(type3Msg.getNTLMHash()); if (logger.isDebugEnabled()) logger.debug("Updated cached NTLM details"); } if (logger.isDebugEnabled()) logger.debug("User logged on via NTLM, " + ntlmDetails); if (onLoginComplete(context, req, res, userInit)) { // Allow the user to access the requested page return true; } } else { restartLoginChallenge(context, req, res); } } return false; }
/** * Validate the MD4 hash against local password * * @param type3Msg Type3NTLMMessage * @param ntlmDetails NTLMLogonDetails * @param authenticated boolean * @param md4hash String * @return true if password hash is valid, false otherwise */ protected boolean validateLocalHashedPassword( Type3NTLMMessage type3Msg, NTLMLogonDetails ntlmDetails, boolean authenticated, String md4hash) { // Make sure we have hte cached NTLM details, including the type2 message with the server // challenge if (ntlmDetails == null || ntlmDetails.getType2Message() == null) { // DEBUG if (getLogger().isDebugEnabled()) getLogger().debug("No cached Type2, ntlmDetails=" + ntlmDetails); // Not authenticated return false; } // Determine if the client sent us NTLMv1 or NTLMv2 if (type3Msg.hasFlag(NTLM.FlagNTLM2Key)) { // Determine if the client sent us an NTLMv2 blob or an NTLMv2 session key if (type3Msg.getNTLMHashLength() > 24) { // Looks like an NTLMv2 blob authenticated = checkNTLMv2(md4hash, ntlmDetails.getChallengeKey(), type3Msg); if (getLogger().isDebugEnabled()) getLogger() .debug((authenticated ? "Logged on" : "Logon failed") + " using NTLMSSP/NTLMv2"); // If the NTlmv2 autentication failed then check if the client has sent an NTLMv1 hash if (authenticated == false && type3Msg.hasFlag(NTLM.Flag56Bit) && type3Msg.getLMHashLength() == 24) { // Check the LM hash field authenticated = checkNTLMv1(md4hash, ntlmDetails.getChallengeKey(), type3Msg, true); // DEBUG if (getLogger().isDebugEnabled()) getLogger() .debug( (authenticated ? "Logged on" : "Logon failed") + " using NTLMSSP/NTLMv1 (via fallback)"); } } else { // Looks like an NTLMv2 session key authenticated = checkNTLMv2SessionKey(md4hash, ntlmDetails.getChallengeKey(), type3Msg); if (getLogger().isDebugEnabled()) getLogger() .debug( (authenticated ? "Logged on" : "Logon failed") + " using NTLMSSP/NTLMv2SessKey"); } } else { // Looks like an NTLMv1 blob authenticated = checkNTLMv1(md4hash, ntlmDetails.getChallengeKey(), type3Msg, false); if (getLogger().isDebugEnabled()) getLogger().debug((authenticated ? "Logged on" : "Logon failed") + " using NTLMSSP/NTLMv1"); } return authenticated; }
/** * Process a type 1 NTLM message * * @param type1Msg Type1NTLMMessage * @param req HttpServletRequest * @param res HttpServletResponse * @exception IOException */ protected void processType1( Type1NTLMMessage type1Msg, HttpServletRequest req, HttpServletResponse res) throws IOException { if (getLogger().isDebugEnabled()) getLogger().debug("Received type1 " + type1Msg); // Get the existing NTLM details NTLMLogonDetails ntlmDetails = null; HttpSession session = req.getSession(); ntlmDetails = (NTLMLogonDetails) session.getAttribute(NTLM_AUTH_DETAILS); // Check if cached logon details are available if (ntlmDetails != null && ntlmDetails.hasType2Message() && ntlmDetails.hasNTLMHashedPassword() && ntlmDetails.hasAuthenticationToken()) { // Get the authentication server type2 response Type2NTLMMessage cachedType2 = ntlmDetails.getType2Message(); byte[] type2Bytes = cachedType2.getBytes(); String ntlmBlob = "NTLM " + new String(Base64.encodeBase64(type2Bytes)); if (getLogger().isDebugEnabled()) getLogger().debug("Sending cached NTLM type2 to client - " + cachedType2); // Send back a request for NTLM authentication res.setHeader(WWW_AUTHENTICATE, ntlmBlob); res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); res.flushBuffer(); } else { // Clear any cached logon details session.removeAttribute(NTLM_AUTH_DETAILS); // Set the 8 byte challenge for the new logon request byte[] challenge = null; NTLMPassthruToken authToken = null; if (nltmAuthenticator.getNTLMMode() == NTLMMode.MD4_PROVIDER) { // Generate a random 8 byte challenge challenge = new byte[8]; DataPacker.putIntelLong(m_random.nextLong(), challenge, 0); } else { // Get the client domain String domain = type1Msg.getDomain(); if (domain == null || domain.length() == 0) { domain = mapClientAddressToDomain(req.getRemoteAddr()); } if (getLogger().isDebugEnabled()) getLogger().debug("Client domain " + domain); // Create an authentication token for the new logon authToken = new NTLMPassthruToken(domain); // Run the first stage of the passthru authentication to get the challenge nltmAuthenticator.authenticate(authToken); // Get the challenge from the token if (authToken.getChallenge() != null) { challenge = authToken.getChallenge().getBytes(); } } // Get the flags from the client request and mask out unsupported features int ntlmFlags = type1Msg.getFlags() & m_ntlmFlags; // Build a type2 message to send back to the client, containing the challenge List<TargetInfo> tList = new ArrayList<TargetInfo>(); String srvName = getServerName(); tList.add(new TargetInfo(NTLM.TargetServer, srvName)); Type2NTLMMessage type2Msg = new Type2NTLMMessage(); type2Msg.buildType2(ntlmFlags, srvName, challenge, null, tList); // Store the NTLM logon details, cache the type2 message, and token if using passthru ntlmDetails = new NTLMLogonDetails(); ntlmDetails.setType2Message(type2Msg); ntlmDetails.setAuthenticationToken(authToken); session.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails); if (getLogger().isDebugEnabled()) getLogger().debug("Sending NTLM type2 to client - " + type2Msg); // Send back a request for NTLM authentication byte[] type2Bytes = type2Msg.getBytes(); String ntlmBlob = "NTLM " + new String(Base64.encodeBase64(type2Bytes)); res.setHeader(WWW_AUTHENTICATE, ntlmBlob); res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); res.flushBuffer(); } }