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