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