/**
   * 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;
  }