2 * Copyright (C) 2001, 2002 The Mir-coders group
\r
4 * This file is part of Mir.
\r
6 * Mir is free software; you can redistribute it and/or modify
\r
7 * it under the terms of the GNU General Public License as published by
\r
8 * the Free Software Foundation; either version 2 of the License, or
\r
9 * (at your option) any later version.
\r
11 * Mir is distributed in the hope that it will be useful,
\r
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
14 * GNU General Public License for more details.
\r
16 * You should have received a copy of the GNU General Public License
\r
17 * along with Mir; if not, write to the Free Software
\r
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
\r
20 * In addition, as a special exception, The Mir-coders gives permission to link
\r
21 * the code of this program with any library licensed under the Apache Software License,
\r
22 * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
\r
23 * (or with modified versions of the above that use the same license as the above),
\r
24 * and distribute linked combinations including the two. You must obey the
\r
25 * GNU General Public License in all respects for all of the code used other than
\r
26 * the above mentioned libraries. If you modify this file, you may extend this
\r
27 * exception to your version of the file, but you are not obligated to do so.
\r
28 * If you do not wish to do so, delete this exception statement from your version.
\r
31 package mircoders.global;
\r
33 import java.io.File;
\r
34 import java.io.FileNotFoundException;
\r
35 import java.io.FileOutputStream;
\r
36 import java.util.Arrays;
\r
37 import java.util.Date;
\r
38 import java.util.HashMap;
\r
39 import java.util.Iterator;
\r
40 import java.util.List;
\r
41 import java.util.Map;
\r
42 import java.util.Random;
\r
43 import java.util.Vector;
\r
44 import javax.servlet.http.Cookie;
\r
45 import javax.servlet.http.HttpServletRequest;
\r
46 import javax.servlet.http.HttpServletResponse;
\r
48 import org.apache.commons.collections.ExtendedProperties;
\r
49 import gnu.regexp.RE;
\r
51 import mir.config.MirPropertiesConfiguration;
\r
52 import mir.entity.Entity;
\r
53 import mir.log.LoggerWrapper;
\r
54 import mir.session.HTTPAdapters;
\r
55 import mir.session.Request;
\r
56 import mir.util.GeneratorFormatAdapters;
\r
57 import mir.util.InternetFunctions;
\r
58 import mir.util.StringRoutines;
\r
59 import mir.config.*;
\r
60 import mircoders.entity.EntityComment;
\r
61 import mircoders.entity.EntityContent;
\r
62 import mircoders.entity.EntityUsers;
\r
63 import mircoders.localizer.MirAdminInterfaceLocalizer;
\r
66 public class Abuse {
\r
67 private List filters;
\r
68 private int maxIdentifier;
\r
69 private LoggerWrapper logger;
\r
70 private LoggerWrapper adminUsageLogger;
\r
71 private int logSize;
\r
72 private boolean logEnabled;
\r
73 private boolean openPostingDisabled;
\r
74 private boolean openPostingPassword;
\r
75 private boolean cookieOnBlock;
\r
76 private String articleBlockAction;
\r
77 private String commentBlockAction;
\r
79 private String configFile = MirGlobal.config().getStringWithHome("Abuse.Config");
\r
81 private MirPropertiesConfiguration configuration;
\r
84 private static final String IP_FILTER_TYPE="ip";
\r
85 private static final String REGEXP_FILTER_TYPE="regexp";
\r
86 private static String cookieName=MirGlobal.config().getString("Abuse.CookieName");
\r
87 private static int cookieMaxAge = 60*60*MirGlobal.config().getInt("Abuse.CookieMaxAge");
\r
90 logger = new LoggerWrapper("Global.Abuse");
\r
91 adminUsageLogger = new LoggerWrapper("AdminUsage");
\r
92 filters = new Vector();
\r
97 configuration = MirPropertiesConfiguration.instance();
\r
99 catch (Throwable e) {
\r
100 throw new RuntimeException("Can't get configuration: " + e.getMessage());
\r
104 logEnabled = false;
\r
105 articleBlockAction = "";
\r
106 commentBlockAction = "";
\r
107 openPostingPassword = false;
\r
108 openPostingDisabled = false;
\r
109 cookieOnBlock = false;
\r
114 public boolean checkIpFilter(String anIpAddress) {
\r
115 synchronized (filters) {
\r
116 Iterator i = filters.iterator();
\r
118 while (i.hasNext()) {
\r
119 Filter filter = (Filter) i.next();
\r
122 if ( (filter.getType().equals(IP_FILTER_TYPE)) &&
\r
123 InternetFunctions.isIpAddressInNetwork(anIpAddress, filter.getExpression())) {
\r
124 logger.debug("ip match on " + filter.getExpression());
\r
128 catch (Throwable t) {
\r
129 logger.warn("error while checking ip address " + anIpAddress + " over network " + filter.expression + ": " + t.getMessage());
\r
137 private boolean checkRegExpFilter(Entity anEntity) {
\r
138 synchronized (filters) {
\r
139 Iterator i = filters.iterator();
\r
141 while (i.hasNext()) {
\r
142 Filter filter = (Filter) i.next();
\r
144 if (filter.getType().equals(REGEXP_FILTER_TYPE)) {
\r
146 RE regularExpression = new RE(filter.getExpression(), RE.REG_ICASE);
\r
148 Iterator j = anEntity.getFields().iterator();
\r
149 while (j.hasNext()) {
\r
150 String field = anEntity.getValue( (String) j.next());
\r
152 if (field != null && regularExpression.isMatch(field.toLowerCase())) {
\r
153 logger.debug("regexp match on " + filter.getExpression());
\r
158 catch (Throwable t) {
\r
159 logger.warn("error while checking entity with regexp " + filter.getExpression() + ": " + t.getMessage());
\r
168 private void setCookie(HttpServletResponse aResponse) {
\r
169 Random random = new Random();
\r
171 Cookie cookie = new Cookie(cookieName, Integer.toString(random.nextInt(1000000000)));
\r
172 cookie.setMaxAge(cookieMaxAge);
\r
173 cookie.setPath("/");
\r
175 if (aResponse!=null)
\r
176 aResponse.addCookie(cookie);
\r
179 private boolean checkCookie(List aCookies) {
\r
180 if (getCookieOnBlock()) {
\r
181 Iterator i = aCookies.iterator();
\r
183 while (i.hasNext()) {
\r
184 Cookie cookie = (Cookie) i.next();
\r
186 if (cookie.getName().equals(cookieName)) {
\r
187 logger.debug("cookie match");
\r
196 public boolean checkRequest(Request aRequest, HttpServletResponse aResponse, String anId, boolean anIsComment) {
\r
197 String address = "0.0.0.0";
\r
198 String browser = "unknown";
\r
199 List cookies = null;
\r
201 HttpServletRequest request = null;
\r
203 if (aRequest instanceof HTTPAdapters.HTTPParsedRequestAdapter) {
\r
204 request = ((HTTPAdapters.HTTPParsedRequestAdapter) aRequest).getRequest();
\r
206 else if (aRequest instanceof HTTPAdapters.HTTPRequestAdapter) {
\r
207 request = ((HTTPAdapters.HTTPRequestAdapter) aRequest).getRequest();
\r
209 if (request!=null) {
\r
210 browser = (String) request.getHeader("User-Agent");
\r
211 address = request.getRemoteAddr();
\r
212 if (request.getCookies()!=null)
\r
213 cookies = Arrays.asList(request.getCookies());
\r
215 cookies = new Vector();
\r
219 logComment(address, anId , new Date(), browser);
\r
221 logArticle(address, anId , new Date(), browser);
\r
223 return checkCookie(cookies) || checkIpFilter(address);
\r
226 public void checkComment(EntityComment aComment, Request aRequest, HttpServletResponse aResponse) {
\r
228 long time = System.currentTimeMillis();
\r
230 MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation = MirGlobal.localizer().adminInterface().simpleCommentOperationForName(commentBlockAction);
\r
232 if (checkRequest(aRequest, aResponse, aComment.getId(), true) || checkRegExpFilter(aComment)) {
\r
233 logger.debug("performing operation " + operation.getName());
\r
234 operation.perform(null, MirGlobal.localizer().dataModel().adapterModel().makeEntityAdapter("comment", aComment));
\r
235 setCookie(aResponse);
\r
238 logger.info("checkComment: " + (System.currentTimeMillis()-time) + "ms");
\r
240 catch (Throwable t) {
\r
241 t.printStackTrace(logger.asPrintWriter(logger.DEBUG_MESSAGE));
\r
242 logger.error("Abuse.checkComment: " + t.toString());
\r
246 public void checkArticle(EntityContent anArticle, Request aRequest, HttpServletResponse aResponse) {
\r
248 long time = System.currentTimeMillis();
\r
250 MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation = MirGlobal.localizer().adminInterface().simpleArticleOperationForName(articleBlockAction);
\r
252 if (checkRequest(aRequest, aResponse, anArticle.getId(), false) || checkRegExpFilter(anArticle)) {
\r
253 logger.debug("performing operation " + operation.getName());
\r
254 operation.perform(null, MirGlobal.localizer().dataModel().adapterModel().makeEntityAdapter("content", anArticle));
\r
255 setCookie(aResponse);
\r
258 logger.info("checkArticle: " + (System.currentTimeMillis()-time) + "ms");
\r
260 catch (Throwable t) {
\r
261 t.printStackTrace(logger.asPrintWriter(logger.DEBUG_MESSAGE));
\r
262 logger.error("Abuse.checkArticle: " + t.toString());
\r
266 public boolean getLogEnabled() {
\r
270 public void setLogEnabled(boolean anEnabled) {
\r
271 if (!configuration.getString("Abuse.DisallowIPLogging", "0").equals("1"))
\r
272 logEnabled = anEnabled;
\r
276 public int getLogSize() {
\r
280 public void setLogSize(int aSize) {
\r
285 public boolean getOpenPostingDisabled() {
\r
286 return openPostingDisabled;
\r
289 public void setOpenPostingDisabled(boolean anOpenPostingDisabled) {
\r
290 openPostingDisabled = anOpenPostingDisabled;
\r
293 public boolean getOpenPostingPassword() {
\r
294 return openPostingPassword;
\r
297 public void setOpenPostingPassword(boolean anOpenPostingPassword) {
\r
298 openPostingPassword = anOpenPostingPassword;
\r
301 public boolean getCookieOnBlock() {
\r
302 return cookieOnBlock;
\r
305 public void setCookieOnBlock(boolean aCookieOnBlock) {
\r
306 cookieOnBlock = aCookieOnBlock;
\r
309 public String getArticleBlockAction() {
\r
310 return articleBlockAction;
\r
313 public void setArticleBlockAction(String anAction) {
\r
314 articleBlockAction = anAction;
\r
317 public String getCommentBlockAction() {
\r
318 return commentBlockAction;
\r
321 public void setCommentBlockAction(String anAction) {
\r
322 commentBlockAction = anAction;
\r
326 public List getLog() {
\r
327 synchronized(log) {
\r
329 List result = new Vector();
\r
331 Iterator i = log.iterator();
\r
332 while (i.hasNext()) {
\r
333 LogEntry logEntry = (LogEntry) i.next();
\r
334 Map entry = new HashMap();
\r
336 entry.put("ip", logEntry.getIpNumber());
\r
337 entry.put("id", logEntry.getId());
\r
338 entry.put("timestamp", new GeneratorFormatAdapters.DateFormatAdapter(logEntry.getTimeStamp(), MirPropertiesConfiguration.instance().getString("Mir.DefaultTimezone")));
\r
339 if (logEntry.getIsArticle())
\r
340 entry.put("type", "content");
\r
342 entry.put("type", "comment");
\r
343 entry.put("browser", logEntry.getBrowserString());
\r
350 catch (Throwable t) {
\r
351 throw new RuntimeException(t.toString());
\r
356 public void logComment(String anIp, String anId, Date aTimeStamp, String aBrowser) {
\r
357 appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, false));
\r
360 public void logArticle(String anIp, String anId, Date aTimeStamp, String aBrowser) {
\r
361 appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, true));
\r
364 public void load() {
\r
366 ExtendedProperties configuration = new ExtendedProperties();
\r
369 configuration = new ExtendedProperties(configFile);
\r
371 catch (FileNotFoundException e) {
\r
374 getFilterConfig(filters, "abuse.filter", configuration);
\r
376 setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));
\r
377 setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));
\r
378 setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));
\r
379 setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));
\r
380 setLogSize(configuration.getInt("abuse.logSize", 10));
\r
381 setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));
\r
382 setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));
\r
384 catch (Throwable t) {
\r
385 throw new RuntimeException(t.toString());
\r
388 public void save() {
\r
390 ExtendedProperties configuration = new ExtendedProperties();
\r
392 setFilterConfig(filters, "abuse.filter", configuration);
\r
394 configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled()?"1":"0");
\r
395 configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword()?"1":"0");
\r
396 configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock()?"1":"0");
\r
397 configuration.addProperty("abuse.logEnabled", getLogEnabled()?"1":"0");
\r
398 configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));
\r
399 configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());
\r
400 configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());
\r
402 configuration.save(new FileOutputStream(new File(configFile)), "Anti abuse configuration");
\r
404 catch (Throwable t) {
\r
405 throw new RuntimeException(t.toString());
\r
409 public List getFilterTypes() {
\r
410 List result = new Vector();
\r
412 Map entry = new HashMap();
\r
413 entry.put("resource", "ip");
\r
414 entry.put("id", IP_FILTER_TYPE);
\r
417 entry = new HashMap();
\r
418 entry.put("resource", "regexp");
\r
419 entry.put("id", REGEXP_FILTER_TYPE);
\r
425 public List getArticleActions() {
\r
427 List result = new Vector();
\r
429 Iterator i = MirGlobal.localizer().adminInterface().simpleArticleOperations().iterator();
\r
430 while (i.hasNext()) {
\r
431 MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
\r
432 (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
\r
434 Map action = new HashMap();
\r
435 action.put("resource", operation.getName());
\r
436 action.put("identifier", operation.getName());
\r
438 result.add(action);
\r
443 catch (Throwable t) {
\r
444 throw new RuntimeException("can't get article actions");
\r
448 public List getCommentActions() {
\r
450 List result = new Vector();
\r
452 Iterator i = MirGlobal.localizer().adminInterface().simpleCommentOperations().iterator();
\r
453 while (i.hasNext()) {
\r
454 MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
\r
455 (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
\r
457 Map action = new HashMap();
\r
458 action.put("resource", operation.getName());
\r
459 action.put("identifier", operation.getName());
\r
461 result.add(action);
\r
466 catch (Throwable t) {
\r
467 throw new RuntimeException("can't get comment actions");
\r
471 public List getFilters() {
\r
472 return getFiltersAsMaps(filters);
\r
475 public void addFilter(String aType, String anExpression) {
\r
476 addFilter(filters, aType, anExpression);
\r
479 public void setFilter(String anIdentifier, String aType, String anExpression) {
\r
480 setFilter(filters, anIdentifier, aType, anExpression);
\r
483 public void deleteFilter(String anIdentifier) {
\r
484 deleteFilter(filters, anIdentifier);
\r
487 public void validateIpFilter(String anIdentifier, String anArticleAction, String aCommentAction) throws Exception {
\r
490 private List getFiltersAsMaps(List aFilters) {
\r
491 synchronized(aFilters) {
\r
492 List result = new Vector();
\r
494 Iterator i = aFilters.iterator();
\r
495 while (i.hasNext()) {
\r
496 Filter filter = (Filter) i.next();
\r
497 Map map = new HashMap();
\r
499 map.put("id", filter.getId());
\r
500 map.put("expression", filter.getExpression());
\r
501 map.put("type", filter.getType());
\r
509 private void addFilter(List aFilters, String aType, String anExpression) {
\r
510 Filter filter = new Filter();
\r
512 filter.setId(generateId());
\r
513 filter.setExpression(anExpression);
\r
514 filter.setType(aType);
\r
516 synchronized (aFilters) {
\r
517 aFilters.add(filter);
\r
521 private void setFilter(List aFilters, String anIdentifier, String aType, String anExpression) {
\r
522 synchronized (aFilters) {
\r
523 Filter filter = findFilter(aFilters, anIdentifier);
\r
525 if (filter!=null) {
\r
526 filter.setExpression(anExpression);
\r
527 filter.setType(aType);
\r
532 private Filter findFilter(List aFilters, String anIdentifier) {
\r
533 synchronized (aFilters) {
\r
534 Iterator i = aFilters.iterator();
\r
535 while (i.hasNext()) {
\r
536 Filter filter = (Filter) i.next();
\r
538 if (filter.getId().equals(anIdentifier)) {
\r
547 private void deleteFilter(List aFilters, String anIdentifier) {
\r
548 synchronized (aFilters) {
\r
549 Filter filter = findFilter(aFilters, anIdentifier);
\r
551 if (filter!=null) {
\r
552 aFilters.remove(filter);
\r
557 private String generateId() {
\r
558 synchronized(this) {
\r
559 maxIdentifier = maxIdentifier+1;
\r
561 return Integer.toString(maxIdentifier);
\r
565 private static class Filter {
\r
566 private String identifier;
\r
567 private String expression;
\r
568 private String type;
\r
576 public String getId() {
\r
580 public void setId(String anId) {
\r
584 public String getExpression() {
\r
588 public void setExpression(String anExpression) {
\r
589 expression = anExpression;
\r
592 public String getType() {
\r
596 public void setType(String aType) {
\r
601 private void setFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {
\r
602 synchronized(aFilters) {
\r
603 Iterator i = aFilters.iterator();
\r
605 while (i.hasNext()) {
\r
606 Filter filter = (Filter) i.next();
\r
608 aConfiguration.addProperty(aConfigKey, filter.getType()+":"+filter.getExpression());
\r
613 private void getFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {
\r
614 synchronized(aFilters) {
\r
617 if (aConfiguration.getStringArray(aConfigKey)!=null) {
\r
619 Iterator i = Arrays.asList(aConfiguration.getStringArray(aConfigKey)).
\r
622 while (i.hasNext()) {
\r
623 String filter = (String) i.next();
\r
624 List parts = StringRoutines.separateString(filter, ":");
\r
626 if (parts.size() == 2) {
\r
627 addFilter( (String) parts.get(0), (String) parts.get(1));
\r
634 private static class LogEntry {
\r
635 private String ipNumber;
\r
636 private String browserString;
\r
638 private Date timeStamp;
\r
639 private boolean isArticle;
\r
641 public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {
\r
642 ipNumber = anIpNumber;
\r
643 browserString = aBrowserString;
\r
645 isArticle = anIsArticle;
\r
646 timeStamp=aTimeStamp;
\r
649 public String getIpNumber() {
\r
653 public String getBrowserString() {
\r
654 return browserString;
\r
657 public String getId() {
\r
661 public Date getTimeStamp() {
\r
665 public boolean getIsArticle() {
\r
670 private void truncateLog() {
\r
671 synchronized(log) {
\r
675 while (log.size()>0 && log.size()>logSize) {
\r
682 private void appendLog(LogEntry anEntry) {
\r
683 synchronized (log) {
\r
691 public void logAdminUsage(EntityUsers aUser, String aDescription) {
\r
693 String user = "unknown (" + aUser.toString() +")";
\r
695 user = aUser.getValue("login");
\r
696 adminUsageLogger.info(user + ": " + aDescription);
\r
698 catch (Throwable t) {
\r
699 logger.error("Error while logging admin usage ("+aUser.toString()+", "+aDescription+"): " +t.toString());
\r