OSDN Git Service

Ticket処理を追加
[nucleus-jp/nucleus-plugins.git] / trunk / NP_Blacklist / NP_Blacklist.php
1 <?php
2
3 /*                                                                                */
4 /* NP_Blacklist                                                                   */
5 /* ------------------------------------                                           */
6 /* version information ----------------                                           */
7 /* 0.90 initial release                                                           */
8 /* 0.91 issues with xhtml compliance. sloppy coding removed                       */
9 /* 0.92 added user, userid and host to check for spam                             */
10 /* 0.93 bug in fetching fresh blacklist solved                                    */
11 /* 0.94 code cleanup,no more pivot specific functions and files                   */
12 /* 0.952 added the posibility to block on the referrerfield against the same lists
13          added the option to ip-ban the commenting machine (commented out! with //ip
14          remove all '//ip' if you want to checkout this functionality.
15          personally i don't like it -xiffy-
16 */
17 /* 0.95b2 removed ip-ban option.
18           added the yet non-existent event PreActioAddComment to kick in at the right moment
19           and not 'too late'. Solves emailnotification problem on adding comments
20 */
21 /* 0.95 final
22         removed the option to have a different url for referrer spamming. This will grow wild
23         if more spam-blocking types (like trackback) will be introduced.
24         So 1 url to serve them all.
25         the function blacklist is from now on the 1 function to call from other plugins
26         to call blacklist from inside your plugin add the following code:
27 --deleted obsolete call for blacklist --
28 */
29 /* 0.96 Beta
30         added ip-based blocking. This option differs from earlier attempts to add the ip to the nucleus ip-ban
31         Now, wehn a machine spam your website above the ip-block-threshold (default 10) the machine will be added
32         to the blocked ip addresses table. This way, newly undiscovered spamming domains won't be showing up
33         easily since most spamming is done by a subset of machines (zombies)
34         added menu item to maintain blocked ip-addresses.
35 */
36 /* 0.96 Beta 2
37         .htaccess snippets work. Thanks to Karma for his regexp reworke
38         there are two modes, one for blocked IP's and one for matched rules, each give a different kind of output
39         Once you've generated the rules and incorporated the finished result into your .htaccess you should Reset the file.
40         Otherwise you would end up with doubles inside your .htaccess, this should be avoided, but is completly acceptable for apache.
41 */
42 /* 0.96 Beta 3
43         Plugins calling plugins. Rakaz and I think we made it happen on a way that is future prove and a proof of concept for
44         other plugin writers. This plugin listens to the event SpamCheck, which is unknown inside nucleus-core.
45         NP_MailToAFriend, NP_Trackback and Referrer2 call this plugin if it is installed. It handles redirection itself.
46         The easy way:
47 // check for spam attempts, you never knnow !
48     $spamcheck = array ('type'  => 'MailtoaFriend',
49                         'data'  => $extra."\n".$toEmail."\n".$fromEmail);
50     $manager->notify('SpamCheck', array ('spamcheck' => & $spamcheck));
51 // done
52         The hard way and Total Control!
53     $spamcheck = array (
54         'type'  => 'Referer',
55         'data'  => 'data that needs to be checked',
56         'return'  => true
57     );
58
59     $manager->notify('SpamCheck',
60         array ('spamcheck' => & $spamcheck)
61     );
62
63     if (isset($spamcheck['result']) &&
64         $spamcheck['result'] == true)
65     {
66         // Handle spam
67     }
68 */
69 /* 0.97 Added eventHandler for the new ValidateForm event (nucleus 3.2)
70 */
71 /* 0.98 Solved naar.be bug
72 */
73
74 include_once(dirname(__FILE__)."/blacklist/blacklist_lib.php");
75
76 class NP_Blacklist extends NucleusPlugin {
77         function getName()                { return 'Blacklist'; }
78         function getAuthor()      { return 'xiffy + cles'; }
79         function getURL()                 { return 'http://blog.cles.jp/np_cles/category/31/subcatid/11'; }
80         function getVersion()     { return '0.98 jp9'; }
81         function getDescription() { return 'Blacklist for commentspammers (SpamCheck API 2.0 compatible)';      }
82         function supportsFeature($what) {
83                 switch($what) {
84                     case 'SqlTablePrefix':
85                                 return 1;
86                         default:
87                                 return 0;
88                 }
89         }
90
91     function install() {
92         // create some options
93         $this->createOption('enabled','Blacklist engine enabled?','yesno','yes');
94         $this->createOption('redirect','To which URL should spammers be redireted?','text','');
95 //        $this->createOption('update','From which URL should we get a fresh blacklist copy?', 'text','');
96         $this->createOption('referrerblock','Enable referrer based blocking?','yesno','no');
97         $this->createOption('ipblock','Enable ip based blocking?','yesno','yes');
98         $this->createOption('ipthreshold','','text','10');
99                 $this->createOption('BulkfeedsKey', 'Bulkfeeds API Key', 'text', '');
100                 $this->createOption('SkipNameResolve', 'Skip reverse DNS lookup ?', 'yesno','yes');
101                 
102                 $this->_initSettings();
103     }
104
105         function unInstall() {}
106
107     function getPluginOption ($name) {
108         return $this->getOption($name);
109     }
110
111         function getEventList() {
112                 $this->_initSettings();
113                 return array('QuickMenu','PreAddComment','PreSkinParse','ValidateForm', 'SpamCheck');
114         }
115
116         function hasAdminArea() {
117                 return 1;
118         }
119         
120         function init(){
121                 $this->resultCache = false;
122         }
123
124         function event_QuickMenu(&$data) {
125                 global $member, $nucleus, $blogid;
126                 // only show to admins
127                 if (preg_match("/MD$/", $nucleus['version'])) {
128                         $isblogadmin = $member->isBlogAdmin(-1);
129                 } else {
130                         $isblogadmin = $member->isBlogAdmin($blogid);
131                 }
132                 if (!($member->isLoggedIn() && ($member->isAdmin() | $isblogadmin))) return;
133                 array_push(
134                         $data['options'],
135                         array(
136                                 'title' => 'Blacklist',
137                                 'url' => $this->getAdminURL(),
138                                 'tooltip' => 'Manage your blacklist'
139                         )
140                 );
141         }
142
143     // for other plugin writers ...
144     function event_SpamCheck (&$data) {
145         global $DIR_PLUGINS;
146 //        $fp  = fopen ($DIR_PLUGINS."blacklist/settings/debug.txt", 'a');
147 //        fwrite($fp,"==called ==\n");
148 //        fwrite($fp,'type : ' .$data['spamcheck']['type']."\n");
149 //        fwrite($fp,'data : ' .$data['spamcheck']['data']."\n");
150 //        fclose($fp);
151         if (isset($data['spamcheck']['result']) && $data['spamcheck']['result'] == true){
152             // Already checked... and is spam
153             return;
154         }
155                 
156                 if( ! isset($data['spamcheck']['return']) ){
157                         $data['spamcheck']['return'] = true;
158                 }
159                 
160                 // for SpamCheck API 2.0 compatibility
161                 if( ! $data['spamcheck']['data'] ){
162                         switch( strtolower($data['spamcheck']['type']) ){
163                                 case 'comment':
164                                         $data['spamcheck']['data']  = $data['spamcheck']['body'] . "\n";
165                                         $data['spamcheck']['data'] .= $data['spamcheck']['author'] . "\n";
166                                         $data['spamcheck']['data'] .= $data['spamcheck']['url'] . "\n"; 
167                                         break;
168                                 case 'trackback':
169                                         $data['spamcheck']['data']  = $data['spamcheck']['title']. "\n"; 
170                                         $data['spamcheck']['data'] .= $data['spamcheck']['excerpt']. "\n";
171                                         $data['spamcheck']['data'] .= $data['spamcheck']['blogname']. "\n";
172                                         $data['spamcheck']['data'] .= $data['spamcheck']['url'];
173                                         break;
174                                 case 'referer':
175                                         $data['spamcheck']['data'] = $data['spamcheck']['url'];
176                                         break;
177                         }
178                 }
179                 $ipblock = ( $data['spamcheck']['ipblock'] ) || ($data['spamcheck']['live']);
180                 
181         // Check for spam
182         $result = $this->blacklist($data['spamcheck']['type'], $data['spamcheck']['data'], $ipblock);
183
184         if ($result) {
185             // Spam found
186             // logging !
187             pbl_logspammer($data['spamcheck']['type'].': '.$result);
188             if  (isset($data['spamcheck']['return']) && $data['spamcheck']['return'] == true) {
189                 // Return to caller
190                 $data['spamcheck']['result'] = true;
191                 return;
192             } else {
193                 $this->_redirect($this->getOption('redirect'));
194             }
195         }
196     }
197
198     // will become obsolete when nucleus is patched ...
199         function event_PreAddComment(&$data) {
200             $comment = $data['comment'];
201                 $result = $this->blacklist('comment',postVar('body')."\n".$comment['host']."\n".$comment['user']."\n".$comment['userid']);
202         if ($result) {
203             pbl_logspammer('comment: '.$result);
204             $this->_redirect($this->getOption('redirect'));
205         }
206     }
207
208         function event_ValidateForm(&$data) {
209                 if( $data['type'] == 'comment' ){
210                     $comment = $data['comment'];
211                         $result = $this->blacklist('comment',postVar('body')."\n".$comment['host']."\n".$comment['user']."\n".$comment['userid']);
212                 if ($result) {
213                     pbl_logspammer('comment: '.$result);
214                     $this->_redirect($this->getOption('redirect'));
215                 }
216                 } else if( $data['type'] == 'membermail' ){
217                         $result = $this->blacklist('membermail',postVar('frommail')."\n".postVar('message'));
218                         if ($result) {
219                                 pbl_logspammer('membermail: '.$result);
220                                 $this->_redirect($this->getOption('redirect'));
221                         }
222                 }
223     }
224
225         // preskinparse will check the referrer for spamming attempts
226         // only when option enabled !
227         // logging also only when option enabled ...
228         function event_PreSkinParse(&$data) {
229         $result = $this->blacklist('PreSkinParse','');
230         if ($result) {
231             pbl_logspammer('PreSkinParse: '.$result);
232             $this->_redirect($this->getOption('redirect'));
233         }
234         }
235
236         function blacklist($type, $testString, $ipblock = true) {
237         global $DIR_PLUGINS;
238                 if( $this->resultCache )
239                         return $this->resultCache . '[Cached]';
240                 
241             if ($this->getOption('enabled') == 'yes') {
242             // update the blacklist first file
243             //pbl_updateblacklist($this->getOption('update'),false);
244             if ($ipblock) {
245                 $ipblock = ( $this->getOption('ipblock') == 'yes' ) ? true : false ;
246             }
247                         
248                         $result = '';
249             if ($this->getOption('referrerblock') == 'yes')  {
250                                 $refer = parse_url(serverVar('HTTP_REFERER'));
251                 $result = pbl_checkforspam($refer['host']."\n".$testString, $ipblock , $this->getOption('ipthreshold'), true);
252             } elseif ($ipblock || $testString != '') {
253                 $result = pbl_checkforspam($testString, $ipblock, $this->getOption('ipthreshold'), true);
254             }
255                         
256                         if( $result ){
257                                 $this->resultCache = $result;
258                         }
259                         
260                         return $result;
261         }
262     }
263         
264         function submitSpamToBulkfeeds($url) {
265                 if( is_array($url) ) $url = implode("\n", $url);
266                 
267                 $postData['apikey'] = $this->getOption('BulkfeedsKey');
268                 if( ! $postData['apikey'] ) return "BulkfeedsKey not found. see http://bulkfeeds.net/app/register_api.html";
269                 $postData['url'] = $url;
270                 
271                 $data = $this->_http('http://bulkfeeds.net:80/app/submit_spam.xml', 'POST', '', $postData);
272                 //preg_match('#<result>([^<]*)</result>#mi', $data, $matches);
273                 //$result = trim($matches[1]);
274                 
275                 return $data;
276         }
277         
278         function _http($url, $method = "GET", $headers = "", $post = array ("")) {
279                 $URL = parse_url($url);
280
281                 if (isset ($URL['query'])) {
282                         $URL['query'] = "?".$URL['query'];
283                 } else {
284                         $URL['query'] = "";
285                 }
286
287                 if (!isset ($URL['port']))
288                         $URL['port'] = 80;
289
290                 $request = $method." ".$URL['path'].$URL['query']." HTTP/1.0\r\n";
291
292                 $request .= "Host: ".$URL['host']."\r\n";
293                 $request .= "User-Agent: PHP/".phpversion()."\r\n";
294
295                 if (isset ($URL['user']) && isset ($URL['pass'])) {
296                         $request .= "Authorization: Basic ".base64_encode($URL['user'].":".$URL['pass'])."\r\n";
297                 }
298
299                 $request .= $headers;
300
301                 if (strtoupper($method) == "POST") {
302                         while (list ($name, $value) = each($post)) {
303                                 $POST[] = $name."=".urlencode($value);
304                         }
305                         $postdata = implode("&", $POST);
306                         $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
307                         $request .= "Content-Length: ".strlen($postdata)."\r\n";
308                         $request .= "\r\n";
309                         $request .= $postdata;
310                 } else {
311                         $request .= "\r\n";
312                 }
313
314                 $fp = fsockopen($URL['host'], $URL['port'], $errno, $errstr, 20);
315
316                 if ($fp) {
317                         socket_set_timeout($fp, 20);
318                         fputs($fp, $request);
319                         $response = "";
320                         while (!feof($fp)) {
321                                 $response .= fgets($fp, 4096);
322                         }
323                         fclose($fp);
324                         $DATA = split("\r\n\r\n", $response, 2);
325                         return $DATA[1];
326                 } else {
327                         $host = $URL['host'];
328                         $port = $URL['port'];
329                         ACTIONLOG :: add(WARNING, $this->getName().':'."[$errno]($host:$port) $errstr");
330                         return "";
331                 }
332         }
333
334         function _spamMark($word){
335                 $_GET["expression"] = preg_quote($word, '/');
336                 $_GET["comment"] = 'SpamMark [' . date("Y/m/d H:i:s") . ']';
337
338                 $existTest = pbl_checkforspam(getVar("expression"));
339                 if (! (strlen($existTest) > 0))  {
340                         pbl_addexpression();
341                 }
342         }
343         
344         function _redirect($url) {
345                 if( !$url ){
346                         header("HTTP/1.0 403 Forbidden");
347                         header("Status: 403 Forbidden");
348                         
349                         include(dirname(__FILE__).'/blacklist/blocked.txt');
350                 } else {
351                         $url = preg_replace('|[^a-z0-9-~+_.?#=&;,/:@%]|i', '', $url);
352                         header('Location: ' . $url);
353                 }
354                 exit;
355         }
356         
357         function _initSettings(){
358                 $settingsDir = dirname(__FILE__).'/blacklist/settings/';
359                 $settings = array(
360                         'blacklist.log',
361                         'blockip.pbl',
362                         'matched.pbl',
363                         'blacklist.pbl',
364                         'blacklist.txt',
365                         'suspects.pbl',
366                 );
367                 $personalBlacklist = $settingsDir . 'personal_blacklist.pbl';
368                 $personalBlacklistDist = $settingsDir . 'personal_blacklist.pbl.dist';
369
370                 // setup settings
371                 if( $this->_is_writable($settingsDir) ){
372                         foreach($settings as $setting ){
373                                 touch($settingsDir.$setting);
374                         }
375                         // setup personal blacklist
376                         if( ! file_exists($personalBlacklist) ){
377                                 if( copy( $personalBlacklistDist , $personalBlacklist ) ){
378                                         $this->_warn("'$personalBlacklist' created.");
379                                 } else {
380                                         $this->_warn("'$personalBlacklist' cannot create.");
381                                 }
382                         }
383                 }
384         
385                 // check settings       
386                 foreach($settings as $setting ){
387                         $this->_is_writable($settingsDir.$setting);
388                 }                       
389                 $this->_is_writable($personalBlacklist);
390                 
391                 // setup and check cache dir
392                 $cacheDir = NP_BLACKLIST_CACHE_DIR;
393                 $this->_is_writable($cacheDir);
394         }
395         
396         function _is_writable($file){
397                 $ret = is_writable($file);
398                 if( ! $ret ){
399                         $this->_warn("'$file' is not writable.");
400                 }
401                 return $ret;
402         }
403         
404         function _warn($msg) {
405                 ACTIONLOG :: add(WARNING, 'Blacklist: '.$msg);
406         }
407         
408 }
409 ?>