2 * Copyright (C) 2001, 2002 The Mir-coders group
4 * This file is part of Mir.
6 * Mir is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * Mir is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Mir; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * In addition, as a special exception, The Mir-coders gives permission to link
21 * the code of this program with any library licensed under the Apache Software License,
22 * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23 * (or with modified versions of the above that use the same license as the above),
24 * and distribute linked combinations including the two. You must obey the
25 * GNU General Public License in all respects for all of the code used other than
26 * the above mentioned libraries. If you modify this file, you may extend this
27 * exception to your version of the file, but you are not obligated to do so.
28 * If you do not wish to do so, delete this exception statement from your version.
31 package mircoders.global;
33 import mir.config.MirPropertiesConfiguration;
34 import mir.entity.Entity;
35 import mir.entity.adapter.EntityAdapterModel;
36 import mir.log.LoggerWrapper;
37 import mir.module.EntityNotFoundExc;
38 import mir.session.Request;
39 import mir.util.DateTimeRoutines;
40 import mir.util.EntityUtility;
41 import mir.util.GeneratorFormatAdapters;
42 import mircoders.abuse.FilterEngine;
43 import mircoders.entity.EntityComment;
44 import mircoders.entity.EntityContent;
45 import mircoders.localizer.MirAdminInterfaceLocalizer;
46 import mircoders.module.ModuleComment;
47 import mircoders.module.ModuleContent;
48 import org.apache.commons.collections.ExtendedProperties;
50 import javax.servlet.http.Cookie;
51 import javax.servlet.http.HttpServletResponse;
52 import java.io.BufferedOutputStream;
54 import java.io.FileOutputStream;
55 import java.util.ArrayList;
56 import java.util.Date;
57 import java.util.GregorianCalendar;
58 import java.util.HashMap;
59 import java.util.Iterator;
60 import java.util.List;
62 import java.util.Random;
65 * This class manages abuse (spam, offending material, etc.). This
66 * is done by using a set of filters managed by the FilterEngine class.
67 * Filters may be of different types (IP, throttle, regexp...),
68 * but are created and configured in a single user interface (web page),
69 * and are stored in a single database table called "filter".
72 private LoggerWrapper logger;
74 private boolean logEnabled;
75 private boolean openPostingDisabled;
76 private boolean openPostingPassword;
77 private boolean cookieOnBlock;
78 private String articleBlockAction;
79 private String commentBlockAction;
80 private final List log = new ArrayList();
81 private File configFile = MirGlobal.config().getFile("Abuse.Config");
82 private FilterEngine filterEngine;
84 private MirPropertiesConfiguration configuration;
86 private static String cookieName = MirGlobal.config().getString("Abuse.CookieName");
87 private static int cookieMaxAge = 60 * 60 * MirGlobal.config().getInt("Abuse.CookieMaxAge");
88 private EntityAdapterModel model;
90 public Abuse(EntityAdapterModel aModel) {
91 logger = new LoggerWrapper("Global.Abuse");
92 filterEngine = new FilterEngine(aModel);
96 configuration = MirPropertiesConfiguration.instance();
99 throw new RuntimeException("Can't get configuration: " + e.getMessage());
104 articleBlockAction = "";
105 commentBlockAction = "";
106 openPostingPassword = false;
107 openPostingDisabled = false;
108 cookieOnBlock = false;
113 public FilterEngine getFilterEngine() {
117 private void setCookie(HttpServletResponse aResponse) {
118 Random random = new Random();
120 Cookie cookie = new Cookie(cookieName, Integer.toString(random.nextInt(1000000000)));
121 cookie.setMaxAge(cookieMaxAge);
124 if (aResponse != null)
125 aResponse.addCookie(cookie);
128 private boolean checkCookie(List aCookies) {
129 if (getCookieOnBlock()) {
130 Iterator i = aCookies.iterator();
132 while (i.hasNext()) {
133 Cookie cookie = (Cookie) i.next();
135 if (cookie.getName().equals(cookieName)) {
136 logger.debug("cookie match");
144 /** Checks if there is a filter that matches a comment and takes
145 * appropriate action (as configured in the xxxxxaction field of
146 * the filter table). The actual matching is delegated to the
147 * FilterEngine class.
149 public void checkComment(EntityComment aComment, Request aRequest, HttpServletResponse aResponse) {
151 long time = System.currentTimeMillis();
153 FilterEngine.Filter matchingFilter = filterEngine.testPosting(aComment, aRequest);
155 if (matchingFilter != null) {
156 logger.debug("Match for " + matchingFilter.getTag());
157 matchingFilter.updateLastHit(new GregorianCalendar().getTime());
159 StringBuffer line = new StringBuffer();
161 line.append(DateTimeRoutines.advancedDateFormat(
162 configuration.getString("Mir.DefaultDateTimeFormat"),
163 (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
166 line.append(matchingFilter.getTag());
167 EntityUtility.appendLineToField(aComment, "comment", line.toString());
169 MirGlobal.performCommentOperation(null, aComment, matchingFilter.getCommentAction());
170 setCookie(aResponse);
172 logComment(aComment, aRequest, matchingFilter.getTag());
175 logComment(aComment, aRequest);
178 logger.debug("checkComment: " + (System.currentTimeMillis() - time) + "ms");
180 catch (Throwable t) {
181 logger.error("Exception thrown while checking comment", t);
184 /** Checks if there is a filter that matches an articleand takes
185 * appropriate action (as configured in the xxxxxaction field of
186 * the filter table). The actual matching is delegated to the
187 * FilterEngine class.
189 public void checkArticle(EntityContent anArticle, Request aRequest, HttpServletResponse aResponse) {
191 long time = System.currentTimeMillis();
193 FilterEngine.Filter matchingFilter = filterEngine.testPosting(anArticle, aRequest);
195 if (matchingFilter != null) {
196 logger.debug("Match for " + matchingFilter.getTag());
197 // matchingFilter.updateLastHit(new GregorianCalendar().getTime());
199 StringBuffer line = new StringBuffer();
201 line.append(DateTimeRoutines.advancedDateFormat(
202 configuration.getString("Mir.DefaultDateTimeFormat"),
203 (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
206 line.append(matchingFilter.getTag());
207 EntityUtility.appendLineToField(anArticle, "comment", line.toString());
209 MirGlobal.performArticleOperation(null, anArticle, matchingFilter.getArticleAction());
210 setCookie(aResponse);
212 logArticle(anArticle, aRequest, matchingFilter.getTag());
215 logArticle(anArticle, aRequest);
218 logger.info("checkArticle: " + (System.currentTimeMillis() - time) + "ms");
220 catch (Throwable t) {
221 logger.error("Exception thrown while checking article", t);
225 public boolean getLogEnabled() {
229 public void setLogEnabled(boolean anEnabled) {
230 if (!configuration.getString("Abuse.DisallowIPLogging", "0").equals("1"))
231 logEnabled = anEnabled;
235 public int getLogSize() {
239 public void setLogSize(int aSize) {
244 public boolean getOpenPostingDisabled() {
245 return openPostingDisabled;
248 public void setOpenPostingDisabled(boolean anOpenPostingDisabled) {
249 openPostingDisabled = anOpenPostingDisabled;
252 public boolean getOpenPostingPassword() {
253 return openPostingPassword;
256 public void setOpenPostingPassword(boolean anOpenPostingPassword) {
257 openPostingPassword = anOpenPostingPassword;
260 public boolean getCookieOnBlock() {
261 return cookieOnBlock;
264 public void setCookieOnBlock(boolean aCookieOnBlock) {
265 cookieOnBlock = aCookieOnBlock;
268 public String getArticleBlockAction() {
269 return articleBlockAction;
272 public void setArticleBlockAction(String anAction) {
273 articleBlockAction = anAction;
276 public String getCommentBlockAction() {
277 return commentBlockAction;
280 public void setCommentBlockAction(String anAction) {
281 commentBlockAction = anAction;
284 public List getLog() {
285 ModuleContent contentModule = new ModuleContent();
286 ModuleComment commentModule = new ModuleComment();
289 List result = new ArrayList();
291 Iterator i = log.iterator();
292 while (i.hasNext()) {
293 LogEntry logEntry = (LogEntry) i.next();
294 Map entry = new HashMap();
296 entry.put("ip", logEntry.getIpNumber());
297 entry.put("id", logEntry.getId());
298 entry.put("timestamp", new GeneratorFormatAdapters.DateFormatAdapter(logEntry.getTimeStamp(), MirPropertiesConfiguration.instance().getString("Mir.DefaultTimezone")));
300 if (logEntry.getIsArticle()) {
301 entry.put("type", "content");
304 model.makeEntityAdapter("content", contentModule.getById(logEntry.getId())));
306 catch (EntityNotFoundExc e) {
307 entry.put("object", null);
311 entry.put("type", "comment");
314 model.makeEntityAdapter("comment", commentModule.getById(logEntry.getId())));
316 catch (EntityNotFoundExc e) {
317 entry.put("object", null);
321 entry.put("browser", logEntry.getBrowserString());
322 entry.put("filtertag", logEntry.getMatchingFilterTag());
331 public void logComment(Entity aComment, Request aRequest) {
332 logComment(aComment, aRequest, null);
335 public void logComment(Entity aComment, Request aRequest, String aMatchingFilterTag) {
336 String ipAddress = aRequest.getHeader("ip");
337 String id = aComment.getId();
338 String browser = aRequest.getHeader("User-Agent");
340 logComment(ipAddress, id, new Date(), browser, aMatchingFilterTag);
343 public void logArticle(Entity anArticle, Request aRequest) {
344 logArticle(anArticle, aRequest, null);
347 public void logArticle(Entity anArticle, Request aRequest, String aMatchingFilterTag) {
348 String ipAddress = aRequest.getHeader("ip");
349 String id = anArticle.getId();
350 String browser = aRequest.getHeader("User-Agent");
352 logArticle(ipAddress, id, new Date(), browser, aMatchingFilterTag);
355 public void logComment(String anIp, String anId, Date aTimeStamp, String aBrowser, String aMatchingFilterTag) {
356 appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, false, aMatchingFilterTag));
359 public void logArticle(String anIp, String anId, Date aTimeStamp, String aBrowser, String aMatchingFilterTag) {
360 appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, true, aMatchingFilterTag));
363 public synchronized void load() {
365 ExtendedProperties configuration = new ExtendedProperties(configFile.getAbsolutePath());
367 setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));
368 setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));
369 setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));
370 setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));
371 setLogSize(configuration.getInt("abuse.logSize", 10));
372 setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));
373 setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));
375 catch (Throwable t) {
376 throw new RuntimeException(t.toString());
380 public synchronized void save() {
382 ExtendedProperties configuration = new ExtendedProperties();
384 configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled() ? "1" : "0");
385 configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword() ? "1" : "0");
386 configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock() ? "1" : "0");
387 configuration.addProperty("abuse.logEnabled", getLogEnabled() ? "1" : "0");
388 configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));
389 configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());
390 configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());
392 configuration.save(new BufferedOutputStream(new FileOutputStream(configFile),8192), "Anti abuse configuration");
394 catch (Throwable t) {
395 throw new RuntimeException(t.toString());
399 public List getArticleActions() {
401 List result = new ArrayList();
403 Iterator i = MirGlobal.localizer().adminInterface().simpleArticleOperations().iterator();
404 while (i.hasNext()) {
405 MirAdminInterfaceLocalizer.EntityOperation operation =
406 (MirAdminInterfaceLocalizer.EntityOperation) i.next();
408 Map action = new HashMap();
409 action.put("resource", operation.getName());
410 action.put("identifier", operation.getName());
417 catch (Throwable t) {
418 throw new RuntimeException("can't get article actions");
422 public List getCommentActions() {
424 List result = new ArrayList();
426 Iterator i = MirGlobal.localizer().adminInterface().simpleCommentOperations().iterator();
427 while (i.hasNext()) {
428 MirAdminInterfaceLocalizer.EntityOperation operation =
429 (MirAdminInterfaceLocalizer.EntityOperation) i.next();
431 Map action = new HashMap();
432 action.put("resource", operation.getName());
433 action.put("identifier", operation.getName());
440 catch (Throwable t) {
441 throw new RuntimeException("can't get comment actions");
445 private static class LogEntry {
446 private String ipNumber;
447 private String browserString;
449 private Date timeStamp;
450 private boolean isArticle;
451 private String matchingFilterTag;
453 public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle, String aMatchingFilterTag) {
454 ipNumber = anIpNumber;
455 browserString = aBrowserString;
457 isArticle = anIsArticle;
458 timeStamp = aTimeStamp;
459 matchingFilterTag = aMatchingFilterTag;
462 public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {
463 this(aTimeStamp, anIpNumber, aBrowserString, anId, anIsArticle, null);
466 public String getIpNumber() {
470 public String getBrowserString() {
471 return browserString;
474 public String getId() {
478 public String getMatchingFilterTag() {
479 return matchingFilterTag;
482 public Date getTimeStamp() {
486 public boolean getIsArticle() {
491 private void truncateLog() {
496 while (log.size() > 0 && log.size() > logSize) {
497 log.remove(log.size()-1);
503 private void appendLog(LogEntry anEntry) {