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-
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
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 --
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.
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.
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.
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));
52 The hard way and Total Control!
55 'data' => 'data that needs to be checked',
59 $manager->notify('SpamCheck',
60 array ('spamcheck' => & $spamcheck)
63 if (isset($spamcheck['result']) &&
64 $spamcheck['result'] == true)
69 /* 0.97 Added eventHandler for the new ValidateForm event (nucleus 3.2)
71 /* 0.98 Solved naar.be bug
74 include_once(dirname(__FILE__)."/blacklist/blacklist_lib.php");
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) {
84 case 'SqlTablePrefix':
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');
102 $this->_initSettings();
105 function unInstall() {}
107 function getPluginOption ($name) {
108 return $this->getOption($name);
111 function getEventList() {
112 $this->_initSettings();
113 return array('QuickMenu','PreAddComment','PreSkinParse','ValidateForm', 'SpamCheck');
116 function hasAdminArea() {
121 $this->resultCache = false;
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);
130 $isblogadmin = $member->isBlogAdmin($blogid);
132 if (!($member->isLoggedIn() && ($member->isAdmin() | $isblogadmin))) return;
136 'title' => 'Blacklist',
137 'url' => $this->getAdminURL(),
138 'tooltip' => 'Manage your blacklist'
143 // for other plugin writers ...
144 function event_SpamCheck (&$data) {
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");
151 if (isset($data['spamcheck']['result']) && $data['spamcheck']['result'] == true){
152 // Already checked... and is spam
156 if( ! isset($data['spamcheck']['return']) ){
157 $data['spamcheck']['return'] = true;
160 // for SpamCheck API 2.0 compatibility
161 if( ! $data['spamcheck']['data'] ){
162 switch( strtolower($data['spamcheck']['type']) ){
164 $data['spamcheck']['data'] = $data['spamcheck']['body'] . "\n";
165 $data['spamcheck']['data'] .= $data['spamcheck']['author'] . "\n";
166 $data['spamcheck']['data'] .= $data['spamcheck']['url'] . "\n";
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'];
175 $data['spamcheck']['data'] = $data['spamcheck']['url'];
179 $ipblock = ( $data['spamcheck']['ipblock'] ) || ($data['spamcheck']['live']);
182 $result = $this->blacklist($data['spamcheck']['type'], $data['spamcheck']['data'], $ipblock);
187 pbl_logspammer($data['spamcheck']['type'].': '.$result);
188 if (isset($data['spamcheck']['return']) && $data['spamcheck']['return'] == true) {
190 $data['spamcheck']['result'] = true;
193 $this->_redirect($this->getOption('redirect'));
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']);
203 pbl_logspammer('comment: '.$result);
204 $this->_redirect($this->getOption('redirect'));
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']);
213 pbl_logspammer('comment: '.$result);
214 $this->_redirect($this->getOption('redirect'));
216 } else if( $data['type'] == 'membermail' ){
217 $result = $this->blacklist('membermail',postVar('frommail')."\n".postVar('message'));
219 pbl_logspammer('membermail: '.$result);
220 $this->_redirect($this->getOption('redirect'));
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','');
231 pbl_logspammer('PreSkinParse: '.$result);
232 $this->_redirect($this->getOption('redirect'));
236 function blacklist($type, $testString, $ipblock = true) {
238 if( $this->resultCache )
239 return $this->resultCache . '[Cached]';
241 if ($this->getOption('enabled') == 'yes') {
242 // update the blacklist first file
243 //pbl_updateblacklist($this->getOption('update'),false);
245 $ipblock = ( $this->getOption('ipblock') == 'yes' ) ? true : false ;
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);
257 $this->resultCache = $result;
264 function submitSpamToBulkfeeds($url) {
265 if( is_array($url) ) $url = implode("\n", $url);
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;
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]);
278 function _http($url, $method = "GET", $headers = "", $post = array ("")) {
279 $URL = parse_url($url);
281 if (isset ($URL['query'])) {
282 $URL['query'] = "?".$URL['query'];
287 if (!isset ($URL['port']))
290 $request = $method." ".$URL['path'].$URL['query']." HTTP/1.0\r\n";
292 $request .= "Host: ".$URL['host']."\r\n";
293 $request .= "User-Agent: PHP/".phpversion()."\r\n";
295 if (isset ($URL['user']) && isset ($URL['pass'])) {
296 $request .= "Authorization: Basic ".base64_encode($URL['user'].":".$URL['pass'])."\r\n";
299 $request .= $headers;
301 if (strtoupper($method) == "POST") {
302 while (list ($name, $value) = each($post)) {
303 $POST[] = $name."=".urlencode($value);
305 $postdata = implode("&", $POST);
306 $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
307 $request .= "Content-Length: ".strlen($postdata)."\r\n";
309 $request .= $postdata;
314 $fp = fsockopen($URL['host'], $URL['port'], $errno, $errstr, 20);
317 socket_set_timeout($fp, 20);
318 fputs($fp, $request);
321 $response .= fgets($fp, 4096);
324 $DATA = split("\r\n\r\n", $response, 2);
327 $host = $URL['host'];
328 $port = $URL['port'];
329 ACTIONLOG :: add(WARNING, $this->getName().':'."[$errno]($host:$port) $errstr");
334 function _spamMark($word){
335 $_GET["expression"] = preg_quote($word, '/');
336 $_GET["comment"] = 'SpamMark [' . date("Y/m/d H:i:s") . ']';
338 $existTest = pbl_checkforspam(getVar("expression"));
339 if (! (strlen($existTest) > 0)) {
344 function _redirect($url) {
346 header("HTTP/1.0 403 Forbidden");
347 header("Status: 403 Forbidden");
349 include(dirname(__FILE__).'/blacklist/blocked.txt');
351 $url = preg_replace('|[^a-z0-9-~+_.?#=&;,/:@%]|i', '', $url);
352 header('Location: ' . $url);
357 function _initSettings(){
358 $settingsDir = dirname(__FILE__).'/blacklist/settings/';
367 $personalBlacklist = $settingsDir . 'personal_blacklist.pbl';
368 $personalBlacklistDist = $settingsDir . 'personal_blacklist.pbl.dist';
371 if( $this->_is_writable($settingsDir) ){
372 foreach($settings as $setting ){
373 touch($settingsDir.$setting);
375 // setup personal blacklist
376 if( ! file_exists($personalBlacklist) ){
377 if( copy( $personalBlacklistDist , $personalBlacklist ) ){
378 $this->_warn("'$personalBlacklist' created.");
380 $this->_warn("'$personalBlacklist' cannot create.");
386 foreach($settings as $setting ){
387 $this->_is_writable($settingsDir.$setting);
389 $this->_is_writable($personalBlacklist);
391 // setup and check cache dir
392 $cacheDir = NP_BLACKLIST_CACHE_DIR;
393 $this->_is_writable($cacheDir);
396 function _is_writable($file){
397 $ret = is_writable($file);
399 $this->_warn("'$file' is not writable.");
404 function _warn($msg) {
405 ACTIONLOG :: add(WARNING, 'Blacklist: '.$msg);