Organizing import, refresh the license (zapata deleted cos.jar)
[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 void checkComment(EntityComment aComment, Request aRequest, HttpServletResponse aResponse) {
185     try {
186       long time = System.currentTimeMillis();
187       String address = "0.0.0.0";
188       String browser = "unknown";
189       Cookie[] cookies = {};
190
191       HttpServletRequest request = null;
192
193       if (aRequest instanceof HTTPAdapters.HTTPParsedRequestAdapter) {
194         request = ((HTTPAdapters.HTTPParsedRequestAdapter) aRequest).getRequest();
195       }
196       else if (aRequest instanceof HTTPAdapters.HTTPRequestAdapter) {
197         request = ((HTTPAdapters.HTTPRequestAdapter) aRequest).getRequest();
198       }
199       if (request!=null) {
200         browser = (String) request.getHeader("User-Agent");
201         address = request.getRemoteAddr();
202         cookies = request.getCookies();
203       }
204
205       logComment(address, aComment.getId(), new Date(), browser);
206
207       MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation = MirGlobal.localizer().adminInterface().simpleCommentOperationForName(commentBlockAction);
208
209       if (checkCookie(Arrays.asList(cookies)) || checkIpFilter(address) || checkRegExpFilter(aComment)) {
210         operation.perform(null, MirGlobal.localizer().dataModel().adapterModel().makeEntityAdapter("comment", aComment));
211         setCookie(aResponse);
212       }
213
214       logger.info("checkComment: " + (System.currentTimeMillis()-time) + "ms");
215     }
216     catch (Throwable t) {
217       logger.error("Abuse.checkComment: " + t.toString());
218     }
219   }
220
221   public void checkArticle(EntityContent anArticle, HttpServletRequest aRequest, HttpServletResponse aResponse) {
222     try {
223       long time = System.currentTimeMillis();
224
225       logArticle(aRequest.getRemoteAddr(), anArticle.getId(), new Date(), (String) aRequest.getHeader("User-Agent"));
226
227       MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation = MirGlobal.localizer().adminInterface().simpleCommentOperationForName(commentBlockAction);
228
229       if (checkCookie(Arrays.asList(aRequest.getCookies())) || checkIpFilter(aRequest.getRemoteAddr()) || checkRegExpFilter(anArticle)) {
230         operation.perform(null, MirGlobal.localizer().dataModel().adapterModel().makeEntityAdapter("content", anArticle));
231         setCookie(aResponse);
232       }
233
234       logger.info("checkArticle: " + (System.currentTimeMillis()-time) + "ms");
235     }
236     catch (Throwable t) {
237       logger.error("Abuse.checkArticle: " + t.toString());
238     }
239   }
240
241   public boolean getLogEnabled() {
242     return logEnabled;
243   }
244
245   public void setLogEnabled(boolean anEnabled) {
246     logEnabled = anEnabled;
247     truncateLog();
248   }
249
250   public int getLogSize() {
251     return logSize;
252   }
253
254   public void setLogSize(int aSize) {
255     logSize = aSize;
256     truncateLog();
257   }
258
259   public boolean getOpenPostingDisabled() {
260     return openPostingDisabled;
261   }
262
263   public void setOpenPostingDisabled(boolean anOpenPostingDisabled) {
264     openPostingDisabled = anOpenPostingDisabled;
265   }
266
267   public boolean getOpenPostingPassword() {
268     return openPostingPassword;
269   }
270
271   public void setOpenPostingPassword(boolean anOpenPostingPassword) {
272     openPostingPassword = anOpenPostingPassword;
273   }
274
275   public boolean getCookieOnBlock() {
276     return cookieOnBlock;
277   }
278
279   public void setCookieOnBlock(boolean aCookieOnBlock) {
280     cookieOnBlock = aCookieOnBlock;
281   }
282
283   public String getArticleBlockAction() {
284     return articleBlockAction;
285   }
286
287   public void setArticleBlockAction(String anAction) {
288     articleBlockAction = anAction;
289   }
290
291   public String getCommentBlockAction() {
292     return commentBlockAction;
293   }
294
295   public void setCommentBlockAction(String anAction) {
296     commentBlockAction = anAction;
297   }
298
299
300   public List getLog() {
301     synchronized(log) {
302       List result = new Vector();
303
304       Iterator i = log.iterator();
305       while (i.hasNext()) {
306         LogEntry logEntry = (LogEntry) i.next();
307         Map entry = new HashMap();
308
309         entry.put("ip", logEntry.getIpNumber());
310         entry.put("id", logEntry.getId());
311         entry.put("timestamp", new DateToMapAdapter(logEntry.getTimeStamp()));
312         if (logEntry.getIsArticle())
313           entry.put("type", "content");
314         else
315           entry.put("type", "comment");
316         entry.put("browser", logEntry.getBrowserString());
317
318         result.add(entry);
319       }
320
321       return result;
322     }
323   }
324
325   public void logComment(String anIp, String anId, Date aTimeStamp, String aBrowser) {
326     appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, false));
327   }
328
329   public void logArticle(String anIp, String anId, Date aTimeStamp, String aBrowser) {
330     appendLog(new LogEntry(aTimeStamp, anIp, aBrowser, anId, true));
331   }
332
333   public void load() {
334     try {
335       ExtendedProperties configuration = new ExtendedProperties();
336
337       try {
338         configuration = new ExtendedProperties(configFile);
339       }
340       catch (FileNotFoundException e) {
341       }
342
343       getFilterConfig(filters, "abuse.filter", configuration);
344
345       setOpenPostingDisabled(configuration.getString("abuse.openPostingDisabled", "0").equals("1"));
346       setOpenPostingPassword(configuration.getString("abuse.openPostingPassword", "0").equals("1"));
347       setCookieOnBlock(configuration.getString("abuse.cookieOnBlock", "0").equals("1"));
348       setLogEnabled(configuration.getString("abuse.logEnabled", "0").equals("1"));
349       setLogSize(configuration.getInt("abuse.logSize", 10));
350       setArticleBlockAction(configuration.getString("abuse.articleBlockAction", ""));
351       setCommentBlockAction(configuration.getString("abuse.commentBlockAction", ""));
352     }
353     catch (Throwable t) {
354       throw new RuntimeException(t.toString());
355     }
356   }
357   public void save() {
358     try {
359       ExtendedProperties configuration = new ExtendedProperties();
360
361       setFilterConfig(filters, "abuse.filter", configuration);
362
363       configuration.addProperty("abuse.openPostingDisabled", getOpenPostingDisabled()?"1":"0");
364       configuration.addProperty("abuse.openPostingPassword", getOpenPostingPassword()?"1":"0");
365       configuration.addProperty("abuse.cookieOnBlock", getCookieOnBlock()?"1":"0");
366       configuration.addProperty("abuse.logEnabled", getLogEnabled()?"1":"0");
367       configuration.addProperty("abuse.logSize", Integer.toString(getLogSize()));
368       configuration.addProperty("abuse.articleBlockAction", getArticleBlockAction());
369       configuration.addProperty("abuse.commentBlockAction", getCommentBlockAction());
370
371       configuration.save(new FileOutputStream(new File(configFile)), "Anti abuse configuration");
372     }
373     catch (Throwable t) {
374       throw new RuntimeException(t.toString());
375     }
376   }
377
378   public List getFilterTypes() {
379     List result = new Vector();
380
381     Map entry = new HashMap();
382     entry.put("resource", "ip");
383     entry.put("id", IP_FILTER_TYPE);
384     result.add(entry);
385
386     entry = new HashMap();
387     entry.put("resource", "regexp");
388     entry.put("id", REGEXP_FILTER_TYPE);
389     result.add(entry);
390
391     return result;
392   }
393
394   public List getArticleActions() {
395     try {
396       List result = new Vector();
397
398       Iterator i = MirGlobal.localizer().adminInterface().simpleArticleOperations().iterator();
399       while (i.hasNext()) {
400         MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
401             (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
402
403         Map action = new HashMap();
404         action.put("resource", operation.getName());
405         action.put("identifier", operation.getName());
406
407         result.add(action);
408       }
409
410       return result;
411     }
412     catch (Throwable t) {
413       throw new RuntimeException("can't get article actions");
414     }
415   }
416
417   public List getCommentActions() {
418     try {
419       List result = new Vector();
420
421       Iterator i = MirGlobal.localizer().adminInterface().simpleCommentOperations().iterator();
422       while (i.hasNext()) {
423         MirAdminInterfaceLocalizer.MirSimpleEntityOperation operation =
424             (MirAdminInterfaceLocalizer.MirSimpleEntityOperation) i.next();
425
426         Map action = new HashMap();
427         action.put("resource", operation.getName());
428         action.put("identifier", operation.getName());
429
430         result.add(action);
431       }
432
433       return result;
434     }
435     catch (Throwable t) {
436       throw new RuntimeException("can't get comment actions");
437     }
438   }
439
440   public List getFilters() {
441     return getFiltersAsMaps(filters);
442   }
443
444   public void addFilter(String aType, String anExpression) {
445     addFilter(filters, aType, anExpression);
446   }
447
448   public void setFilter(String anIdentifier, String aType, String anExpression) {
449     setFilter(filters, anIdentifier, aType, anExpression);
450   }
451
452   public void deleteFilter(String anIdentifier) {
453     deleteFilter(filters, anIdentifier);
454   }
455
456   public void validateIpFilter(String anIdentifier, String anArticleAction, String aCommentAction) throws Exception {
457   }
458
459   private List getFiltersAsMaps(List aFilters) {
460     synchronized(aFilters) {
461       List result = new Vector();
462
463       Iterator i = aFilters.iterator();
464       while (i.hasNext()) {
465         Filter filter = (Filter) i.next();
466         Map map = new HashMap();
467
468         map.put("id", filter.getId());
469         map.put("expression", filter.getExpression());
470         map.put("type", filter.getType());
471
472         result.add(map);
473       }
474       return result;
475     }
476   }
477
478   private void addFilter(List aFilters, String aType, String anExpression) {
479     Filter filter = new Filter();
480
481     filter.setId(generateId());
482     filter.setExpression(anExpression);
483     filter.setType(aType);
484
485     synchronized (aFilters) {
486       aFilters.add(filter);
487     }
488   }
489
490   private void setFilter(List aFilters, String anIdentifier, String aType, String anExpression) {
491     synchronized (aFilters) {
492       Filter filter = findFilter(aFilters, anIdentifier);
493
494       if (filter!=null) {
495         filter.setExpression(anExpression);
496         filter.setType(aType);
497       }
498     }
499   }
500
501   private Filter findFilter(List aFilters, String anIdentifier) {
502     synchronized (aFilters) {
503       Iterator i = aFilters.iterator();
504       while (i.hasNext()) {
505         Filter filter = (Filter) i.next();
506
507         if (filter.getId().equals(anIdentifier)) {
508           return filter;
509         }
510       }
511     }
512
513     return null;
514   }
515
516   private void deleteFilter(List aFilters, String anIdentifier) {
517     synchronized (aFilters) {
518       Filter filter = findFilter(aFilters, anIdentifier);
519
520       if (filter!=null) {
521         aFilters.remove(filter);
522       }
523     }
524   }
525
526   private String generateId() {
527     synchronized(this) {
528       maxIdentifier = maxIdentifier+1;
529
530       return Integer.toString(maxIdentifier);
531     }
532   }
533
534   private static class Filter {
535     private String identifier;
536     private String expression;
537     private String type;
538
539     public Filter() {
540       expression="";
541       type="";
542       identifier="";
543     }
544
545     public String getId() {
546       return identifier;
547     }
548
549     public void setId(String anId) {
550       identifier = anId;
551     }
552
553     public String getExpression() {
554       return expression;
555     }
556
557     public void setExpression(String anExpression) {
558       expression = anExpression;
559     }
560
561     public String getType() {
562       return type;
563     }
564
565     public void setType(String aType) {
566       type = aType;
567     }
568   }
569
570   private void setFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {
571     synchronized(aFilters) {
572       Iterator i = aFilters.iterator();
573
574       while (i.hasNext()) {
575         Filter filter = (Filter) i.next();
576
577         aConfiguration.addProperty(aConfigKey, filter.getType()+":"+filter.getExpression());
578       }
579     }
580   }
581
582   private void getFilterConfig(List aFilters, String aConfigKey, ExtendedProperties aConfiguration) {
583     synchronized(aFilters) {
584       aFilters.clear();
585
586       Iterator i = Arrays.asList(aConfiguration.getStringArray(aConfigKey)).iterator();
587
588       while (i.hasNext()) {
589         String filter = (String) i.next();
590         List parts = StringRoutines.separateString(filter, ":");
591
592         if (parts.size()==2) {
593           addFilter( (String) parts.get(0), (String) parts.get(1));
594         }
595       }
596     }
597   }
598
599   private static class LogEntry {
600     private String ipNumber;
601     private String browserString;
602     private String id;
603     private Date timeStamp;
604     private boolean isArticle;
605
606     public LogEntry(Date aTimeStamp, String anIpNumber, String aBrowserString, String anId, boolean anIsArticle) {
607       ipNumber = anIpNumber;
608       browserString = aBrowserString;
609       id = anId;
610       isArticle = anIsArticle;
611       timeStamp=aTimeStamp;
612     }
613
614     public String getIpNumber() {
615       return ipNumber;
616     }
617
618     public String getBrowserString() {
619       return browserString;
620     }
621
622     public String getId() {
623       return id;
624     }
625
626     public Date getTimeStamp() {
627       return timeStamp;
628     }
629
630     public boolean getIsArticle() {
631       return isArticle;
632     }
633   }
634
635   private void truncateLog() {
636     synchronized(log) {
637       if (!logEnabled)
638         log.clear();
639       else {
640         while (log.size()>0 && log.size()>logSize) {
641           log.remove(0);
642         }
643       }
644     }
645   };
646
647   private void appendLog(LogEntry anEntry) {
648     synchronized (log) {
649       if (logEnabled) {
650         log.add(anEntry);
651         truncateLog();
652       }
653     }
654   }
655
656 }