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 java.io.BufferedOutputStream;
35 import java.io.FileNotFoundException;
36 import java.io.FileOutputStream;
37 import java.util.ArrayList;
38 import java.util.Date;
39 import java.util.GregorianCalendar;
40 import java.util.HashMap;
41 import java.util.Iterator;
42 import java.util.List;
44 import java.util.Random;
46 import javax.servlet.http.Cookie;
47 import javax.servlet.http.HttpServletResponse;
49 import mir.config.MirPropertiesConfiguration;
50 import mir.entity.Entity;
51 import mir.entity.adapter.EntityAdapterModel;
52 import mir.log.LoggerWrapper;
53 import mir.session.Request;
54 import mir.util.DateTimeRoutines;
55 import mir.util.EntityUtility;
56 import mir.util.GeneratorFormatAdapters;
57 import mir.util.StringRoutines;
58 import mircoders.abuse.FilterEngine;
59 import mircoders.entity.EntityComment;
60 import mircoders.entity.EntityContent;
61 import mircoders.localizer.MirAdminInterfaceLocalizer;
62 import mircoders.module.ModuleContent;
63 import mircoders.module.ModuleComment;
65 import org.apache.commons.collections.ExtendedProperties;
68 * This class manages abuse (spam, offending material, etc.). This
69 * is done by using a set of filters managed by the FilterEngine class.
70 * Filters may be of different types (IP, throttle, regexp...),
71 * but are created and configured in a single user interface (web page),
72 * and are stored in a single database table called "filter".
75 private LoggerWrapper logger;
77 private boolean logEnabled;
78 private boolean openPostingDisabled;
79 private boolean openPostingPassword;
80 private boolean cookieOnBlock;
81 private String articleBlockAction;
82 private String commentBlockAction;
84 private File configFile = MirGlobal.config().getFile("Abuse.Config");
85 private FilterEngine filterEngine;
87 private MirPropertiesConfiguration configuration;
89 private static String cookieName = MirGlobal.config().getString("Abuse.CookieName");
90 private static int cookieMaxAge = 60 * 60 * MirGlobal.config().getInt("Abuse.CookieMaxAge");
91 private EntityAdapterModel model;
93 public Abuse(EntityAdapterModel aModel) {
94 logger = new LoggerWrapper("Global.Abuse");
95 filterEngine = new FilterEngine(aModel);
98 log = new ArrayList();
101 configuration = MirPropertiesConfiguration.instance();
103 catch (Throwable e) {
104 throw new RuntimeException("Can't get configuration: " + e.getMessage());
109 articleBlockAction = "";
110 commentBlockAction = "";
111 openPostingPassword = false;
112 openPostingDisabled = false;
113 cookieOnBlock = false;
118 public FilterEngine getFilterEngine() {
122 private void setCookie(HttpServletResponse aResponse) {
123 Random random = new Random();
125 Cookie cookie = new Cookie(cookieName, Integer.toString(random.nextInt(1000000000)));
126 cookie.setMaxAge(cookieMaxAge);
129 if (aResponse != null)
130 aResponse.addCookie(cookie);
133 private boolean checkCookie(List aCookies) {
134 if (getCookieOnBlock()) {
135 Iterator i = aCookies.iterator();
137 while (i.hasNext()) {
138 Cookie cookie = (Cookie) i.next();
140 if (cookie.getName().equals(cookieName)) {
141 logger.debug("cookie match");
149 /** Checks if there is a filter that matches a comment and takes
150 * appropriate action (as configured in the xxxxxaction field of
151 * the filter table). The actual matching is delegated to the
152 * FilterEngine class.
154 public void checkComment(EntityComment aComment, Request aRequest, HttpServletResponse aResponse) {
156 long time = System.currentTimeMillis();
158 FilterEngine.Filter matchingFilter = filterEngine.testPosting(aComment, aRequest);
160 if (matchingFilter != null) {
161 logger.debug("Match for " + matchingFilter.getTag());
162 matchingFilter.updateLastHit(new GregorianCalendar().getTime());
164 StringBuffer line = new StringBuffer();
166 line.append(DateTimeRoutines.advancedDateFormat(
167 configuration.getString("Mir.DefaultDateTimeFormat"),
168 (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
171 line.append(matchingFilter.getTag());
172 EntityUtility.appendLineToField(aComment, "comment", line.toString());
174 MirGlobal.performCommentOperation(null, aComment, matchingFilter.getCommentAction());
175 setCookie(aResponse);
177 logComment(aComment, aRequest, matchingFilter.getTag());
180 logComment(aComment, aRequest);
183 logger.debug("checkComment: " + (System.currentTimeMillis() - time) + "ms");
185 catch (Throwable t) {
186 logger.error("Exception thrown while checking comment", t);
189 /** Checks if there is a filter that matches an articleand takes
190 * appropriate action (as configured in the xxxxxaction field of
191 * the filter table). The actual matching is delegated to the
192 * FilterEngine class.
194 public void checkArticle(EntityContent anArticle, Request aRequest, HttpServletResponse aResponse) {
196 long time = System.currentTimeMillis();
198 FilterEngine.Filter matchingFilter = filterEngine.testPosting(anArticle, aRequest);
200 if (matchingFilter != null) {
201 logger.debug("Match for " + matchingFilter.getTag());
202 // matchingFilter.updateLastHit(new GregorianCalendar().getTime());
204 StringBuffer line = new StringBuffer();
206 line.append(DateTimeRoutines.advancedDateFormat(
207 configuration.getString("Mir.DefaultDateTimeFormat"),
208 (new GregorianCalendar()).getTime(), configuration.getString("Mir.DefaultTimezone")));
211 line.append(matchingFilter.getTag());
212 EntityUtility.appendLineToField(anArticle, "comment", line.toString());
214 MirGlobal.performArticleOperation(null, anArticle, matchingFilter.getArticleAction());
215 setCookie(aResponse);
217 logArticle(anArticle, aRequest, matchingFilter.getTag());
220 logArticle(anArticle, aRequest);
223 logger.info("checkArticle: " + (System.currentTimeMillis() - time) + "ms");
225 catch (Throwable t) {
226 logger.error("Exception thrown while checking article", t);
230 public boolean getLogEnabled() {
234 public void setLogEnabled(boolean anEnabled) {
235 if (!configuration.getString("Abuse.DisallowIPLogging", "0").equals("1"))
236 logEnabled = anEnabled;
240 public int getLogSize() {
244 public void setLogSize(int aSize) {
249 public boolean getOpenPostingDisabled() {
250 return openPostingDisabled;
253 public void setOpenPostingDisabled(boolean anOpenPostingDisabled) {
254 openPostingDisabled = anOpenPostingDisabled;
257 public boolean getOpenPostingPassword() {
258 return openPostingPassword;
261 public void setOpenPostingPassword(boolean anOpenPostingPassword) {
262 openPostingPassword = anOpenPostingPassword;
265 public boolean getCookieOnBlock() {
266 return cookieOnBlock;
269 public void setCookieOnBlock(boolean aCookieOnBlock) {
270 cookieOnBlock = aCookieOnBlock;
273 public String getArticleBlockAction() {
274 return articleBlockAction;
277 public void setArticleBlockAction(String anAction) {
278 articleBlockAction = anAction;
281 public String getCommentBlockAction() {
282 return commentBlockAction;
285 public void setCommentBlockAction(String anAction) {
286 commentBlockAction = anAction;
289 public List getLog() {
290 ModuleContent contentModule = new ModuleContent();
291 ModuleComment commentModule = new ModuleComment();
295 List result = new ArrayList();
297 Iterator i = log.iterator();
298 while (i.hasNext()) {
299 LogEntry logEntry = (LogEntry) i.next();
300 Map entry = new HashMap();
302 entry.put("ip", logEntry.getIpNumber());
303 entry.put("id", logEntry.getId());
304 entry.put("timestamp", new GeneratorFormatAdapters.DateFormatAdapter(logEntry.getTimeStamp(), MirPropertiesConfiguration.instance().getString("Mir.DefaultTimezone")));
306 if (logEntry.getIsArticle()) {
307 entry.put("type", "content");
309 model.makeEntityAdapter("content", contentModule.getById(logEntry.getId())));
312 entry.put("type", "comment");
314 model.makeEntityAdapter("comment", commentModule.getById(logEntry.getId())));
317 entry.put("browser", logEntry.getBrowserString());
318 entry.put("filtertag", logEntry.getMatchingFilterTag());
325 catch (Throwable t) {
326 throw new RuntimeException(t.toString());
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();
368 configuration = new ExtendedProperties(configFile.getAbsolutePath());
370 catch (FileNotFoundException e) {
373 setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));
374 setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));
375 setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));
376 setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));
377 setLogSize(configuration.getInt("abuse.logSize", 10));
378 setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));
379 setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));
381 catch (Throwable t) {
382 throw new RuntimeException(t.toString());
386 public synchronized void save() {
388 ExtendedProperties configuration = new ExtendedProperties();
390 configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled() ? "1" : "0");
391 configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword() ? "1" : "0");
392 configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock() ? "1" : "0");
393 configuration.addProperty("abuse.logEnabled", getLogEnabled() ? "1" : "0");
394 configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));
395 configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());
396 configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());
398 configuration.save(new BufferedOutputStream(new FileOutputStream(configFile),8192), "Anti abuse configuration");
400 catch (Throwable t) {
401 throw new RuntimeException(t.toString());
405 public List getArticleActions() {
407 List result = new ArrayList();
409 Iterator i = MirGlobal.localizer().adminInterface().simpleArticleOperations().iterator();
410 while (i.hasNext()) {
411 MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
412 (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
414 Map action = new HashMap();
415 action.put("resource", operation.getName());
416 action.put("identifier", operation.getName());
423 catch (Throwable t) {
424 throw new RuntimeException("can't get article actions");
428 public List getCommentActions() {
430 List result = new ArrayList();
432 Iterator i = MirGlobal.localizer().adminInterface().simpleCommentOperations().iterator();
433 while (i.hasNext()) {
434 MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
435 (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
437 Map action = new HashMap();
438 action.put("resource", operation.getName());
439 action.put("identifier", operation.getName());
446 catch (Throwable t) {
447 throw new RuntimeException("can't get comment actions");
451 private String escapeConfigListEntry(String aFilterPart) {
452 return StringRoutines.replaceStringCharacters(aFilterPart,
453 new char[] {'\\', ':'},
454 new String[] {"\\\\", "\\:"});
457 private String escapeFilterPart(String aFilterPart) {
458 return StringRoutines.replaceStringCharacters(aFilterPart,
459 new char[] {'\\', '\n', '\r', '\t', ' '},
460 new String[] {"\\\\", "\\n", "\\r", "\\t", "\\ "});
463 private String deescapeFilterPart(String aFilterPart) {
464 return StringRoutines.replaceEscapedStringCharacters(aFilterPart,
466 new char[] {'\\', ':', 'n', 'r', 't', ' '},
467 new String[] {"\\", ":", "\n", "\r", "\t", " "});
470 private static class LogEntry {
471 private String ipNumber;
472 private String browserString;
474 private Date timeStamp;
475 private boolean isArticle;
476 private String matchingFilterTag;
478 public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle, String aMatchingFilterTag) {
479 ipNumber = anIpNumber;
480 browserString = aBrowserString;
482 isArticle = anIsArticle;
483 timeStamp = aTimeStamp;
484 matchingFilterTag = aMatchingFilterTag;
487 public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {
488 this(aTimeStamp, anIpNumber, aBrowserString, anId, anIsArticle, null);
491 public String getIpNumber() {
495 public String getBrowserString() {
496 return browserString;
499 public String getId() {
503 public String getMatchingFilterTag() {
504 return matchingFilterTag;
507 public Date getTimeStamp() {
511 public boolean getIsArticle() {
516 private void truncateLog() {
521 while (log.size() > 0 && log.size() > logSize) {
522 log.remove(log.size()-1);
528 private void appendLog(LogEntry anEntry) {