title = PLUGIN_EVENT_SPAMBLOCK_TITLE; $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_TITLE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_DESC); $propbag->add('stackable', false); $propbag->add('author', 'Garvin Hicking, Sebastian Nohn, Grischa Brockhaus, Ian'); $propbag->add('requirements', array( 'serendipity' => '0.8', 'smarty' => '2.6.7', 'php' => '4.1.0' )); $propbag->add('version', '1.83'); $propbag->add('event_hooks', array( 'frontend_saveComment' => true, 'external_plugin' => true, 'frontend_comment' => true, 'fetchcomments' => true, 'backend_comments_top' => true, 'backend_view_comment' => true )); $propbag->add('configuration', array( 'killswitch', 'hide_for_authors', 'bodyclone', 'entrytitle', 'ipflood', 'csrf', 'captchas', 'captchas_ttl', 'captcha_color', 'forcemoderation', 'forcemoderation_treat', 'trackback_ipvalidation' , 'trackback_ipvalidation_url_exclude' , 'forcemoderationt', 'forcemoderationt_treat', 'disable_api_comments', 'trackback_check_url', 'links_moderate', 'links_reject', 'contentfilter_activate', 'contentfilter_urls', 'contentfilter_authors', 'contentfilter_words', 'contentfilter_emails', 'akismet', 'akismet_server', 'akismet_filter', 'hide_email', 'checkmail', 'required_fields', 'automagic_htaccess', 'logtype', 'logfile')); $propbag->add('groups', array('ANTISPAM')); $propbag->add('config_groups', array( 'Content Filter' => array( 'contentfilter_activate', 'contentfilter_urls', 'contentfilter_authors', 'contentfilter_words', 'contentfilter_emails', 'akismet', 'akismet_server', 'akismet_filter', ), 'Trackbacks' => array( 'trackback_ipvalidation' , 'trackback_ipvalidation_url_exclude' , 'forcemoderationt', 'forcemoderationt_treat', 'disable_api_comments', 'trackback_check_url', ) )); $this->filter_defaults = array( 'authors' => 'casino;phentermine;credit;loans;poker', 'emails' => '', 'urls' => '8gold\.com;911easymoney\.com;canadianlabels\.net;condodream\.com;crepesuzette\.com;debt-help-bill-consolidation-elimination\.com;fidelityfunding\.net;flafeber\.com;gb\.com;houseofsevengables\.com;instant-quick-money-cash-advance-personal-loans-until-pay-day\.com;mediavisor\.com;newtruths\.com;oiline\.com;onlinegamingassociation\.com;online\-+poker\.com;popwow\.com;royalmailhotel\.com;spoodles\.com;sportsparent\.com;stmaryonline\.org;thatwhichis\.com;tmsathai\.org;uaeecommerce\.com;learnhowtoplay\.com', 'words' => 'very good site!;Real good stuff!' ); } function introspect_config_item($name, &$propbag) { global $serendipity; switch($name) { case 'disable_api_comments': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_API_COMMENTS); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_API_COMMENTS_DESC); $propbag->add('default', 'none'); $propbag->add('radio', array( 'value' => array('moderate', 'reject', 'none'), 'desc' => array(PLUGIN_EVENT_SPAMBLOCK_API_MODERATE, PLUGIN_EVENT_SPAMBLOCK_API_REJECT, NONE) )); $propbag->add('radio_per_row', '1'); break; case 'trackback_ipvalidation': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_TRACKBACKIPVALIDATION); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_TRACKBACKIPVALIDATION_DESC); $propbag->add('default', 'moderate'); $propbag->add('radio', array( 'value' => array('no', 'moderate', 'reject'), 'desc' => array(NO, PLUGIN_EVENT_SPAMBLOCK_API_MODERATE, PLUGIN_EVENT_SPAMBLOCK_API_REJECT) )); $propbag->add('radio_per_row', '1'); break; case 'trackback_ipvalidation_url_exclude': $propbag->add('type', 'text'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_TRACKBACKIPVALIDATION_URL_EXCLUDE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_TRACKBACKIPVALIDATION_URL_EXCLUDE_DESC); $propbag->add('rows', 2); $propbag->add('default', $this->get_default_exclude_urls()); break; case 'trackback_check_url': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_TRACKBACKURL); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_TRACKBACKURL_DESC); $propbag->add('default', false); break; case 'automagic_htaccess': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_HTACCESS); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_HTACCESS_DESC); $propbag->add('default', false); break; case 'hide_email': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_HIDE_EMAIL); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_HIDE_EMAIL_DESC); $propbag->add('default', true); break; case 'csrf': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_CSRF); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_CSRF_DESC); $propbag->add('default', true); break; case 'entrytitle': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_FILTER_TITLE); $propbag->add('description', ''); $propbag->add('default', false); break; case 'checkmail': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_CHECKMAIL); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_CHECKMAIL_DESC); $propbag->add('default', 'false'); $propbag->add('radio', array( 'value' => array('false', 'true', 'verify_once', 'verify_always'), 'desc' => array(NO, YES, PLUGIN_EVENT_SPAMBLOCK_CHECKMAIL_VERIFICATION_ONCE, PLUGIN_EVENT_SPAMBLOCK_CHECKMAIL_VERIFICATION_ALWAYS) )); $propbag->add('radio_per_row', '1'); break; case 'required_fields': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_REQUIRED_FIELDS); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_REQUIRED_FIELDS_DESC); $propbag->add('default', 'name,comment'); break; case 'bodyclone': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_BODYCLONE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_BODYCLONE_DESC); $propbag->add('default', true); break; case 'captchas': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS_DESC); $propbag->add('default', 'yes'); $propbag->add('radio', array( 'value' => array(true, 'no', 'scramble'), 'desc' => array(YES, NO, PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS_SCRAMBLE) )); break; case 'hide_for_authors': $_groups =& serendipity_getAllGroups(); $groups = array( 'all' => ALL_AUTHORS, 'none' => NONE ); foreach($_groups AS $group) { $groups[$group['confkey']] = $group['confvalue']; } $propbag->add('type', 'multiselect'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_HIDE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_HIDE_DESC); $propbag->add('select_values', $groups); $propbag->add('select_size', 5); $propbag->add('default', 'all'); break; case 'killswitch': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_KILLSWITCH); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_KILLSWITCH_DESC); $propbag->add('default', false); break; case 'contentfilter_activate': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_FILTER_ACTIVATE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_FILTER_ACTIVATE_DESC); $propbag->add('default', 'moderate'); $propbag->add('radio', array( 'value' => array('moderate', 'reject', 'none'), 'desc' => array(PLUGIN_EVENT_SPAMBLOCK_API_MODERATE, PLUGIN_EVENT_SPAMBLOCK_API_REJECT, NONE) )); $propbag->add('radio_per_row', '1'); break; case 'akismet': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_AKISMET); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_AKISMET_DESC); $propbag->add('default', ''); break; case 'akismet_server': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_AKISMET_SERVER); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_AKISMET_SERVER_DESC); // If the user has an API key, but hasn't set a server, he // must be using an older version of the plugin; default // to akismet. Otherwise, encourage adoption of the Open // Source alternative, TypePad Antispam. $curr_key = $this->get_config('akismet', false); $propbag->add('default', (empty($curr_key) ? 'akismet' : 'tpas')); $propbag->add('radio', array( 'value' => array('tpas', 'akismet', 'anon-tpas', 'anon-akismet'), 'desc' => array(PLUGIN_EVENT_SPAMBLOCK_SERVER_TPAS, PLUGIN_EVENT_SPAMBLOCK_SERVER_AKISMET, PLUGIN_EVENT_SPAMBLOCK_SERVER_TPAS_ANON, PLUGIN_EVENT_SPAMBLOCK_SERVER_AKISMET_ANON ) )); $propbag->add('radio_per_row', '1'); break; case 'akismet_filter': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_AKISMET_FILTER); $propbag->add('description', ''); $propbag->add('default', 'reject'); $propbag->add('radio', array( 'value' => array('moderate', 'reject', 'none'), 'desc' => array(PLUGIN_EVENT_SPAMBLOCK_API_MODERATE, PLUGIN_EVENT_SPAMBLOCK_API_REJECT, NONE) )); $propbag->add('radio_per_row', '1'); break; case 'contentfilter_urls': $propbag->add('type', 'text'); $propbag->add('rows', 3); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_FILTER_URLS); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_FILTER_URLS_DESC); $propbag->add('default', $this->filter_defaults['urls']); break; case 'contentfilter_authors': $propbag->add('type', 'text'); $propbag->add('rows', 3); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_FILTER_AUTHORS); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_FILTER_AUTHORS_DESC); $propbag->add('default', $this->filter_defaults['authors']); break; case 'contentfilter_words': $propbag->add('type', 'text'); $propbag->add('rows', 3); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_FILTER_WORDS); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_FILTER_AUTHORS_DESC); $propbag->add('default', $this->filter_defaults['words']); break; case 'contentfilter_emails': $propbag->add('type', 'text'); $propbag->add('rows', 3); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_FILTER_EMAILS); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_FILTER_AUTHORS_DESC); $propbag->add('default', $this->filter_defaults['emails']); break; case 'logfile': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_LOGFILE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_LOGFILE_DESC); $propbag->add('default', $serendipity['serendipityPath'] . 'spamblock-%Y-%m-%d.log'); break; case 'logtype': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_LOGTYPE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_LOGTYPE_DESC); $propbag->add('default', 'none'); $propbag->add('radio', array( 'value' => array('file', 'db', 'none'), 'desc' => array(PLUGIN_EVENT_SPAMBLOCK_LOGTYPE_FILE, PLUGIN_EVENT_SPAMBLOCK_LOGTYPE_DB, PLUGIN_EVENT_SPAMBLOCK_LOGTYPE_NONE) )); $propbag->add('radio_per_row', '1'); break; case 'ipflood': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_IPFLOOD); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_IPFLOOD_DESC); $propbag->add('default', 0); break; case 'captchas_ttl': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS_TTL); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_CAPTCHAS_TTL_DESC); $propbag->add('default', '7'); break; case 'captcha_color': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_CAPTCHA_COLOR); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_CAPTCHA_COLOR_DESC); $propbag->add('default', '255,255,255'); $propbag->add('validate', '@^[0-9]{1,3},[0-9]{1,3},[0-9]{1,3}$@'); break; case 'forcemoderation': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_FORCEMODERATION); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_FORCEMODERATION_DESC); $propbag->add('default', '30'); break; case 'forcemoderation_treat': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_FORCEMODERATION_TREAT); $propbag->add('description', ''); $propbag->add('default', 'moderate'); $propbag->add('radio', array( 'value' => array('moderate', 'reject'), 'desc' => array(PLUGIN_EVENT_SPAMBLOCK_API_MODERATE, PLUGIN_EVENT_SPAMBLOCK_API_REJECT) )); $propbag->add('radio_per_row', '1'); break; case 'forcemoderationt': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_FORCEMODERATIONT); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_FORCEMODERATIONT_DESC); $propbag->add('default', '30'); break; case 'forcemoderationt_treat': $propbag->add('type', 'radio'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_FORCEMODERATIONT_TREAT); $propbag->add('description', ''); $propbag->add('default', 'moderate'); $propbag->add('radio', array( 'value' => array('moderate', 'reject'), 'desc' => array(PLUGIN_EVENT_SPAMBLOCK_API_MODERATE, PLUGIN_EVENT_SPAMBLOCK_API_REJECT) )); $propbag->add('radio_per_row', '1'); break; case 'links_moderate': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_LINKS_MODERATE); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_LINKS_MODERATE_DESC); $propbag->add('default', '7'); break; case 'links_reject': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_EVENT_SPAMBLOCK_LINKS_REJECT); $propbag->add('description', PLUGIN_EVENT_SPAMBLOCK_LINKS_REJECT_DESC); $propbag->add('default', '13'); break; default: return false; } return true; } function get_default_exclude_urls() { return '^http://identi\.ca/notice/\d+$'; } function htaccess_update($new_ip) { global $serendipity; serendipity_db_query("INSERT INTO {$serendipity['dbPrefix']}spamblock_htaccess (ip, timestamp) VALUES ('" . serendipity_db_escape_string($new_ip) . "', '" . time() . "')"); // Limit number of banned IPs to prevent .htaccess growing too large. The query selects at max 20*$blocklist_chunksize entries from the last two days. $blocklist_chunksize = 177; $q = "SELECT ip FROM {$serendipity['dbPrefix']}spamblock_htaccess WHERE timestamp > " . (time() - 86400*2) . " GROUP BY ip ORDER BY timestamp DESC LIMIT " . 20*$blocklist_chunksize; $rows = serendipity_db_query($q, false, 'assoc'); $deny = array(); foreach($rows AS $row) { $deny[] = $row['ip']; } $hta = $serendipity['serendipityPath'] . '.htaccess'; $blocklist_size = count($deny); if ($blocklist_size > 0 && file_exists($hta) && is_writable($hta)) { $blocklist = "#IP count: " . $blocklist_size . ", last update: " . date('Y-m-d H:i:s') . "\n"; for ($i = 0; $i < ((int) (($blocklist_size-1) / $blocklist_chunksize))+1; $i++) { $blocklist = $blocklist . "Deny From " . implode(" ", array_slice($deny, $i*$blocklist_chunksize, $blocklist_chunksize)) . "\n"; } $fp = @fopen($hta, 'r+'); if (!$fp) { return false; } if (flock($fp, LOCK_EX|LOCK_NB)) { $htaccess = file_get_contents($hta); if (!$htaccess) { fclose($fp); // also releases the lock return false; } // Check if an old htaccess file existed and try to preserve its contents. Otherwise completely wipe the file. if ($htaccess != '' && preg_match('@^(.*)#SPAMDENY.*Deny From.+#/SPAMDENY(.*)$@imsU', $htaccess, $match)) { // Code outside from s9y-code was found. $content = trim($match[1]) . "\n#SPAMDENY\n" . $blocklist . "#/SPAMDENY\n" . trim($match[2]); } else { $content = trim($htaccess) . "\n#SPAMDENY\n" . $blocklist . "#/SPAMDENY\n"; } ftruncate($fp, 0); fwrite($fp, $content); fclose($fp); return true; } else { fclose($fp); return false; } } return false; } function akismetRequest($api_key, $data, &$ret, $action = 'comment-check', $eventData = null, $addData = null) { global $serendipity; $opt = array( 'method' => 'POST', 'http' => '1.1', 'timeout' => 20, 'allowRedirects' => true, 'maxRedirects' => 3, 'readTimeout' => array(5,0), ); // Default server type to akismet, in case user has an older version of the plugin // where no server was set $server_type = $this->get_config('akismet_server', 'akismet'); $server = ''; $anon = false; switch ($server_type) { case 'anon-tpas': $anon = true; case 'tpas': $server = 'api.antispam.typepad.com'; break; case 'anon-akismet': $anon = true; case 'akismet': $server = 'rest.akismet.com'; break; } if ($anon) { $data['comment_author'] = 'John Doe'; $data['comment_author_email'] = ''; $data['comment_author_url'] = ''; } if (empty($server)) { $this->log($this->logfile, is_null($eventData) ? 0:$eventData['id'], 'AKISMET_SERVER', 'No Akismet server found', $addData); $ret['is_spam'] = false; $ret['message'] = 'No server for Akismet request'; return; } else { // DEBUG //$this->log($this->logfile, $eventData['id'], 'AKISMET_SERVER', 'Using Akismet server at ' . $server, $addData); } $req = new HTTP_Request( 'http://' . $server . '/1.1/verify-key', $opt ); $req->addPostData('key', $api_key); $req->addPostData('blog', $serendipity['baseURL']); if (PEAR::isError($req->sendRequest()) || $req->getResponseCode() != '200') { $ret['is_spam'] = false; $ret['message'] = 'API Verification Request failed'; $this->log($this->logfile, $eventData['id'], 'API_ERROR', 'Akismet HTTP verification request failed.', $addData); return; } else { // Fetch response $reqdata = $req->getResponseBody(); } if (!preg_match('@valid@i', $reqdata)) { $ret['is_spam'] = false; $ret['message'] = 'API Verification failed'; $this->log($this->logfile, $eventData['id'], 'API_ERROR', 'Akismet API verification failed: ' . $reqdata, $addData); return; } $req = new HTTP_Request( 'http://' . $api_key . '.' . $server . '/1.1/' . $action, $opt ); foreach($data AS $key => $value) { $req->addPostData($key, $value); } if (PEAR::isError($req->sendRequest()) || $req->getResponseCode() != '200') { $ret['is_spam'] = false; $ret['message'] = 'Akismet Request failed'; $this->log($this->logfile, $eventData['id'], 'API_ERROR', 'Akismet HTTP request failed.', $addData); return; } else { // Fetch response $reqdata = $req->getResponseBody(); } if ($action == 'comment-check' && preg_match('@true@i', $reqdata)) { $ret['is_spam'] = true; $ret['message'] = $reqdata; // DEBUG //$this->log($this->logfile, $eventData['id'], 'AKISMET_SPAM', 'Akismet API returned spam', $addData); } elseif ($action == 'comment-check' && preg_match('@false@i', $reqdata)) { $ret['is_spam'] = false; $ret['message'] = $reqdata; // DEBUG //$this->log($this->logfile, $eventData['id'], 'AKISMET_PASS', 'Passed Akismet verification', $addData); } elseif ($action != 'comment-check' && preg_match('@received@i', $reqdata)) { $ret['is_spam'] = ($action == 'submit-spam'); $ret['message'] = $reqdata; $this->log($this->logfile, $eventData['id'], 'API_ERROR', 'Akismet API failure: ' . $reqdata, $addData); } else { $ret['is_spam'] = false; $ret['message'] = 'Akismet API failure'; $this->log($this->logfile, $eventData['id'], 'API_ERROR', 'Akismet API failure: ' . $reqdata, $addData); } } function tellAboutComment($where, $api_key, $comment_id, $is_spam) { global $serendipity; $comment = serendipity_db_query(" SELECT C.*, L.useragent as log_useragent, E.title as entry_title " . " FROM {$serendipity['dbPrefix']}comments C, {$serendipity['dbPrefix']}spamblocklog L , {$serendipity['dbPrefix']}entries E " . " WHERE C.id = '" . (int)$comment_id . "' AND C.entry_id=L.entry_id AND C.entry_id=E.id " . " AND C.author=L.author AND C.url=L.url AND C.referer=L.referer " . " AND C.ip=L.ip AND C.body=L.body", true, 'assoc'); if (!is_array($comment)) return; require_once S9Y_PEAR_PATH . 'HTTP/Request.php'; if (function_exists('serendipity_request_start')) serendipity_request_start(); switch($where) { case 'akismet.com': // DEBUG //$this->log($this->logfile, $eventData['id'], 'AKISMET_SAFETY', 'Akismet verification takes place', $addData); $ret = array(); $data = array( 'blog' => $serendipity['baseURL'], 'user_agent' => $comment['log_useragent'], 'referrer' => $comment['referer'], 'user_ip' => $comment['ip'], 'permalink' => serendipity_archiveURL($comment['entry_id'], $comment['entry_title'], 'serendipityHTTPPath', true, array('timestamp' => $comment['timestamp'])), 'comment_type' => ($comment['type'] == 'NORMAL' ? 'comment' : strtolower($comment['type'])), // second: pingback or trackback. 'comment_author' => $comment['author'], 'comment_author_email' => $comment['email'], 'comment_author_url' => $comment['url'], 'comment_content' => $comment['body'] ); $this->akismetRequest($api_key, $data, $ret, ($is_spam ? 'submit-spam' : 'submit-ham')); break; } if (function_exists('serendipity_request_end')) serendipity_request_end(); } function &getBlacklist($where, $api_key, &$eventData, &$addData) { global $serendipity; $ret = false; require_once S9Y_PEAR_PATH . 'HTTP/Request.php'; if (function_exists('serendipity_request_start')) serendipity_request_start(); // this switch statement is a leftover from blogg.de support (i.e. there were more options than just one). Leaving it in place in case we get more options again in the future. switch($where) { case 'akismet.com': // DEBUG //$this->log($this->logfile, $eventData['id'], 'AKISMET_SAFETY', 'Akismet verification takes place', $addData); $ret = array(); $data = array( 'blog' => $serendipity['baseURL'], 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'referrer' => $_SERVER['HTTP_REFERER'], 'user_ip' => $_SERVER['REMOTE_ADDR'] != getenv('SERVER_ADDR') ? $_SERVER['REMOTE_ADDR'] : getenv('HTTP_X_FORWARDED_FOR'), 'permalink' => serendipity_archiveURL($eventData['id'], $eventData['title'], 'serendipityHTTPPath', true, array('timestamp' => $eventData['timestamp'])), 'comment_type' => ($addData['type'] == 'NORMAL' ? 'comment' : strtolower($addData['type'])), // second: pingback or trackback. 'comment_author' => $addData['name'], 'comment_author_email' => $addData['email'], 'comment_author_url' => $addData['url'], 'comment_content' => $addData['comment'] ); $this->akismetRequest($api_key, $data, $ret); break; default: break; } if (function_exists('serendipity_request_end')) serendipity_request_end(); return $ret; } function checkScheme() { global $serendipity; $dbversion = $this->get_config('dbversion', '1'); if ($dbversion == '1') { $q = "CREATE TABLE {$serendipity['dbPrefix']}spamblocklog ( timestamp int(10) {UNSIGNED} default null, type varchar(255), reason text, entry_id int(10) {UNSIGNED} not null default '0', author varchar(80), email varchar(200), url varchar(200), useragent varchar(255), ip varchar(45), referer varchar(255), body text)"; $sql = serendipity_db_schema_import($q); $q = "CREATE INDEX kspamidx ON {$serendipity['dbPrefix']}spamblocklog (timestamp);"; $sql = serendipity_db_schema_import($q); $q = "CREATE INDEX kspamtypeidx ON {$serendipity['dbPrefix']}spamblocklog (type);"; $sql = serendipity_db_schema_import($q); $q = "CREATE INDEX kspamentryidx ON {$serendipity['dbPrefix']}spamblocklog (entry_id);"; $sql = serendipity_db_schema_import($q); $q = "CREATE TABLE {$serendipity['dbPrefix']}spamblock_htaccess ( timestamp int(10) {UNSIGNED} default null, ip varchar(15))"; $sql = serendipity_db_schema_import($q); $q = "CREATE INDEX kshtaidx ON {$serendipity['dbPrefix']}spamblock_htaccess (timestamp);"; $sql = serendipity_db_schema_import($q); $this->set_config('dbversion', '3'); } if ($dbversion == '2') { $q = "ALTER TABLE {$serendipity['dbPrefix']}spamblocklog CHANGE COLUMN ip ip VARCHAR(45)"; $sql = serendipity_db_schema_import($q); $q = "ALTER TABLE {$serendipity['dbPrefix']}spamblock_htaccess CHANGE COLUMN ip ip VARCHAR(45)"; $sql = serendipity_db_schema_import($q); $this->set_config('dbversion', '3'); } return true; } function generate_content(&$title) { $title = $this->title; } // This method will be called on "fatal" spam errors that are unlikely to occur accidentally by users. // Their IPs will be constantly blocked. function IsHardcoreSpammer() { global $serendipity; if (serendipity_db_bool($this->get_config('automagic_htaccess'))) { $this->htaccess_update($_SERVER['REMOTE_ADDR']); } } // Checks whether the current author is contained in one of the gorups that need no spam checking function inGroup() { global $serendipity; $checkgroups = explode('^', $this->get_config('hide_for_authors')); if (!isset($serendipity['authorid']) || !is_array($checkgroups)) { return false; } $mygroups =& serendipity_getGroups($serendipity['authorid'], true); if (!is_array($mygroups)) { return false; } foreach($checkgroups AS $key => $groupid) { if ($groupid == 'all') { return true; } elseif (in_array($groupid, $mygroups)) { return true; } } return false; } function example() { echo '
' . PLUGIN_EVENT_SPAMBLOCK_SPAM . '';
$eventData['action_more'] .= '
' . PLUGIN_EVENT_SPAMBLOCK_NOT_SPAM . '';
}
$eventData['action_author'] .= '
';
if (!empty($eventData['url'])) {
$url_is_filtered = $this->checkFilter('urls', $eventData['url']);
$eventData['action_url'] .= '
';
}
if (!empty($eventData['email'])) {
$email_is_filtered = $this->checkFilter('emails', $eventData['email']);
$eventData['action_email'] .= '
';
}
return true;
break;
default:
return false;
break;
}
} else {
return false;
}
}
/**
* wordfilter, email and additional link check moved to this function, to allow comment user to opt-in (verify_once), but reject all truly spam comments before.
**/
function wordfilter($logfile, &$eventData, $wordmatch, $addData, $ftc = false) {
global $serendipity;
// Check for word filtering
if ($filter_type = $this->get_config('contentfilter_activate', 'moderate')) {
if($ftc) $filter_type = 'reject';
// Filter authors names
$filter_authors = explode(';', $this->get_config('contentfilter_authors', $this->filter_defaults['authors']));
if (is_array($filter_authors)) {
foreach($filter_authors AS $filter_author) {
$filter_author = trim($filter_author);
if (empty($filter_author)) {
continue;
}
if (preg_match('@(' . $filter_author . ')@i', $addData['name'], $wordmatch)) {
if ($filter_type == 'moderate') {
$this->log($logfile, $eventData['id'], 'MODERATE', PLUGIN_EVENT_SPAMBLOCK_FILTER_AUTHORS . ': ' . $wordmatch[1], $addData);
$eventData['moderate_comments'] = true;
$serendipity['csuccess'] = 'moderate';
$serendipity['moderate_reason'] = PLUGIN_EVENT_SPAMBLOCK_ERROR_BODY . ' (' . PLUGIN_EVENT_SPAMBLOCK_FILTER_AUTHORS . ': ' . $wordmatch[1] . ')';
} else {
$this->log($logfile, $eventData['id'], 'REJECTED', PLUGIN_EVENT_SPAMBLOCK_FILTER_AUTHORS . ': ' . $wordmatch[1], $addData);
$eventData = array('allow_comments' => false);
$serendipity['messagestack']['comments'][] = PLUGIN_EVENT_SPAMBLOCK_ERROR_BODY;
return false;
}
}
}
}
// Filter URL
$filter_urls = explode(';', $this->get_config('contentfilter_urls', $this->filter_defaults['urls']));
if (is_array($filter_urls)) {
foreach($filter_urls AS $filter_url) {
$filter_url = trim($filter_url);
if (empty($filter_url)) {
continue;
}
if (preg_match('@(' . $filter_url . ')@i', $addData['url'], $wordmatch)) {
if ($filter_type == 'moderate') {
$this->log($logfile, $eventData['id'], 'MODERATE', PLUGIN_EVENT_SPAMBLOCK_FILTER_URLS . ': ' . $wordmatch[1], $addData);
$eventData['moderate_comments'] = true;
$serendipity['csuccess'] = 'moderate';
$serendipity['moderate_reason'] = PLUGIN_EVENT_SPAMBLOCK_ERROR_BODY . ' (' . PLUGIN_EVENT_SPAMBLOCK_FILTER_URLS . ': ' . $wordmatch[1] . ')';
} else {
$this->log($logfile, $eventData['id'], 'REJECTED', PLUGIN_EVENT_SPAMBLOCK_FILTER_URLS . ': ' . $wordmatch[1], $addData);
$eventData = array('allow_comments' => false);
$serendipity['messagestack']['comments'][] = PLUGIN_EVENT_SPAMBLOCK_ERROR_BODY;
return false;
}
}
}
}
// Filter Content
$filter_bodys = explode(';', $this->get_config('contentfilter_words', $this->filter_defaults['words']));
if (is_array($filter_bodys)) {
foreach($filter_bodys AS $filter_body) {
$filter_body = trim($filter_body);
if (empty($filter_body)) {
continue;
}
if (preg_match('@(' . $filter_body . ')@i', $addData['comment'], $wordmatch)) {
if ($filter_type == 'moderate') {
$this->log($logfile, $eventData['id'], 'MODERATE', PLUGIN_EVENT_SPAMBLOCK_FILTER_WORDS . ': ' . $wordmatch[1], $addData);
$eventData['moderate_comments'] = true;
$serendipity['csuccess'] = 'moderate';
$serendipity['moderate_reason'] = PLUGIN_EVENT_SPAMBLOCK_ERROR_BODY . ' (' . PLUGIN_EVENT_SPAMBLOCK_FILTER_WORDS . ': ' . $wordmatch[1] . ')';
} else {
$this->log($logfile, $eventData['id'], 'REJECTED', PLUGIN_EVENT_SPAMBLOCK_FILTER_WORDS . ': ' . $wordmatch[1], $addData);
$eventData = array('allow_comments' => false);
$serendipity['messagestack']['comments'][] = PLUGIN_EVENT_SPAMBLOCK_ERROR_BODY;
return false;
}
}
}
}
// Filter Emails
$filter_emails = explode(';', $this->get_config('contentfilter_emails', $this->filter_defaults['emails']));
if (is_array($filter_emails)) {
foreach($filter_emails AS $filter_email) {
$filter_email = trim($filter_email);
if (empty($filter_email)) {
continue;
}
if (preg_match('@(' . $filter_email . ')@i', $addData['email'], $wordmatch)) {
$this->IsHardcoreSpammer();
if ($filter_type == 'moderate') {
$this->log($logfile, $eventData['id'], 'MODERATE', PLUGIN_EVENT_SPAMBLOCK_FILTER_EMAILS . ': ' . $wordmatch[1], $addData);
$eventData['moderate_comments'] = true;
$serendipity['csuccess'] = 'moderate';
$serendipity['moderate_reason'] = PLUGIN_EVENT_SPAMBLOCK_ERROR_BODY . ' (' . PLUGIN_EVENT_SPAMBLOCK_FILTER_EMAILS . ': ' . $wordmatch[1] . ')';
} else {
$this->log($logfile, $eventData['id'], 'REJECTED', PLUGIN_EVENT_SPAMBLOCK_FILTER_EMAILS . ': ' . $wordmatch[1], $addData);
$eventData = array('allow_comments' => false);
$serendipity['messagestack']['emails'][] = PLUGIN_EVENT_SPAMBLOCK_ERROR_BODY;
return false;
}
}
}
}
} // Content filtering end
if($ftc) {
// Check for maximum number of links before rejecting
$link_count = substr_count(strtolower($addData['comment']), 'http://');
$links_reject = $this->get_config('links_reject', 20);
if ($links_reject > 0 && $link_count > $links_reject) {
$this->log($logfile, $eventData['id'], 'REJECTED', PLUGIN_EVENT_SPAMBLOCK_REASON_LINKS_REJECT, $addData);
$eventData = array('allow_comments' => false);
$serendipity['messagestack']['comments'][] = PLUGIN_EVENT_SPAMBLOCK_ERROR_BODY;
return false;
}
}
} // function wordfilter end
function &checkFilter($what, $match, $getItems = false) {
$items = explode(';', $this->get_config('contentfilter_' . $what, $this->filter_defaults[$what]));
$filtered = false;
if (is_array($items)) {
foreach($items AS $key => $item) {
if (empty($match)) {
continue;
}
if (empty($item)) {
unset($items[$key]);
continue;
}
if (preg_match('@' . $item . '@', $match)) {
$filtered = true;
unset($items[$key]);
}
}
}
if ($getItems) {
if (!$filtered && !empty($match)) {
$items[] = preg_quote($match, '@');
}
return $items;
}
return $filtered;
}
function getComment($key, $id) {
global $serendipity;
$c = serendipity_db_query("SELECT $key FROM {$serendipity['dbPrefix']}comments WHERE id = '" . (int)$id . "'", true, 'assoc');
if (!is_array($c) || !isset($c[$key])) {
return false;
}
return $c[$key];
}
function random_string($max_char, $min_char) {
$this->chars = array(2, 3, 4, 7, 9); // 1, 5, 6 and 8 may look like characters.
$this->chars = array_merge($this->chars, array('A','B','C','D','E','F','H','J','K','L','M','N','P','Q','R','T','U','V','W','X','Y','Z')); // I, O, S may look like numbers
$strings = array_rand($this->chars, mt_rand($min_char, $max_char));
$string = '';
foreach($strings AS $idx => $charidx) {
$string .= $this->chars[$charidx];
}
$_SESSION['spamblock'] = array('captcha' => $string);
return $strings;
}
function log($logfile, $id, $switch, $reason, $comment) {
global $serendipity;
$method = $this->get_config('logtype');
switch($method) {
case 'file':
if (empty($logfile)) {
return;
}
if (strpos($logfile, '%') !== false) {
$logfile = strftime($logfile);
}
$fp = @fopen($logfile, 'a+');
if (!is_resource($fp)) {
return;
}
fwrite($fp, sprintf(
'[%s] - [%s: %s] - [#%s, Name "%s", E-Mail "%s", URL "%s", User-Agent "%s", IP %s] - [%s]' . "\n",
date('Y-m-d H:i:s', serendipity_serverOffsetHour()),
$switch,
$reason,
$id,
str_replace("\n", ' ', $comment['name']),
str_replace("\n", ' ', $comment['email']),
str_replace("\n", ' ', $comment['url']),
str_replace("\n", ' ', $_SERVER['HTTP_USER_AGENT']),
$_SERVER['REMOTE_ADDR'],
str_replace("\n", ' ', $comment['comment'])
));
fclose($fp);
break;
case 'none':
return;
break;
case 'db':
default:
$q = sprintf("INSERT INTO {$serendipity['dbPrefix']}spamblocklog
(timestamp, type, reason, entry_id, author, email, url, useragent, ip, referer, body)
VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')",
serendipity_serverOffsetHour(),
serendipity_db_escape_string($switch),
serendipity_db_escape_string($reason),
serendipity_db_escape_string($id),
serendipity_db_escape_string($comment['name']),
serendipity_db_escape_string($comment['email']),
serendipity_db_escape_string($comment['url']),
substr(serendipity_db_escape_string($_SERVER['HTTP_USER_AGENT']), 0, 255),
serendipity_db_escape_string($_SERVER['REMOTE_ADDR']),
substr(serendipity_db_escape_string(isset($_SESSION['HTTP_REFERER']) ? $_SESSION['HTTP_REFERER'] : $_SERVER['HTTP_REFERER']), 0, 255),
serendipity_db_escape_string($comment['comment'])
);
serendipity_db_query($q);
break;
}
}
}
/* vim: set sts=4 ts=4 expandtab : */