debug code added
[mir.git] / source / mircoders / global / Abuse.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
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.
10  *
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.
15  *
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
19  *
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.
29  */
30
31 package mircoders.global;
32
33 import gnu.regexp.RE;
34
35 import java.io.File;
36 import java.io.FileNotFoundException;
37 import java.io.FileOutputStream;
38 import java.util.Arrays;
39 import java.util.Date;
40 import java.util.HashMap;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Random;
45 import java.util.Vector;
46
47 import javax.servlet.http.Cookie;
48 import javax.servlet.http.HttpServletRequest;
49 import javax.servlet.http.HttpServletResponse;
50
51 import mir.entity.Entity;
52 import mir.log.LoggerWrapper;
53 import mir.session.HTTPAdapters;
54 import mir.session.Request;
55 import mir.util.DateToMapAdapter;
56 import mir.util.InternetFunctions;
57 import mir.util.StringRoutines;
58 import mircoders.entity.EntityComment;
59 import mircoders.entity.EntityContent;
60 import mircoders.localizer.MirAdminInterfaceLocalizer;
61
62 import org.apache.commons.collections.ExtendedProperties;
63
64
65 public class Abuse {
66   private List filters;
67   private int maxIdentifier;
68   private LoggerWrapper logger;
69   private int logSize;
70   private boolean logEnabled;
71   private boolean openPostingDisabled;
72   private boolean openPostingPassword;
73   private boolean cookieOnBlock;
74   private String articleBlockAction;
75   private String commentBlockAction;
76   private List log;
77   private String configFile = MirGlobal.config().getStringWithHome("Abuse.Config");
78
79
80   private static final String IP_FILTER_TYPE="ip";
81   private static final String REGEXP_FILTER_TYPE="regexp";
82   private static String cookieName=MirGlobal.config().getString("Abuse.CookieName");
83   private static int cookieMaxAge = 60*60*MirGlobal.config().getInt("Abuse.CookieMaxAge");
84
85   public Abuse() {
86     logger = new LoggerWrapper("Global.Abuse");
87     filters = new Vector();
88     maxIdentifier = 0;
89     log = new Vector();
90
91     logSize = 100;
92     logEnabled = false;
93     articleBlockAction = "";
94     commentBlockAction = "";
95     openPostingPassword = false;
96     openPostingDisabled = false;
97     cookieOnBlock = false;
98
99     load();
100   }
101
102   public boolean checkIpFilter(String anIpAddress) {
103     synchronized (filters) {
104       Iterator i = filters.iterator();
105
106       while (i.hasNext()) {
107         Filter filter = (Filter) i.next();
108
109         try {
110           if ( (filter.getType().equals(IP_FILTER_TYPE)) &&
111               InternetFunctions.isIpAddressInNetwork(anIpAddress, filter.getExpression())) {
112             logger.debug("ip match on " + filter.getExpression());
113             return true;
114           }
115         }
116         catch (Throwable t) {
117           logger.warn("error while checking ip address " + anIpAddress + " over network " + filter.expression + ": " + t.getMessage());
118         }
119       }
120
121       return false;
122     }
123   }
124
125   private boolean checkRegExpFilter(Entity anEntity) {
126     synchronized (filters) {
127       Iterator i = filters.iterator();
128
129       while (i.hasNext()) {
130         Filter filter = (Filter) i.next();
131
132         if (filter.getType().equals(REGEXP_FILTER_TYPE)) {
133           try {
134             RE regularExpression = new RE(filter.getExpression());
135
136             Iterator j = anEntity.getFields().iterator();
137             while (j.hasNext()) {
138               String field = anEntity.getValue( (String) j.next());
139
140               if (field != null && regularExpression.isMatch(field.toLowerCase())) {
141                 logger.debug("regexp match on " + filter.getExpression());
142                 return true;
143               }
144             }
145           }
146           catch (Throwable t) {
147             logger.warn("error while checking entity with regexp " + filter.getExpression() + ": " + t.getMessage());
148           }
149         }
150       }
151
152       return false;
153     }
154   }
155
156   private void setCookie(HttpServletResponse aResponse) {
157     Random random = new Random();
158
159     Cookie cookie = new Cookie(cookieName, Integer.toString(random.nextInt(1000000000)));
160     cookie.setMaxAge(cookieMaxAge);
161     cookie.setPath("/");
162
163     if (aResponse!=null)
164       aResponse.addCookie(cookie);
165   }
166
167   private boolean checkCookie(List aCookies) {
168     if (getCookieOnBlock()) {
169       Iterator i = aCookies.iterator();
170
171       while (i.hasNext()) {
172         Cookie cookie = (Cookie) i.next();
173
174         if (cookie.getName().equals(cookieName)) {
175           logger.debug("cookie match");
176           return true;
177         }
178       }
179     }
180
181     return false;
182   }
183
184   public boolean checkRequest(Request aRequest, HttpServletResponse aResponse, String anId, boolean anIsComment) {
185     String address = "0.0.0.0";
186     String browser = "unknown";
187     List cookies = null;
188
189     HttpServletRequest request = null;
190
191     if (aRequest instanceof HTTPAdapters.HTTPParsedRequestAdapter) {
192       request = ((HTTPAdapters.HTTPParsedRequestAdapter) aRequest).getRequest();
193     }
194     else if (aRequest instanceof HTTPAdapters.HTTPRequestAdapter) {
195       request = ((HTTPAdapters.HTTPRequestAdapter) aRequest).getRequest();
196     }
197     if (request!=null) {
198       browser = (String) request.getHeader("User-Agent");
199       address = request.getRemoteAddr();
200       cookies = Arrays.asList(request.getCookies());
201     }
202
203     if (anIsComment)
204       logComment(address, anId , new Date(), browser);
205     else
206       logArticle(address, anId , new Date(), browser);
207
208     return checkCookie(cookies) || checkIpFilter(address);
209   }
210
211   public void checkComment(EntityComment aComment, Request aRequest, HttpServletResponse aResponse) {
212     try {
213       long time = System.currentTimeMillis();
214
215       MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation = MirGlobal.localizer().adminInterface().simpleCommentOperationForName(commentBlockAction);
216
217       if (checkRequest(aRequest, aResponse, aComment.getId(), true) || checkRegExpFilter(aComment)) {
218         logger.debug("performing operation " + operation.getName());
219         operation.perform(null, MirGlobal.localizer().dataModel().adapterModel().makeEntityAdapter("comment", aComment));
220         setCookie(aResponse);
221       }
222
223       logger.info("checkComment: " + (System.currentTimeMillis()-time) + "ms");
224     }
225     catch (Throwable t) {
226       t.printStackTrace(logger.asPrintWriter(logger.DEBUG_MESSAGE));
227       logger.error("Abuse.checkComment: " + t.toString());
228     }
229   }
230
231   public void checkArticle(EntityContent anArticle, Request aRequest, HttpServletResponse aResponse) {
232     try {
233       long time = System.currentTimeMillis();
234
235       MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation = MirGlobal.localizer().adminInterface().simpleArticleOperationForName(commentBlockAction);
236
237       if (checkRequest(aRequest, aResponse, anArticle.getId(), false) || checkRegExpFilter(anArticle)) {
238         logger.debug("performing operation " + operation.getName());
239         operation.perform(null, MirGlobal.localizer().dataModel().adapterModel().makeEntityAdapter("content", anArticle));
240         setCookie(aResponse);
241       }
242
243       logger.info("checkArticle: " + (System.currentTimeMillis()-time) + "ms");
244     }
245     catch (Throwable t) {
246       t.printStackTrace(logger.asPrintWriter(logger.DEBUG_MESSAGE));
247       logger.error("Abuse.checkArticle: " + t.toString());
248     }
249   }
250
251   public boolean getLogEnabled() {
252     return logEnabled;
253   }
254
255   public void setLogEnabled(boolean anEnabled) {
256     logEnabled = anEnabled;
257     truncateLog();
258   }
259
260   public int getLogSize() {
261     return logSize;
262   }
263
264   public void setLogSize(int aSize) {
265     logSize = aSize;
266     truncateLog();
267   }
268
269   public boolean getOpenPostingDisabled() {
270     return openPostingDisabled;
271   }
272
273   public void setOpenPostingDisabled(boolean anOpenPostingDisabled) {
274     openPostingDisabled = anOpenPostingDisabled;
275   }
276
277   public boolean getOpenPostingPassword() {
278     return openPostingPassword;
279   }
280
281   public void setOpenPostingPassword(boolean anOpenPostingPassword) {
282     openPostingPassword = anOpenPostingPassword;
283   }
284
285   public boolean getCookieOnBlock() {
286     return cookieOnBlock;
287   }
288
289   public void setCookieOnBlock(boolean aCookieOnBlock) {
290     cookieOnBlock = aCookieOnBlock;
291   }
292
293   public String getArticleBlockAction() {
294     return articleBlockAction;
295   }
296
297   public void setArticleBlockAction(String anAction) {
298     articleBlockAction = anAction;
299   }
300
301   public String getCommentBlockAction() {
302     return commentBlockAction;
303   }
304
305   public void setCommentBlockAction(String anAction) {
306     commentBlockAction = anAction;
307   }
308
309
310   public List getLog() {
311     synchronized(log) {
312       List result = new Vector();
313
314       Iterator i = log.iterator();
315       while (i.hasNext()) {
316         LogEntry logEntry = (LogEntry) i.next();
317         Map entry = new HashMap();
318
319         entry.put("ip", logEntry.getIpNumber());
320         entry.put("id", logEntry.getId());
321         entry.put("timestamp", new DateToMapAdapter(logEntry.getTimeStamp()));
322         if (logEntry.getIsArticle())
323           entry.put("type", "content");
324         else
325           entry.put("type", "comment");
326         entry.put("browser", logEntry.getBrowserString());
327
328         result.add(entry);
329       }
330
331       return result;
332     }
333   }
334
335   public void logComment(String anIp, String anId, Date aTimeStamp, String aBrowser) {
336     appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, false));
337   }
338
339   public void logArticle(String anIp, String anId, Date aTimeStamp, String aBrowser) {
340     appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, true));
341   }
342
343   public void load() {
344     try {
345       ExtendedProperties configuration = new ExtendedProperties();
346
347       try {
348         configuration = new ExtendedProperties(configFile);
349       }
350       catch (FileNotFoundException e) {
351       }
352
353       getFilterConfig(filters, "abuse.filter", configuration);
354
355       setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));
356       setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));
357       setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));
358       setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));
359       setLogSize(configuration.getInt("abuse.logSize", 10));
360       setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));
361       setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));
362     }
363     catch (Throwable t) {
364       throw new RuntimeException(t.toString());
365     }
366   }
367   public void save() {
368     try {
369       ExtendedProperties configuration = new ExtendedProperties();
370
371       setFilterConfig(filters, "abuse.filter", configuration);
372
373       configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled()?"1":"0");
374       configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword()?"1":"0");
375       configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock()?"1":"0");
376       configuration.addProperty("abuse.logEnabled", getLogEnabled()?"1":"0");
377       configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));
378       configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());
379       configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());
380
381       configuration.save(new FileOutputStream(new File(configFile)), "Anti abuse configuration");
382     }
383     catch (Throwable t) {
384       throw new RuntimeException(t.toString());
385     }
386   }
387
388   public List getFilterTypes() {
389     List result = new Vector();
390
391     Map entry = new HashMap();
392     entry.put("resource", "ip");
393     entry.put("id", IP_FILTER_TYPE);
394     result.add(entry);
395
396     entry = new HashMap();
397     entry.put("resource", "regexp");
398     entry.put("id", REGEXP_FILTER_TYPE);
399     result.add(entry);
400
401     return result;
402   }
403
404   public List getArticleActions() {
405     try {
406       List result = new Vector();
407
408       Iterator i = MirGlobal.localizer().adminInterface().simpleArticleOperations().iterator();
409       while (i.hasNext()) {
410         MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
411             (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
412
413         Map action = new HashMap();
414         action.put("resource", operation.getName());
415         action.put("identifier", operation.getName());
416
417         result.add(action);
418       }
419
420       return result;
421     }
422     catch (Throwable t) {
423       throw new RuntimeException("can't get article actions");
424     }
425   }
426
427   public List getCommentActions() {
428     try {
429       List result = new Vector();
430
431       Iterator i = MirGlobal.localizer().adminInterface().simpleCommentOperations().iterator();
432       while (i.hasNext()) {
433         MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
434             (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
435
436         Map action = new HashMap();
437         action.put("resource", operation.getName());
438         action.put("identifier", operation.getName());
439
440         result.add(action);
441       }
442
443       return result;
444     }
445     catch (Throwable t) {
446       throw new RuntimeException("can't get comment actions");
447     }
448   }
449
450   public List getFilters() {
451     return getFiltersAsMaps(filters);
452   }
453
454   public void addFilter(String aType, String anExpression) {
455     addFilter(filters, aType, anExpression);
456   }
457
458   public void setFilter(String anIdentifier, String aType, String anExpression) {
459     setFilter(filters, anIdentifier, aType, anExpression);
460   }
461
462   public void deleteFilter(String anIdentifier) {
463     deleteFilter(filters, anIdentifier);
464   }
465
466   public void validateIpFilter(String anIdentifier, String anArticleAction, String aCommentAction) throws Exception {
467   }
468
469   private List getFiltersAsMaps(List aFilters) {
470     synchronized(aFilters) {
471       List result = new Vector();
472
473       Iterator i = aFilters.iterator();
474       while (i.hasNext()) {
475         Filter filter = (Filter) i.next();
476         Map map = new HashMap();
477
478         map.put("id", filter.getId());
479         map.put("expression", filter.getExpression());
480         map.put("type", filter.getType());
481
482         result.add(map);
483       }
484       return result;
485     }
486   }
487
488   private void addFilter(List aFilters, String aType, String anExpression) {
489     Filter filter = new Filter();
490
491     filter.setId(generateId());
492     filter.setExpression(anExpression);
493     filter.setType(aType);
494
495     synchronized (aFilters) {
496       aFilters.add(filter);
497     }
498   }
499
500   private void setFilter(List aFilters, String anIdentifier, String aType, String anExpression) {
501     synchronized (aFilters) {
502       Filter filter = findFilter(aFilters, anIdentifier);
503
504       if (filter!=null) {
505         filter.setExpression(anExpression);
506         filter.setType(aType);
507       }
508     }
509   }
510
511   private Filter findFilter(List aFilters, String anIdentifier) {
512     synchronized (aFilters) {
513       Iterator i = aFilters.iterator();
514       while (i.hasNext()) {
515         Filter filter = (Filter) i.next();
516
517         if (filter.getId().equals(anIdentifier)) {
518           return filter;
519         }
520       }
521     }
522
523     return null;
524   }
525
526   private void deleteFilter(List aFilters, String anIdentifier) {
527     synchronized (aFilters) {
528       Filter filter = findFilter(aFilters, anIdentifier);
529
530       if (filter!=null) {
531         aFilters.remove(filter);
532       }
533     }
534   }
535
536   private String generateId() {
537     synchronized(this) {
538       maxIdentifier = maxIdentifier+1;
539
540       return Integer.toString(maxIdentifier);
541     }
542   }
543
544   private static class Filter {
545     private String identifier;
546     private String expression;
547     private String type;
548
549     public Filter() {
550       expression="";
551       type="";
552       identifier="";
553     }
554
555     public String getId() {
556       return identifier;
557     }
558
559     public void setId(String anId) {
560       identifier = anId;
561     }
562
563     public String getExpression() {
564       return expression;
565     }
566
567     public void setExpression(String anExpression) {
568       expression = anExpression;
569     }
570
571     public String getType() {
572       return type;
573     }
574
575     public void setType(String aType) {
576       type = aType;
577     }
578   }
579
580   private void setFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {
581     synchronized(aFilters) {
582       Iterator i = aFilters.iterator();
583
584       while (i.hasNext()) {
585         Filter filter = (Filter) i.next();
586
587         aConfiguration.addProperty(aConfigKey, filter.getType()+":"+filter.getExpression());
588       }
589     }
590   }
591
592   private void getFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {
593     synchronized(aFilters) {
594       aFilters.clear();
595
596       Iterator i = Arrays.asList(aConfiguration.getStringArray(aConfigKey)).iterator();
597
598       while (i.hasNext()) {
599         String filter = (String) i.next();
600         List parts = StringRoutines.separateString(filter, ":");
601
602         if (parts.size()==2) {
603           addFilter( (String) parts.get(0), (String) parts.get(1));
604         }
605       }
606     }
607   }
608
609   private static class LogEntry {
610     private String ipNumber;
611     private String browserString;
612     private String id;
613     private Date timeStamp;
614     private boolean isArticle;
615
616     public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {
617       ipNumber = anIpNumber;
618       browserString = aBrowserString;
619       id = anId;
620       isArticle = anIsArticle;
621       timeStamp=aTimeStamp;
622     }
623
624     public String getIpNumber() {
625       return ipNumber;
626     }
627
628     public String getBrowserString() {
629       return browserString;
630     }
631
632     public String getId() {
633       return id;
634     }
635
636     public Date getTimeStamp() {
637       return timeStamp;
638     }
639
640     public boolean getIsArticle() {
641       return isArticle;
642     }
643   }
644
645   private void truncateLog() {
646     synchronized(log) {
647       if (!logEnabled)
648         log.clear();
649       else {
650         while (log.size()>0 && log.size()>logSize) {
651           log.remove(0);
652         }
653       }
654     }
655   };
656
657   private void appendLog(LogEntry anEntry) {
658     synchronized (log) {
659       if (logEnabled) {
660         log.add(anEntry);
661         truncateLog();
662       }
663     }
664   }
665
666 }