/****************************************************************************** * JBoss, a division of Red Hat * * Copyright 2010, Red Hat Middleware, LLC, and individual * * contributors as indicated by the @authors tag. See the * * copyright.txt in the distribution for a full listing of * * individual contributors. * * * * This is free software; you can redistribute it and/or modify it * * under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation; either version 2.1 of * * the License, or (at your option) any later version. * * * * This software is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this software; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * ******************************************************************************/ package org.exoplatform.web.security.errorlogin; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.exoplatform.container.xml.InitParams; import org.exoplatform.services.mail.MailService; import org.gatein.common.logging.Logger; import org.gatein.common.logging.LoggerFactory; /** * Service can be used to track invalid login attempts of users and do some actions when some number of successive login * attempts is detected. * * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @version $Revision$ */ public class InvalidLoginAttemptsService { private static final Logger log = LoggerFactory.getLogger(InvalidLoginAttemptsService.class); /** * If false, then feature of sending mails to administrator about bad login attempts is disabled. */ private final Boolean sendMailEnabled; /** * Number of successive invalid login attempts of user, after the mail will be send. */ private final Integer numberOfFailedAttempts; /** * Policy for indication that invalid login attempts are coming from same source. Valid values are SESSION, * SESSION_AND_USER, SERVER. SESSION is default and it means that login attempts are coming from same HTTP session. * SESSION_AND_USER is indicating login attempts of same user and from same HTTP session. SERVER means login attempts from * same remote server. */ private InvalidLoginPolicy invalidLoginPolicy; /** * This will be used as 'from' header in admin mail */ private final String mailFrom; /** * This should be admin e-mail address, where e-mail about invalid login attempts will be send. */ private final String mailTo; /** * Subject of email about invalid login attempts. */ private final String mailSubject; /** * Content of mail message, which will be send to administrator. Real content will be based on value of parameter * "invalidLoginPolicy". Tokens like ${username}, ${sessionId}, ${hostname} will be replaced with real values from attacker. */ private final String mailMessage; /** * MailService injected by exo kernel. */ private final MailService mailService; /** * Helper map to track login attempts from different users. */ private final ConcurrentMap<InvalidAttemptKey, Integer> attemptMap = new ConcurrentHashMap<InvalidAttemptKey, Integer>(); public InvalidLoginAttemptsService(InitParams params, MailService mailService) { this.mailService = mailService; this.sendMailEnabled = Boolean.valueOf(params.getValueParam("sendingMailsEnabled").getValue()); this.numberOfFailedAttempts = Integer.parseInt(params.getValueParam("numberOfFailedAttempts").getValue()); this.invalidLoginPolicy = InvalidLoginPolicy.valueOf(params.getValueParam("invalidLoginPolicy").getValue()); this.mailFrom = params.getValueParam("mailFrom").getValue(); this.mailTo = params.getValueParam("mailTo").getValue(); this.mailSubject = params.getValueParam("mailSubject").getValue(); this.mailMessage = params.getPropertiesParam("mailMessage").getProperty(invalidLoginPolicy.toString()); } /** * This should be called each time when invalid login attempt is detected (typically from HttpFilter) * * @param sessionId * @param username * @param hostname */ public void badLoginAttempt(String sessionId, String username, String hostname) { if (log.isDebugEnabled()) { log.debug(new StringBuilder("Detected invalid login attempt. Session id=").append(sessionId).append(", username=") .append(username).append(", hostname=").append(hostname)); } if (numberOfFailedAttempts == 1) { sendMail(sessionId, username, hostname); return; } InvalidAttemptKey key = InvalidAttemptKey.createKey(invalidLoginPolicy, sessionId, username, hostname); // TODO: better synchronization int currentCount = 1; if (attemptMap.containsKey(key)) { currentCount = attemptMap.get(key) + 1; if (currentCount == numberOfFailedAttempts) { sendMail(sessionId, username, hostname); attemptMap.remove(key); } else { attemptMap.put(key, currentCount); } } else { attemptMap.put(key, currentCount); } } /** * This should be called each time successful login attempt is detected. We need to use it to clean previous bad attempts * when we have good attempt. * * @param sessionId * @param username * @param hostname */ public void successfulLoginAttempt(String sessionId, String username, String hostname) { if (log.isDebugEnabled()) { log.debug(new StringBuilder("Detected successful login attempt. Session id=").append(sessionId) .append(", username=").append(username).append(", hostname=").append(hostname)); } InvalidAttemptKey key = InvalidAttemptKey.createKey(invalidLoginPolicy, sessionId, username, hostname); attemptMap.remove(key); } /** * Allows to set custom invalidLoginPolicy * * @param invalidLoginPolicy */ public void setInvalidLoginPolicy(InvalidLoginPolicy invalidLoginPolicy) { this.invalidLoginPolicy = invalidLoginPolicy; } private void sendMail(String sessionId, String username, String hostname) { // return if sending mails disabled in configuration. if (!sendMailEnabled) { if (log.isDebugEnabled()) { log.debug("Sending of mails disabled. Mail won't be send about invalid login attempts."); } return; } // replace tokens from configuration with real values. String result = mailMessage.replaceAll("\\$\\{sessionId\\}", sessionId); result = result.replaceAll("\\$\\{username\\}", username); result = result.replaceAll("\\$\\{hostname\\}", hostname); result = result.replaceAll("\\$\\{number\\}", String.valueOf(numberOfFailedAttempts)); try { if (log.isDebugEnabled()) { log.debug("Sending mail about the invalid login attempts. Mail message is: " + result); } mailService.sendMessage(mailFrom, mailTo, mailSubject, result); } catch (Exception e) { // log exception but not throw it when sending of mail happen log.error("Error when sending mail to admin after detected invalid number of login attempts.", e); } } }