-/*\r
- * Copyright (C) 2001, 2002 The Mir-coders group\r
- *\r
- * This file is part of Mir.\r
- *\r
- * Mir is free software; you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation; either version 2 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * Mir is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with Mir; if not, write to the Free Software\r
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
- *\r
- * In addition, as a special exception, The Mir-coders gives permission to link\r
- * the code of this program with any library licensed under the Apache Software License,\r
- * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library\r
- * (or with modified versions of the above that use the same license as the above),\r
- * and distribute linked combinations including the two. You must obey the\r
- * GNU General Public License in all respects for all of the code used other than\r
- * the above mentioned libraries. If you modify this file, you may extend this\r
- * exception to your version of the file, but you are not obligated to do so.\r
- * If you do not wish to do so, delete this exception statement from your version.\r
- */\r
-\r
-package mircoders.global;\r
-\r
-import java.io.File;\r
-import java.io.FileNotFoundException;\r
-import java.io.FileOutputStream;\r
-import java.util.Arrays;\r
-import java.util.Date;\r
-import java.util.GregorianCalendar;\r
-import java.util.HashMap;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Random;\r
-import java.util.Vector;\r
-import javax.servlet.http.Cookie;\r
-import javax.servlet.http.HttpServletResponse;\r
-\r
-import org.apache.commons.collections.ExtendedProperties;\r
-import mir.config.MirPropertiesConfiguration;\r
-import mir.entity.Entity;\r
-import mir.log.LoggerWrapper;\r
-import mir.session.Request;\r
-import mir.util.GeneratorFormatAdapters;\r
-import mir.util.StringRoutines;\r
-import mircoders.entity.EntityComment;\r
-import mircoders.entity.EntityContent;\r
-import mircoders.entity.EntityUsers;\r
-import mircoders.localizer.MirAdminInterfaceLocalizer;\r
-import mircoders.localizer.MirAntiAbuseFilterType;\r
-\r
-\r
-public class Abuse {\r
- private List filterRules;\r
- private Map filterTypes;\r
- private List filterTypeIds;\r
- private int maxIdentifier;\r
- private LoggerWrapper logger;\r
- private int logSize;\r
- private boolean logEnabled;\r
- private boolean openPostingDisabled;\r
- private boolean openPostingPassword;\r
- private boolean cookieOnBlock;\r
- private String articleBlockAction;\r
- private String commentBlockAction;\r
- private List log;\r
- private String configFile = MirGlobal.config().getStringWithHome("Abuse.Config");\r
-\r
- private MirPropertiesConfiguration configuration;\r
-\r
- private static String cookieName = MirGlobal.config().getString("Abuse.CookieName");\r
- private static int cookieMaxAge = 60 * 60 * MirGlobal.config().getInt("Abuse.CookieMaxAge");\r
-\r
- public Abuse() {\r
- logger = new LoggerWrapper("Global.Abuse");\r
- filterRules = new Vector();\r
- maxIdentifier = 0;\r
- log = new Vector();\r
-\r
- try {\r
- configuration = MirPropertiesConfiguration.instance();\r
- }\r
- catch (Throwable e) {\r
- throw new RuntimeException("Can't get configuration: " + e.getMessage());\r
- }\r
-\r
- logSize = 100;\r
- logEnabled = false;\r
- articleBlockAction = "";\r
- commentBlockAction = "";\r
- openPostingPassword = false;\r
- openPostingDisabled = false;\r
- cookieOnBlock = false;\r
-\r
- try {\r
- filterTypes = new HashMap();\r
- filterTypeIds = new Vector();\r
-\r
- Iterator i = MirGlobal.localizer().openPostings().getAntiAbuseFilterTypes().iterator();\r
-\r
- while (i.hasNext()) {\r
- MirAntiAbuseFilterType filterType = (MirAntiAbuseFilterType) i.next();\r
- filterTypes.put(filterType.getName(), filterType);\r
- filterTypeIds.add(filterType.getName());\r
- }\r
- }\r
- catch (Throwable t) {\r
- throw new RuntimeException("Can't get filter types: " + t.getMessage());\r
- }\r
-\r
- load();\r
- }\r
-\r
- private void setCookie(HttpServletResponse aResponse) {\r
- Random random = new Random();\r
-\r
- Cookie cookie = new Cookie(cookieName, Integer.toString(random.nextInt(1000000000)));\r
- cookie.setMaxAge(cookieMaxAge);\r
- cookie.setPath("/");\r
-\r
- if (aResponse != null)\r
- aResponse.addCookie(cookie);\r
- }\r
-\r
- private boolean checkCookie(List aCookies) {\r
- if (getCookieOnBlock()) {\r
- Iterator i = aCookies.iterator();\r
-\r
- while (i.hasNext()) {\r
- Cookie cookie = (Cookie) i.next();\r
-\r
- if (cookie.getName().equals(cookieName)) {\r
- logger.debug("cookie match");\r
- return true;\r
- }\r
- }\r
- }\r
-\r
- return false;\r
- }\r
-\r
- FilterRule findMatchingFilter(Entity anEntity, Request aRequest) {\r
- Iterator iterator = filterRules.iterator();\r
-\r
- while (iterator.hasNext()) {\r
- FilterRule rule = (FilterRule) iterator.next();\r
-\r
- if (rule.test(anEntity, aRequest))\r
- return rule;\r
- }\r
-\r
- return null;\r
- }\r
-\r
- public void checkComment(EntityComment aComment, Request aRequest, HttpServletResponse aResponse) {\r
- logComment(aComment, aRequest);\r
-\r
- try {\r
- long time = System.currentTimeMillis();\r
-\r
- FilterRule filterRule = findMatchingFilter(aComment, aRequest);\r
-\r
- if (filterRule != null) {\r
- logger.debug("Match for " + filterRule.getType() + " rule '" + filterRule.getExpression() + "'");\r
- filterRule.setLastHit(new GregorianCalendar().getTime());\r
- MirGlobal.performCommentOperation(null, aComment, filterRule.getCommentAction());\r
- setCookie(aResponse);\r
- save();\r
- }\r
-\r
- logger.info("checkComment: " + (System.currentTimeMillis() - time) + "ms");\r
- }\r
- catch (Throwable t) {\r
- t.printStackTrace(logger.asPrintWriter(logger.DEBUG_MESSAGE));\r
- logger.error("Abuse.checkComment: " + t.toString());\r
- }\r
- }\r
-\r
- public void checkArticle(EntityContent anArticle, Request aRequest, HttpServletResponse aResponse) {\r
- logArticle(anArticle, aRequest);\r
-\r
- try {\r
- long time = System.currentTimeMillis();\r
-\r
- FilterRule filterRule = findMatchingFilter(anArticle, aRequest);\r
-\r
- if (filterRule != null) {\r
- logger.debug("Match for " + filterRule.getType() + " rule '" + filterRule.getExpression() + "'");\r
- filterRule.setLastHit(new GregorianCalendar().getTime());\r
- MirGlobal.performArticleOperation(null, anArticle, filterRule.getArticleAction());\r
- setCookie(aResponse);\r
- save();\r
- }\r
-\r
- logger.info("checkArticle: " + (System.currentTimeMillis() - time) + "ms");\r
- }\r
- catch (Throwable t) {\r
- t.printStackTrace(logger.asPrintWriter(logger.DEBUG_MESSAGE));\r
- logger.error("Abuse.checkArticle: " + t.toString());\r
- }\r
- }\r
-\r
- public boolean getLogEnabled() {\r
- return logEnabled;\r
- }\r
-\r
- public void setLogEnabled(boolean anEnabled) {\r
- if (!configuration.getString("Abuse.DisallowIPLogging", "0").equals("1"))\r
- logEnabled = anEnabled;\r
- truncateLog();\r
- }\r
-\r
- public int getLogSize() {\r
- return logSize;\r
- }\r
-\r
- public void setLogSize(int aSize) {\r
- logSize = aSize;\r
- truncateLog();\r
- }\r
-\r
- public boolean getOpenPostingDisabled() {\r
- return openPostingDisabled;\r
- }\r
-\r
- public void setOpenPostingDisabled(boolean anOpenPostingDisabled) {\r
- openPostingDisabled = anOpenPostingDisabled;\r
- }\r
-\r
- public boolean getOpenPostingPassword() {\r
- return openPostingPassword;\r
- }\r
-\r
- public void setOpenPostingPassword(boolean anOpenPostingPassword) {\r
- openPostingPassword = anOpenPostingPassword;\r
- }\r
-\r
- public boolean getCookieOnBlock() {\r
- return cookieOnBlock;\r
- }\r
-\r
- public void setCookieOnBlock(boolean aCookieOnBlock) {\r
- cookieOnBlock = aCookieOnBlock;\r
- }\r
-\r
- public String getArticleBlockAction() {\r
- return articleBlockAction;\r
- }\r
-\r
- public void setArticleBlockAction(String anAction) {\r
- articleBlockAction = anAction;\r
- }\r
-\r
- public String getCommentBlockAction() {\r
- return commentBlockAction;\r
- }\r
-\r
- public void setCommentBlockAction(String anAction) {\r
- commentBlockAction = anAction;\r
- }\r
-\r
- public List getLog() {\r
- synchronized (log) {\r
- try {\r
- List result = new Vector();\r
-\r
- Iterator i = log.iterator();\r
- while (i.hasNext()) {\r
- LogEntry logEntry = (LogEntry) i.next();\r
- Map entry = new HashMap();\r
-\r
- entry.put("ip", logEntry.getIpNumber());\r
- entry.put("id", logEntry.getId());\r
- entry.put("timestamp", new GeneratorFormatAdapters.DateFormatAdapter(logEntry.getTimeStamp(), MirPropertiesConfiguration.instance().getString("Mir.DefaultTimezone")));\r
- if (logEntry.getIsArticle())\r
- entry.put("type", "content");\r
- else\r
- entry.put("type", "comment");\r
- entry.put("browser", logEntry.getBrowserString());\r
-\r
- result.add(entry);\r
- }\r
-\r
- return result;\r
- }\r
- catch (Throwable t) {\r
- throw new RuntimeException(t.toString());\r
- }\r
- }\r
- }\r
-\r
- public void logComment(Entity aComment, Request aRequest) {\r
- String ipAddress = aRequest.getHeader("ip");\r
- String id = aComment.getId();\r
- String browser = aRequest.getHeader("User-Agent");\r
-\r
- logComment(ipAddress, id, new Date(), browser);\r
- }\r
-\r
- public void logArticle(Entity anArticle, Request aRequest) {\r
- String ipAddress = aRequest.getHeader("ip");\r
- String id = anArticle.getId();\r
- String browser = aRequest.getHeader("User-Agent");\r
-\r
- logArticle(ipAddress, id, new Date(), browser);\r
- }\r
-\r
- public void logComment(String anIp, String anId, Date aTimeStamp, String aBrowser) {\r
- appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, false));\r
- }\r
-\r
- public void logArticle(String anIp, String anId, Date aTimeStamp, String aBrowser) {\r
- appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, true));\r
- }\r
-\r
- public void load() {\r
- synchronized (filterRules) {\r
- try {\r
- ExtendedProperties configuration = new ExtendedProperties();\r
-\r
- try {\r
- configuration = new ExtendedProperties(configFile);\r
- }\r
- catch (FileNotFoundException e) {\r
- }\r
-\r
- getFilterConfig(filterRules, "abuse.filter", configuration);\r
-\r
- setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));\r
- setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));\r
- setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));\r
- setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));\r
- setLogSize(configuration.getInt("abuse.logSize", 10));\r
- setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));\r
- setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));\r
- }\r
- catch (Throwable t) {\r
- throw new RuntimeException(t.toString());\r
- }\r
- }\r
- }\r
-\r
- public void save() {\r
- synchronized (filterRules) {\r
- try {\r
- ExtendedProperties configuration = new ExtendedProperties();\r
-\r
- setFilterConfig(filterRules, "abuse.filter", configuration);\r
-\r
- configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled() ? "1" : "0");\r
- configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword() ? "1" : "0");\r
- configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock() ? "1" : "0");\r
- configuration.addProperty("abuse.logEnabled", getLogEnabled() ? "1" : "0");\r
- configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));\r
- configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());\r
- configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());\r
-\r
- configuration.save(new FileOutputStream(new File(configFile)), "Anti abuse configuration");\r
- }\r
- catch (Throwable t) {\r
- throw new RuntimeException(t.toString());\r
- }\r
- }\r
- }\r
-\r
- public List getFilterTypes() {\r
- try {\r
- List result = new Vector();\r
-\r
- Iterator i = filterTypeIds.iterator();\r
- while (i.hasNext()) {\r
- String id = (String) i.next();\r
-\r
- Map action = new HashMap();\r
- action.put("resource", id);\r
- action.put("identifier", id);\r
-\r
- result.add(action);\r
- }\r
-\r
- return result;\r
- }\r
- catch (Throwable t) {\r
- throw new RuntimeException("can't get article actions");\r
- }\r
- }\r
-\r
- public List getArticleActions() {\r
- try {\r
- List result = new Vector();\r
-\r
- Iterator i = MirGlobal.localizer().adminInterface().simpleArticleOperations().iterator();\r
- while (i.hasNext()) {\r
- MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =\r
- (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();\r
-\r
- Map action = new HashMap();\r
- action.put("resource", operation.getName());\r
- action.put("identifier", operation.getName());\r
-\r
- result.add(action);\r
- }\r
-\r
- return result;\r
- }\r
- catch (Throwable t) {\r
- throw new RuntimeException("can't get article actions");\r
- }\r
- }\r
-\r
- public List getCommentActions() {\r
- try {\r
- List result = new Vector();\r
-\r
- Iterator i = MirGlobal.localizer().adminInterface().simpleCommentOperations().iterator();\r
- while (i.hasNext()) {\r
- MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =\r
- (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();\r
-\r
- Map action = new HashMap();\r
- action.put("resource", operation.getName());\r
- action.put("identifier", operation.getName());\r
-\r
- result.add(action);\r
- }\r
-\r
- return result;\r
- }\r
- catch (Throwable t) {\r
- throw new RuntimeException("can't get comment actions");\r
- }\r
- }\r
-\r
- public List getFilters() {\r
- List result = new Vector();\r
-\r
- synchronized (filterRules) {\r
- Iterator i = filterRules.iterator();\r
- while (i.hasNext()) {\r
- FilterRule filter = (FilterRule) i.next();\r
- result.add(filter.clone());\r
- }\r
- return result;\r
- }\r
- }\r
-\r
- public String addFilter(String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {\r
- return addFilter(aType, anExpression, aComments, aCommentAction, anArticleAction, null);\r
- }\r
-\r
- public String addFilter(String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction, Date aListHit) {\r
- return addFilter(filterRules, aType, anExpression, aComments, aCommentAction, anArticleAction, aListHit);\r
- }\r
-\r
- public FilterRule getFilter(String anId) {\r
- synchronized (filterRules) {\r
- FilterRule result = (FilterRule) findFilter(filterRules, anId);\r
- if (result == null)\r
- return result;\r
- else\r
- return (FilterRule) result.clone();\r
- }\r
- }\r
-\r
- public String setFilter(String anIdentifier, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {\r
- return setFilter(filterRules, anIdentifier, aType, anExpression, aComments, aCommentAction, anArticleAction);\r
- }\r
-\r
- public void deleteFilter(String anIdentifier) {\r
- deleteFilter(filterRules, anIdentifier);\r
- }\r
-\r
- public void moveFilterUp(String anIdentifier) {\r
- moveFilter(filterRules, anIdentifier, -1);\r
- }\r
-\r
- public void moveFilterDown(String anIdentifier) {\r
- moveFilter(filterRules, anIdentifier, 1);\r
- }\r
-\r
- private String addFilter(List aFilters, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction, Date aLastHit) {\r
- MirAntiAbuseFilterType type = (MirAntiAbuseFilterType) filterTypes.get(aType);\r
-\r
- if (type == null)\r
- return "invalidtype";\r
-\r
- if (!type.validate(anExpression)) {\r
- return "invalidexpression";\r
- }\r
-\r
- FilterRule filter = new FilterRule();\r
-\r
- filter.setId(generateId());\r
- filter.setExpression(anExpression);\r
- filter.setType(aType);\r
- filter.setComments(aComments);\r
- filter.setArticleAction(anArticleAction);\r
- filter.setCommentAction(aCommentAction);\r
- filter.setLastHit(aLastHit);\r
-\r
- synchronized (aFilters) {\r
- aFilters.add(filter);\r
- }\r
-\r
- return null;\r
- }\r
-\r
- private String setFilter(List aFilters, String anIdentifier, String aType, String anExpression, String aComments, String aCommentAction, String anArticleAction) {\r
- MirAntiAbuseFilterType type = (MirAntiAbuseFilterType) filterTypes.get(aType);\r
-\r
- if (type == null)\r
- return "invalidtype";\r
-\r
- if (!type.validate(anExpression)) {\r
- return "invalidexpression";\r
- }\r
-\r
- synchronized (aFilters) {\r
- FilterRule filter = findFilter(aFilters, anIdentifier);\r
-\r
- if (filter != null) {\r
- filter.setExpression(anExpression);\r
- filter.setType(aType);\r
- filter.setCommentAction(aCommentAction);\r
- filter.setArticleAction(anArticleAction);\r
- filter.setComments(aComments);\r
- }\r
-\r
- return null;\r
- }\r
- }\r
-\r
- private FilterRule findFilter(List aFilters, String anIdentifier) {\r
- synchronized (aFilters) {\r
- Iterator i = aFilters.iterator();\r
- while (i.hasNext()) {\r
- FilterRule filter = (FilterRule) i.next();\r
-\r
- if (filter.getId().equals(anIdentifier)) {\r
- return filter;\r
- }\r
- }\r
- }\r
-\r
- return null;\r
- }\r
-\r
- private void moveFilter(List aFilters, String anIdentifier, int aDirection) {\r
- synchronized (aFilters) {\r
- for (int i = 0; i < aFilters.size(); i++) {\r
- FilterRule rule = (FilterRule) aFilters.get(i);\r
-\r
- if (rule.getId().equals(anIdentifier) && (i + aDirection >= 0) && (i + aDirection < aFilters.size())) {\r
- aFilters.remove(rule);\r
- aFilters.add(i + aDirection, rule);\r
- break;\r
- }\r
- }\r
- }\r
- }\r
-\r
- private void deleteFilter(List aFilters, String anIdentifier) {\r
- synchronized (aFilters) {\r
- FilterRule filter = findFilter(aFilters, anIdentifier);\r
-\r
- if (filter != null) {\r
- aFilters.remove(filter);\r
- }\r
- }\r
- }\r
-\r
- private String generateId() {\r
- synchronized (this) {\r
- maxIdentifier = maxIdentifier + 1;\r
-\r
- return Integer.toString(maxIdentifier);\r
- }\r
- }\r
-\r
- public class FilterRule {\r
- private String identifier;\r
- private String expression;\r
- private String type;\r
- private String comments;\r
- private String articleAction;\r
- private String commentAction;\r
- private Date lastHit;\r
-\r
- public FilterRule() {\r
- expression = "";\r
- type = "";\r
- identifier = "";\r
- comments = "";\r
- articleAction = articleBlockAction;\r
- commentAction = commentBlockAction;\r
- lastHit = null;\r
- }\r
-\r
- public Date getLastHit() {\r
- return lastHit;\r
- }\r
-\r
- public void setLastHit(Date aDate) {\r
- lastHit = aDate;\r
- }\r
-\r
- public String getId() {\r
- return identifier;\r
- }\r
-\r
- public void setId(String anId) {\r
- identifier = anId;\r
- }\r
-\r
- public String getExpression() {\r
- return expression;\r
- }\r
-\r
- public void setExpression(String anExpression) {\r
- expression = anExpression;\r
- }\r
-\r
- public String getType() {\r
- return type;\r
- }\r
-\r
- public void setType(String aType) {\r
- type = aType;\r
- }\r
-\r
- public void setComments(String aComments) {\r
- comments = aComments;\r
- }\r
-\r
- public String getComments() {\r
- return comments;\r
- }\r
-\r
- public String getArticleAction() {\r
- return articleAction;\r
- }\r
-\r
- public void setArticleAction(String anArticleAction) {\r
- articleAction = anArticleAction;\r
- }\r
-\r
- public String getCommentAction() {\r
- return commentAction;\r
- }\r
-\r
- public void setCommentAction(String aCommentAction) {\r
- commentAction = aCommentAction;\r
- }\r
-\r
- public boolean test(Entity anEntity, Request aRequest) {\r
- MirAntiAbuseFilterType filterType = (MirAntiAbuseFilterType) filterTypes.get(type);\r
- try {\r
- if (filterType != null)\r
- return filterType.test(expression, anEntity, aRequest);\r
- }\r
- catch (Throwable t) {\r
- logger.error("error while testing " + type + "-filter '" + expression + "'");\r
- }\r
-\r
- return false;\r
- };\r
-\r
- public Object clone() {\r
- FilterRule result = new FilterRule();\r
- result.setComments(getComments());\r
- result.setExpression(getExpression());\r
- result.setId(getId());\r
- result.setType(getType());\r
- result.setArticleAction(getArticleAction());\r
- result.setCommentAction(getCommentAction());\r
- result.setLastHit(getLastHit());\r
-\r
- return result;\r
- }\r
- }\r
-\r
- private String escapeFilterPart(String aFilterPart) {\r
- return StringRoutines.replaceStringCharacters(aFilterPart,\r
- new char[] {'\\', ':', '\n', '\r', '\t', ' '}\r
- ,\r
- new String[] {"\\\\", "\\:", "\\n", "\\r", "\\t", "\\ "});\r
- }\r
-\r
- private String deescapeFilterPart(String aFilterPart) {\r
- return StringRoutines.replaceEscapedStringCharacters(aFilterPart,\r
- '\\',\r
- new char[] {'\\', ':', 'n', 'r', 't', ' '}\r
- ,\r
- new String[] {"\\", ":", "\n", "\r", "\t", " "});\r
- }\r
-\r
- private void setFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {\r
- synchronized (aFilters) {\r
- Iterator i = aFilters.iterator();\r
-\r
- while (i.hasNext()) {\r
- FilterRule filter = (FilterRule) i.next();\r
-\r
- String filterconfig =\r
- escapeFilterPart(filter.getType()) + ":" +\r
- escapeFilterPart(filter.getExpression()) + ":" +\r
- escapeFilterPart(filter.getArticleAction()) + ":" +\r
- escapeFilterPart(filter.getCommentAction()) + ":" +\r
- escapeFilterPart(filter.getComments()) + ":";\r
-\r
- if (filter.getLastHit() != null)\r
- filterconfig = filterconfig + filter.getLastHit().getTime();\r
-\r
- aConfiguration.addProperty(aConfigKey, filterconfig);\r
- }\r
- }\r
- }\r
-\r
- private void getFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {\r
- synchronized (aFilters) {\r
- aFilters.clear();\r
-\r
- if (aConfiguration.getStringArray(aConfigKey) != null) {\r
-\r
- Iterator i = Arrays.asList(aConfiguration.getStringArray(aConfigKey)).\r
- iterator();\r
-\r
- while (i.hasNext()) {\r
- String filter = (String) i.next();\r
- List parts = StringRoutines.splitStringWithEscape(filter, ':', '\\');\r
- if (parts.size() == 2) {\r
- parts.add(articleBlockAction);\r
- parts.add(commentBlockAction);\r
- parts.add("");\r
- parts.add("");\r
- }\r
-\r
- if (parts.size() >= 5) {\r
- Date lastHit = null;\r
-\r
- if (parts.size() >= 6) {\r
- String lastHitString = (String) parts.get(5);\r
-\r
- try {\r
- lastHit = new Date(Long.parseLong(lastHitString));\r
- }\r
- catch (Throwable t) {\r
- }\r
- }\r
-\r
- addFilter(deescapeFilterPart( (String) parts.get(0)),\r
- deescapeFilterPart( (String) parts.get(1)),\r
- deescapeFilterPart( (String) parts.get(4)),\r
- deescapeFilterPart( (String) parts.get(3)),\r
- deescapeFilterPart( (String) parts.get(2)), lastHit);\r
- }\r
- }\r
- }\r
- }\r
- }\r
-\r
- private static class LogEntry {\r
- private String ipNumber;\r
- private String browserString;\r
- private String id;\r
- private Date timeStamp;\r
- private boolean isArticle;\r
-\r
- public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {\r
- ipNumber = anIpNumber;\r
- browserString = aBrowserString;\r
- id = anId;\r
- isArticle = anIsArticle;\r
- timeStamp = aTimeStamp;\r
- }\r
-\r
- public String getIpNumber() {\r
- return ipNumber;\r
- }\r
-\r
- public String getBrowserString() {\r
- return browserString;\r
- }\r
-\r
- public String getId() {\r
- return id;\r
- }\r
-\r
- public Date getTimeStamp() {\r
- return timeStamp;\r
- }\r
-\r
- public boolean getIsArticle() {\r
- return isArticle;\r
- }\r
- }\r
-\r
- private void truncateLog() {\r
- synchronized (log) {\r
- if (!logEnabled)\r
- log.clear();\r
- else {\r
- while (log.size() > 0 && log.size() > logSize) {\r
- log.remove(0);\r
- }\r
- }\r
- }\r
- };\r
-\r
- private void appendLog(LogEntry anEntry) {\r
- synchronized (log) {\r
- if (logEnabled) {\r
- log.add(anEntry);\r
- truncateLog();\r
- }\r
- }\r
- }\r
-}\r
+/*
+ * Copyright (C) 2001, 2002 The Mir-coders group
+ *
+ * This file is part of Mir.
+ *
+ * Mir is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Mir 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Mir; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * In addition, as a special exception, The Mir-coders gives permission to link
+ * the code of this program with any library licensed under the Apache Software License,
+ * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
+ * (or with modified versions of the above that use the same license as the above),
+ * and distribute linked combinations including the two. You must obey the
+ * GNU General Public License in all respects for all of the code used other than
+ * the above mentioned libraries. If you modify this file, you may extend this
+ * exception to your version of the file, but you are not obligated to do so.
+ * If you do not wish to do so, delete this exception statement from your version.
+ */
+
+package mircoders.global;
+
+import mir.config.MirPropertiesConfiguration;
+import mir.entity.Entity;
+import mir.entity.adapter.EntityAdapterModel;
+import mir.log.LoggerWrapper;
+import mir.module.EntityNotFoundExc;
+import mir.session.Request;
+import mir.util.DateTimeRoutines;
+import mir.util.EntityUtility;
+import mir.util.GeneratorFormatAdapters;
+import mircoders.abuse.FilterEngine;
+import mircoders.entity.EntityComment;
+import mircoders.entity.EntityContent;
+import mircoders.localizer.MirAdminInterfaceLocalizer;
+import mircoders.module.ModuleComment;
+import mircoders.module.ModuleContent;
+import org.apache.commons.collections.ExtendedProperties;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * This class manages abuse (spam, offending material, etc.). This
+ * is done by using a set of filters managed by the FilterEngine class.
+ * Filters may be of different types (IP, throttle, regexp...),
+ * but are created and configured in a single user interface (web page),
+ * and are stored in a single database table called "filter".
+ */
+public class Abuse {
+ private LoggerWrapper logger;
+ private int logSize;
+ private boolean logEnabled;
+ private boolean openPostingDisabled;
+ private boolean openPostingPassword;
+ private boolean cookieOnBlock;
+ private String articleBlockAction;
+ private String commentBlockAction;
+ private final List log = new ArrayList();
+ private File configFile = MirGlobal.config().getFile("Abuse.Config");
+ private FilterEngine filterEngine;
+
+ private MirPropertiesConfiguration configuration;
+
+ private static String cookieName = MirGlobal.config().getString("Abuse.CookieName");
+ private static int cookieMaxAge = 60 * 60 * MirGlobal.config().getInt("Abuse.CookieMaxAge");
+ private EntityAdapterModel model;
+
+ public Abuse(EntityAdapterModel aModel) {
+ logger = new LoggerWrapper("Global.Abuse");
+ filterEngine = new FilterEngine(aModel);
+ model = aModel;
+
+ try {
+ configuration = MirPropertiesConfiguration.instance();
+ }
+ catch (Throwable e) {
+ throw new RuntimeException("Can't get configuration: " + e.getMessage());
+ }
+
+ logSize = 100;
+ logEnabled = false;
+ articleBlockAction = "";
+ commentBlockAction = "";
+ openPostingPassword = false;
+ openPostingDisabled = false;
+ cookieOnBlock = false;
+
+ load();
+ }
+
+ public FilterEngine getFilterEngine() {
+ return filterEngine;
+ }
+
+ private void setCookie(HttpServletResponse aResponse) {
+ Random random = new Random();
+
+ Cookie cookie = new Cookie(cookieName, Integer.toString(random.nextInt(1000000000)));
+ cookie.setMaxAge(cookieMaxAge);
+ cookie.setPath("/");
+
+ if (aResponse != null)
+ aResponse.addCookie(cookie);
+ }
+
+ private boolean checkCookie(List aCookies) {
+ if (getCookieOnBlock()) {
+ Iterator i = aCookies.iterator();
+
+ while (i.hasNext()) {
+ Cookie cookie = (Cookie) i.next();
+
+ if (cookie.getName().equals(cookieName)) {
+ logger.debug("cookie match");
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+ /** Checks if there is a filter that matches a comment and takes
+ * appropriate action (as configured in the xxxxxaction field of
+ * the filter table). The actual matching is delegated to the
+ * FilterEngine class.
+ */
+ public void checkComment(EntityComment aComment, Request aRequest, HttpServletResponse aResponse) {
+ try {
+ long time = System.currentTimeMillis();
+
+ FilterEngine.Filter matchingFilter = filterEngine.testPosting(aComment, aRequest);
+
+ if (matchingFilter != null) {
+ logger.debug("Match for " + matchingFilter.getTag());
+ matchingFilter.updateLastHit(new GregorianCalendar().getTime());
+
+ StringBuffer line = new StringBuffer();
+
+ line.append(DateTimeRoutines.advancedDateFormat(
+ configuration.getString("Mir.DefaultDateTimeFormat"),
+ (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
+
+ line.append(" ");
+ line.append(matchingFilter.getTag());
+ EntityUtility.appendLineToField(aComment, "comment", line.toString());
+
+ MirGlobal.performCommentOperation(null, aComment, matchingFilter.getCommentAction());
+ setCookie(aResponse);
+ save();
+ logComment(aComment, aRequest, matchingFilter.getTag());
+ }
+ else {
+ logComment(aComment, aRequest);
+ }
+
+ logger.debug("checkComment: " + (System.currentTimeMillis() - time) + "ms");
+ }
+ catch (Throwable t) {
+ logger.error("Exception thrown while checking comment", t);
+ }
+ }
+ /** Checks if there is a filter that matches an articleand takes
+ * appropriate action (as configured in the xxxxxaction field of
+ * the filter table). The actual matching is delegated to the
+ * FilterEngine class.
+ */
+ public void checkArticle(EntityContent anArticle, Request aRequest, HttpServletResponse aResponse) {
+ try {
+ long time = System.currentTimeMillis();
+
+ FilterEngine.Filter matchingFilter = filterEngine.testPosting(anArticle, aRequest);
+
+ if (matchingFilter != null) {
+ logger.debug("Match for " + matchingFilter.getTag());
+// matchingFilter.updateLastHit(new GregorianCalendar().getTime());
+
+ StringBuffer line = new StringBuffer();
+
+ line.append(DateTimeRoutines.advancedDateFormat(
+ configuration.getString("Mir.DefaultDateTimeFormat"),
+ (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
+
+ line.append(" ");
+ line.append(matchingFilter.getTag());
+ EntityUtility.appendLineToField(anArticle, "comment", line.toString());
+
+ MirGlobal.performArticleOperation(null, anArticle, matchingFilter.getArticleAction());
+ setCookie(aResponse);
+ save();
+ logArticle(anArticle, aRequest, matchingFilter.getTag());
+ }
+ else {
+ logArticle(anArticle, aRequest);
+ }
+
+ logger.info("checkArticle: " + (System.currentTimeMillis() - time) + "ms");
+ }
+ catch (Throwable t) {
+ logger.error("Exception thrown while checking article", t);
+ }
+ }
+
+ public boolean getLogEnabled() {
+ return logEnabled;
+ }
+
+ public void setLogEnabled(boolean anEnabled) {
+ if (!configuration.getString("Abuse.DisallowIPLogging", "0").equals("1"))
+ logEnabled = anEnabled;
+ truncateLog();
+ }
+
+ public int getLogSize() {
+ return logSize;
+ }
+
+ public void setLogSize(int aSize) {
+ logSize = aSize;
+ truncateLog();
+ }
+
+ public boolean getOpenPostingDisabled() {
+ return openPostingDisabled;
+ }
+
+ public void setOpenPostingDisabled(boolean anOpenPostingDisabled) {
+ openPostingDisabled = anOpenPostingDisabled;
+ }
+
+ public boolean getOpenPostingPassword() {
+ return openPostingPassword;
+ }
+
+ public void setOpenPostingPassword(boolean anOpenPostingPassword) {
+ openPostingPassword = anOpenPostingPassword;
+ }
+
+ public boolean getCookieOnBlock() {
+ return cookieOnBlock;
+ }
+
+ public void setCookieOnBlock(boolean aCookieOnBlock) {
+ cookieOnBlock = aCookieOnBlock;
+ }
+
+ public String getArticleBlockAction() {
+ return articleBlockAction;
+ }
+
+ public void setArticleBlockAction(String anAction) {
+ articleBlockAction = anAction;
+ }
+
+ public String getCommentBlockAction() {
+ return commentBlockAction;
+ }
+
+ public void setCommentBlockAction(String anAction) {
+ commentBlockAction = anAction;
+ }
+
+ public List getLog() {
+ ModuleContent contentModule = new ModuleContent();
+ ModuleComment commentModule = new ModuleComment();
+
+ synchronized (log) {
+ List result = new ArrayList();
+
+ Iterator i = log.iterator();
+ while (i.hasNext()) {
+ LogEntry logEntry = (LogEntry) i.next();
+ Map entry = new HashMap();
+
+ entry.put("ip", logEntry.getIpNumber());
+ entry.put("id", logEntry.getId());
+ entry.put("timestamp", new GeneratorFormatAdapters.DateFormatAdapter(logEntry.getTimeStamp(), MirPropertiesConfiguration.instance().getString("Mir.DefaultTimezone")));
+
+ if (logEntry.getIsArticle()) {
+ entry.put("type", "content");
+ try {
+ entry.put("object",
+ model.makeEntityAdapter("content", contentModule.getById(logEntry.getId())));
+ }
+ catch (EntityNotFoundExc e) {
+ entry.put("object", null);
+ }
+ }
+ else {
+ entry.put("type", "comment");
+ try {
+ entry.put("object",
+ model.makeEntityAdapter("comment", commentModule.getById(logEntry.getId())));
+ }
+ catch (EntityNotFoundExc e) {
+ entry.put("object", null);
+ }
+ }
+
+ entry.put("browser", logEntry.getBrowserString());
+ entry.put("filtertag", logEntry.getMatchingFilterTag());
+
+ result.add(entry);
+ }
+
+ return result;
+ }
+ }
+
+ public void logComment(Entity aComment, Request aRequest) {
+ logComment(aComment, aRequest, null);
+ }
+
+ public void logComment(Entity aComment, Request aRequest, String aMatchingFilterTag) {
+ String ipAddress = aRequest.getHeader("ip");
+ String id = aComment.getId();
+ String browser = aRequest.getHeader("User-Agent");
+
+ logComment(ipAddress, id, new Date(), browser, aMatchingFilterTag);
+ }
+
+ public void logArticle(Entity anArticle, Request aRequest) {
+ logArticle(anArticle, aRequest, null);
+ }
+
+ public void logArticle(Entity anArticle, Request aRequest, String aMatchingFilterTag) {
+ String ipAddress = aRequest.getHeader("ip");
+ String id = anArticle.getId();
+ String browser = aRequest.getHeader("User-Agent");
+
+ logArticle(ipAddress, id, new Date(), browser, aMatchingFilterTag);
+ }
+
+ public void logComment(String anIp, String anId, Date aTimeStamp, String aBrowser, String aMatchingFilterTag) {
+ appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, false, aMatchingFilterTag));
+ }
+
+ public void logArticle(String anIp, String anId, Date aTimeStamp, String aBrowser, String aMatchingFilterTag) {
+ appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, true, aMatchingFilterTag));
+ }
+
+ public synchronized void load() {
+ try {
+ ExtendedProperties configuration = new ExtendedProperties(configFile.getAbsolutePath());
+
+ setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));
+ setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));
+ setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));
+ setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));
+ setLogSize(configuration.getInt("abuse.logSize", 10));
+ setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));
+ setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));
+ }
+ catch (Throwable t) {
+ throw new RuntimeException(t.toString());
+ }
+ }
+
+ public synchronized void save() {
+ try {
+ ExtendedProperties configuration = new ExtendedProperties();
+
+ configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled() ? "1" : "0");
+ configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword() ? "1" : "0");
+ configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock() ? "1" : "0");
+ configuration.addProperty("abuse.logEnabled", getLogEnabled() ? "1" : "0");
+ configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));
+ configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());
+ configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());
+
+ configuration.save(new BufferedOutputStream(new FileOutputStream(configFile),8192), "Anti abuse configuration");
+ }
+ catch (Throwable t) {
+ throw new RuntimeException(t.toString());
+ }
+ }
+
+ public List getArticleActions() {
+ try {
+ List result = new ArrayList();
+
+ Iterator i = MirGlobal.localizer().adminInterface().simpleArticleOperations().iterator();
+ while (i.hasNext()) {
+ MirAdminInterfaceLocalizer.EntityOperation operation =
+ (MirAdminInterfaceLocalizer.EntityOperation) i.next();
+
+ Map action = new HashMap();
+ action.put("resource", operation.getName());
+ action.put("identifier", operation.getName());
+
+ result.add(action);
+ }
+
+ return result;
+ }
+ catch (Throwable t) {
+ throw new RuntimeException("can't get article actions");
+ }
+ }
+
+ public List getCommentActions() {
+ try {
+ List result = new ArrayList();
+
+ Iterator i = MirGlobal.localizer().adminInterface().simpleCommentOperations().iterator();
+ while (i.hasNext()) {
+ MirAdminInterfaceLocalizer.EntityOperation operation =
+ (MirAdminInterfaceLocalizer.EntityOperation) i.next();
+
+ Map action = new HashMap();
+ action.put("resource", operation.getName());
+ action.put("identifier", operation.getName());
+
+ result.add(action);
+ }
+
+ return result;
+ }
+ catch (Throwable t) {
+ throw new RuntimeException("can't get comment actions");
+ }
+ }
+
+ private static class LogEntry {
+ private String ipNumber;
+ private String browserString;
+ private String id;
+ private Date timeStamp;
+ private boolean isArticle;
+ private String matchingFilterTag;
+
+ public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle, String aMatchingFilterTag) {
+ ipNumber = anIpNumber;
+ browserString = aBrowserString;
+ id = anId;
+ isArticle = anIsArticle;
+ timeStamp = aTimeStamp;
+ matchingFilterTag = aMatchingFilterTag;
+ }
+
+ public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {
+ this(aTimeStamp, anIpNumber, aBrowserString, anId, anIsArticle, null);
+ }
+
+ public String getIpNumber() {
+ return ipNumber;
+ }
+
+ public String getBrowserString() {
+ return browserString;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getMatchingFilterTag() {
+ return matchingFilterTag;
+ }
+
+ public Date getTimeStamp() {
+ return timeStamp;
+ }
+
+ public boolean getIsArticle() {
+ return isArticle;
+ }
+ }
+
+ private void truncateLog() {
+ synchronized (log) {
+ if (!logEnabled)
+ log.clear();
+ else {
+ while (log.size() > 0 && log.size() > logSize) {
+ log.remove(log.size()-1);
+ }
+ }
+ }
+ }
+
+ private void appendLog(LogEntry anEntry) {
+ synchronized (log) {
+ if (logEnabled) {
+ log.add(0, anEntry);
+ truncateLog();
+ }
+ }
+ }
+}