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.session.Request;
38 import mir.util.DateTimeRoutines;
39 import mir.util.EntityUtility;
40 import mir.util.GeneratorFormatAdapters;
41 import mir.util.StringRoutines;
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.FileNotFoundException;
55 import java.io.FileOutputStream;
56 import java.util.ArrayList;
57 import java.util.Date;
58 import java.util.GregorianCalendar;
59 import java.util.HashMap;
60 import java.util.Iterator;
61 import java.util.List;
63 import java.util.Random;
66 * This class manages abuse (spam, offending material, etc.). This
67 * is done by using a set of filters managed by the FilterEngine class.
68 * Filters may be of different types (IP, throttle, regexp...),
69 * but are created and configured in a single user interface (web page),
70 * and are stored in a single database table called "filter".
73 private LoggerWrapper logger;
75 private boolean logEnabled;
76 private boolean openPostingDisabled;
77 private boolean openPostingPassword;
78 private boolean cookieOnBlock;
79 private String articleBlockAction;
80 private String commentBlockAction;
82 private File configFile = MirGlobal.config().getFile("Abuse.Config");
83 private FilterEngine filterEngine;
85 private MirPropertiesConfiguration configuration;
87 private static String cookieName = MirGlobal.config().getString("Abuse.CookieName");
88 private static int cookieMaxAge = 60 * 60 * MirGlobal.config().getInt("Abuse.CookieMaxAge");
89 private EntityAdapterModel model;
91 public Abuse(EntityAdapterModel aModel) {
92 logger = new LoggerWrapper("Global.Abuse");
93 filterEngine = new FilterEngine(aModel);
96 log = new ArrayList();
99 configuration = MirPropertiesConfiguration.instance();
101 catch (Throwable e) {
102 throw new RuntimeException("Can't get configuration: " + e.getMessage());
107 articleBlockAction = "";
108 commentBlockAction = "";
109 openPostingPassword = false;
110 openPostingDisabled = false;
111 cookieOnBlock = false;
116 public FilterEngine getFilterEngine() {
120 private void setCookie(HttpServletResponse aResponse) {
121 Random random = new Random();
123 Cookie cookie = new Cookie(cookieName, Integer.toString(random.nextInt(1000000000)));
124 cookie.setMaxAge(cookieMaxAge);
127 if (aResponse != null)
128 aResponse.addCookie(cookie);
131 private boolean checkCookie(List aCookies) {
132 if (getCookieOnBlock()) {
133 Iterator i = aCookies.iterator();
135 while (i.hasNext()) {
136 Cookie cookie = (Cookie) i.next();
138 if (cookie.getName().equals(cookieName)) {
139 logger.debug("cookie match");
147 /** Checks if there is a filter that matches a comment and takes
148 * appropriate action (as configured in the xxxxxaction field of
149 * the filter table). The actual matching is delegated to the
150 * FilterEngine class.
152 public void checkComment(EntityComment aComment, Request aRequest, HttpServletResponse aResponse) {
154 long time = System.currentTimeMillis();
156 FilterEngine.Filter matchingFilter = filterEngine.testPosting(aComment, aRequest);
158 if (matchingFilter != null) {
159 logger.debug("Match for " + matchingFilter.getTag());
160 matchingFilter.updateLastHit(new GregorianCalendar().getTime());
162 StringBuffer line = new StringBuffer();
164 line.append(DateTimeRoutines.advancedDateFormat(
165 configuration.getString("Mir.DefaultDateTimeFormat"),
166 (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
169 line.append(matchingFilter.getTag());
170 EntityUtility.appendLineToField(aComment, "comment", line.toString());
172 MirGlobal.performCommentOperation(null, aComment, matchingFilter.getCommentAction());
173 setCookie(aResponse);
175 logComment(aComment, aRequest, matchingFilter.getTag());
178 logComment(aComment, aRequest);
181 logger.debug("checkComment: " + (System.currentTimeMillis() - time) + "ms");
183 catch (Throwable t) {
184 logger.error("Exception thrown while checking comment", t);
187 /** Checks if there is a filter that matches an articleand takes
188 * appropriate action (as configured in the xxxxxaction field of
189 * the filter table). The actual matching is delegated to the
190 * FilterEngine class.
192 public void checkArticle(EntityContent anArticle, Request aRequest, HttpServletResponse aResponse) {
194 long time = System.currentTimeMillis();
196 FilterEngine.Filter matchingFilter = filterEngine.testPosting(anArticle, aRequest);
198 if (matchingFilter != null) {
199 logger.debug("Match for " + matchingFilter.getTag());
200 // matchingFilter.updateLastHit(new GregorianCalendar().getTime());
202 StringBuffer line = new StringBuffer();
204 line.append(DateTimeRoutines.advancedDateFormat(
205 configuration.getString("Mir.DefaultDateTimeFormat"),
206 (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
209 line.append(matchingFilter.getTag());
210 EntityUtility.appendLineToField(anArticle, "comment", line.toString());
212 MirGlobal.performArticleOperation(null, anArticle, matchingFilter.getArticleAction());
213 setCookie(aResponse);
215 logArticle(anArticle, aRequest, matchingFilter.getTag());
218 logArticle(anArticle, aRequest);
221 logger.info("checkArticle: " + (System.currentTimeMillis() - time) + "ms");
223 catch (Throwable t) {
224 logger.error("Exception thrown while checking article", t);
228 public boolean getLogEnabled() {
232 public void setLogEnabled(boolean anEnabled) {
233 if (!configuration.getString("Abuse.DisallowIPLogging", "0").equals("1"))
234 logEnabled = anEnabled;
238 public int getLogSize() {
242 public void setLogSize(int aSize) {
247 public boolean getOpenPostingDisabled() {
248 return openPostingDisabled;
251 public void setOpenPostingDisabled(boolean anOpenPostingDisabled) {
252 openPostingDisabled = anOpenPostingDisabled;
255 public boolean getOpenPostingPassword() {
256 return openPostingPassword;
259 public void setOpenPostingPassword(boolean anOpenPostingPassword) {
260 openPostingPassword = anOpenPostingPassword;
263 public boolean getCookieOnBlock() {
264 return cookieOnBlock;
267 public void setCookieOnBlock(boolean aCookieOnBlock) {
268 cookieOnBlock = aCookieOnBlock;
271 public String getArticleBlockAction() {
272 return articleBlockAction;
275 public void setArticleBlockAction(String anAction) {
276 articleBlockAction = anAction;
279 public String getCommentBlockAction() {
280 return commentBlockAction;
283 public void setCommentBlockAction(String anAction) {
284 commentBlockAction = anAction;
287 public List getLog() {
288 ModuleContent contentModule = new ModuleContent();
289 ModuleComment commentModule = new ModuleComment();
293 List result = new ArrayList();
295 Iterator i = log.iterator();
296 while (i.hasNext()) {
297 LogEntry logEntry = (LogEntry) i.next();
298 Map entry = new HashMap();
300 entry.put("ip", logEntry.getIpNumber());
301 entry.put("id", logEntry.getId());
302 entry.put("timestamp", new GeneratorFormatAdapters.DateFormatAdapter(logEntry.getTimeStamp(), MirPropertiesConfiguration.instance().getString("Mir.DefaultTimezone")));
304 if (logEntry.getIsArticle()) {
305 entry.put("type", "content");
307 model.makeEntityAdapter("content", contentModule.getById(logEntry.getId())));
310 entry.put("type", "comment");
312 model.makeEntityAdapter("comment", commentModule.getById(logEntry.getId())));
315 entry.put("browser", logEntry.getBrowserString());
316 entry.put("filtertag", logEntry.getMatchingFilterTag());
323 catch (Throwable t) {
324 throw new RuntimeException(t.toString());
329 public void logComment(Entity aComment, Request aRequest) {
330 logComment(aComment, aRequest, null);
333 public void logComment(Entity aComment, Request aRequest, String aMatchingFilterTag) {
334 String ipAddress = aRequest.getHeader("ip");
335 String id = aComment.getId();
336 String browser = aRequest.getHeader("User-Agent");
338 logComment(ipAddress, id, new Date(), browser, aMatchingFilterTag);
341 public void logArticle(Entity anArticle, Request aRequest) {
342 logArticle(anArticle, aRequest, null);
345 public void logArticle(Entity anArticle, Request aRequest, String aMatchingFilterTag) {
346 String ipAddress = aRequest.getHeader("ip");
347 String id = anArticle.getId();
348 String browser = aRequest.getHeader("User-Agent");
350 logArticle(ipAddress, id, new Date(), browser, aMatchingFilterTag);
353 public void logComment(String anIp, String anId, Date aTimeStamp, String aBrowser, String aMatchingFilterTag) {
354 appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, false, aMatchingFilterTag));
357 public void logArticle(String anIp, String anId, Date aTimeStamp, String aBrowser, String aMatchingFilterTag) {
358 appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, true, aMatchingFilterTag));
361 public synchronized void load() {
363 ExtendedProperties configuration = new ExtendedProperties();
366 configuration = new ExtendedProperties(configFile.getAbsolutePath());
368 catch (FileNotFoundException e) {
371 setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));
372 setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));
373 setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));
374 setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));
375 setLogSize(configuration.getInt("abuse.logSize", 10));
376 setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));
377 setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));
379 catch (Throwable t) {
380 throw new RuntimeException(t.toString());
384 public synchronized void save() {
386 ExtendedProperties configuration = new ExtendedProperties();
388 configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled() ? "1" : "0");
389 configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword() ? "1" : "0");
390 configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock() ? "1" : "0");
391 configuration.addProperty("abuse.logEnabled", getLogEnabled() ? "1" : "0");
392 configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));
393 configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());
394 configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());
396 configuration.save(new BufferedOutputStream(new FileOutputStream(configFile),8192), "Anti abuse configuration");
398 catch (Throwable t) {
399 throw new RuntimeException(t.toString());
403 public List getArticleActions() {
405 List result = new ArrayList();
407 Iterator i = MirGlobal.localizer().adminInterface().simpleArticleOperations().iterator();
408 while (i.hasNext()) {
409 MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
410 (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
412 Map action = new HashMap();
413 action.put("resource", operation.getName());
414 action.put("identifier", operation.getName());
421 catch (Throwable t) {
422 throw new RuntimeException("can't get article actions");
426 public List getCommentActions() {
428 List result = new ArrayList();
430 Iterator i = MirGlobal.localizer().adminInterface().simpleCommentOperations().iterator();
431 while (i.hasNext()) {
432 MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
433 (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
435 Map action = new HashMap();
436 action.put("resource", operation.getName());
437 action.put("identifier", operation.getName());
444 catch (Throwable t) {
445 throw new RuntimeException("can't get comment actions");
449 private String escapeConfigListEntry(String aFilterPart) {
450 return StringRoutines.replaceStringCharacters(aFilterPart,
451 new char[] {'\\', ':'},
452 new String[] {"\\\\", "\\:"});
455 private String escapeFilterPart(String aFilterPart) {
456 return StringRoutines.replaceStringCharacters(aFilterPart,
457 new char[] {'\\', '\n', '\r', '\t', ' '},
458 new String[] {"\\\\", "\\n", "\\r", "\\t", "\\ "});
461 private String deescapeFilterPart(String aFilterPart) {
462 return StringRoutines.replaceEscapedStringCharacters(aFilterPart,
464 new char[] {'\\', ':', 'n', 'r', 't', ' '},
465 new String[] {"\\", ":", "\n", "\r", "\t", " "});
468 private static class LogEntry {
469 private String ipNumber;
470 private String browserString;
472 private Date timeStamp;
473 private boolean isArticle;
474 private String matchingFilterTag;
476 public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle, String aMatchingFilterTag) {
477 ipNumber = anIpNumber;
478 browserString = aBrowserString;
480 isArticle = anIsArticle;
481 timeStamp = aTimeStamp;
482 matchingFilterTag = aMatchingFilterTag;
485 public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {
486 this(aTimeStamp, anIpNumber, aBrowserString, anId, anIsArticle, null);
489 public String getIpNumber() {
493 public String getBrowserString() {
494 return browserString;
497 public String getId() {
501 public String getMatchingFilterTag() {
502 return matchingFilterTag;
505 public Date getTimeStamp() {
509 public boolean getIsArticle() {
514 private void truncateLog() {
519 while (log.size() > 0 && log.size() > logSize) {
520 log.remove(log.size()-1);
526 private void appendLog(LogEntry anEntry) {