$value) { update_option(trim($key), trim($value)); } } if ( isset($_POST['use_defaults']) ) { ensure_defaults(true); echo '

Using default values.

'; } ?>

"; foreach ( $user_editable_options as $option) { $value = get_option($option); if (is_array($value)) continue; echo ""; echo ""; echo ""; } echo ""; } function wo_list_page() { global $wpdb, $display_name; if ( function_exists('add_management_page') ) { add_management_page($display_name, $display_name, 1, __FILE__, 'wo_list'); } } function wo_list() { global $wpdb, $comment; wo_checkForWorstOffenderDeletion(); wo_worst_offenders(); } add_action('admin_menu', 'wo_list_page'); function wo_checkForWorstOffenderDeletion() { if (isset($_POST['submit']) && 'worstoffenders' == $_POST['action'] && !empty($_POST['worst'])) { wo_processOffendersForm(); } } /** * given a hostname this method returns a host IP address * (note that it does *not* do anything clever in the case of round-robin dns). */ function resolve($domain) { global $keys; static $domains, $init; if (!$init) { $init = true; $domains = get_option($keys['domain_cache']); } if (!isset($domains[$domain])) { // domain must be resolved $domains[$domain] = getHostByName($domain); update_option($keys['domain_cache'], $domains ); } return $domains[$domain]; } function deleteSelected($actions, $offender) { global $wpdb, $keys; if (is_array($actions)) { $actions = array_unique($actions); } $counter = get_option( $keys['counter'] ); $updates = array(); foreach ($actions as $action) { $counter++; $query = "DELETE FROM $wpdb->comments WHERE comment_approved = 'spam' AND ".$action.";"; $nuked = $wpdb->query($query); if ($nuked) { $updates[] = 'Messages from '.$offender.' have been removed.'; } } $updates = array_unique($updates); update_option( $keys['counter'], $counter ); return $updates; } function wo_invoke_ban($ban_these, $max_ban_list_size=400, $section="worst-offenders", $ips) { require_once(ABSPATH . 'wp-admin/admin-functions.php'); //FIXME - this should check for the existence/writeability of the .htaccess file. $home_path = get_home_path(); // retrieve the ban from the htaccess file $currently_banned = extract_from_markers($home_path.".htaccess", $section); // use the list of ip addresses that are being banned to ensure no // ip address is duplicated. $currently_banned = remove_ban($ips, $currently_banned); // now combine the modified cleaned up current list with the // detail of the new bans $amalgamated_list = array_merge( (array)$ban_these, (array)$currently_banned ); // set a maximum size for the list of banned entries. $amalgamated_list = array_slice($amalgamated_list, 0, $max_ban_list_size); // store it. insert_with_markers($home_path.".htaccess", $section, $amalgamated_list); return sizeof($amalgamated_list); } /** * return true if the $str ends with $sub - e.g. * endsWith("Rich Boakes", "Boakes") returns true; * endsWith("Rich Boakes", "Cup of Tea") returns false; **/ function endsWith( $str, $sub ) { return ( substr( $str, strlen( $str ) - strlen( $sub ) ) === $sub ); } function wo_processOffendersForm() { foreach ($_POST['worst'] as $serializedDetail) { $detail = unserialize(rawurldecode($serializedDetail)); $u1 = deleteSelected($detail[action],$detail[offender]); $u2 = banSelected($detail[ban], $detail[offender]); } echo '

'; foreach ($u1 as $update) { echo "

$update

"; } if (can_ban()) { foreach ($u2 as $update) { echo "

$update

"; }; } echo '

'; } function config_link($from, $to, $msg) { $this_page = $_SERVER['PHP_SELF'] ."?". $_SERVER['QUERY_STRING']; $config_page = str_replace($from, $to, $this_page); echo("

$msg

"); } function wo_worst_offenders() { global $wpdb, $keys, $display_name; $comments = $wpdb->get_results("SELECT * FROM $wpdb->comments WHERE comment_approved = 'spam' "); $counter="counter"; if ($debug_on) { ?>

'; _e('Separating spam from ham can be difficult when there is so much spam.'); echo '

'; _e('To make things easier, the list below shows the most prolific of the spammers - it's a fair bet that if you have many comments from one IP address or from one particular website, then those comments are all likely to be spam.'); echo '

'; $offender=array(); $action=array(); $type=array(); $ban=array(); $orig_comment=array(); $newbans=array(); foreach($comments as $comment) { // spot repeating IP addresses $type[$comment->comment_author_IP] = "Comments all originate from the same IP address."; $offender[$comment->comment_author_IP]++; $action[$comment->comment_author_IP][] = "comment_author_ip = '".$comment->comment_author_IP."'"; $ban[$comment->comment_author_IP][] = $comment->comment_author_IP; $orig_comment[$comment->comment_author_IP][] = $comment->comment_ID; //spot repeating URLs addresses //$offender[$comment->comment_author_url]++; //$action[$comment->comment_author_url] = "comment_author_url = '".$comment->comment_author_url."'"; $domain = wo_extract_domain($comment->comment_author_url); $type[$domain] = "Comments all promote the same domain"; $offender[$domain]++; $action[$domain][] = "comment_ID = '".$comment->comment_ID."'"; $ban[$domain][] = $comment->comment_author_IP; $orig_comment[$domain][] = $comment->comment_ID; $resolve = resolve(wo_extract_domain($comment->comment_author_url)); $type[$resolve] = "Comment URL's all resolve to the same IP address"; $offender[$resolve]++; $action[$resolve][] = "comment_author_url like '%".$domain."%'"; $ban[$resolve][] = $comment->comment_author_IP; $explanation[$resolve][] = $resolve; $orig_comment[$resolve][] = $comment->comment_ID; $hash = md5($comment->comment_content); $type[$hash] = "Comments have an identical MD5 hash."; $offender[$hash]++; $action[$hash][] = "comment_author_IP = '".$comment->comment_author_IP."'"; $ban[$hash][] = $comment->comment_author_IP; $explanation[$hash][] = $hash; $orig_comment[$hash][] = $comment->comment_ID; $max_link_count = get_option( $keys['max_link_count'] ); $link_count = count_links($comment->comment_content); if ($link_count>=$max_link_count) { $lc = "MULTILINK"; $type[$lc] = "Comments that contain more than $max_link_count links."; $offender[$lc]++; $action[$lc][] = "comment_ID = '".$comment->comment_ID."'"; $ban[$lc][] = $comment->comment_author_IP; $explanation[$lc][] = $lc." messages."; $orig_comment[$lc][] = $comment->comment_ID; } $max_consonant_sequence = get_option( $keys['max_consonant_sequence'] ); $gobbledygook = count_consonants($comment->comment_author_url); if ($gobbledygook>=$max_consonant_sequence) { $lc = "Unreadable URL"; $type[$lc] = "Comment URL looks unreadable by humans."; $offender[$lc]++; $action[$lc][] = "comment_author_IP = '".$comment->comment_author_IP."'"; $ban[$lc][] = $comment->comment_author_IP; $explanation[$lc][] = $comment->comment_author_url."(".$lc.")"; $orig_comment[$lc][] = $comment->comment_ID; } $match = check_name_match($comment->comment_content, $comment->comment_author); if ($match) { $lc = $comment->comment_author; $type[$lc] = "Comment begins with the comment authors name."; $offender[$lc]++; $action[$lc][] = "comment_author = '".$comment->comment_author."'"; $ban[$lc][] = $comment->comment_author_IP; $explanation[$lc][] = $comment->comment_author; $orig_comment[$lc][] = $comment->comment_ID; } } $lower_limit = get_option( $keys['ui_vis'] ); $list_size = get_option( $keys['ui_list_size'] ); wo_list_offenders($offender, $action, $type, $ban, $explanation, $orig_comment, $list_size, $lower_limit); } ?>
Key:".$key." = ".get_option( $key ).".

"; } } ensure_defaults(); function ensure_defaults($force = false) { global $keys, $n; $keys['ui_vis'] = $n."ui_visibility_threshold"; $keys['ui_preselect'] = $n."ui_preselect_threshold"; $keys['ui_list_size'] = $n."ui_size_threshold"; $keys['user_ban_list'] = $n."current_user_ban"; $keys['user_ban_list_max_size'] = $n."user_ban_list_max_size"; $keys['user_ban_list_size'] = $n."current_user_ban_count"; $keys['debug'] = $n."debug"; $keys['domain_cache'] = $n."domain_cache"; $keys['counter'] = $n."counter"; $keys['max_link_count'] = $n."max_link_count"; $keys['max_consonant_sequence'] = $n."max_consonant_sequence"; $ui_vis = get_option( $keys['ui_vis'] ); $ui_preselect = get_option( $keys['ui_preselect'] ); $ui_list_size = get_option( $keys['ui_list_size'] ); $ban_list = get_option( $keys['ban_list'] ); $user_ban_list = get_option( $keys['user_ban_list'] ); $ban_list_size = get_option( $keys['ban_list_size'] ); $user_ban_list_size = get_option( $keys['user_ban_list_size'] ); $user_ban_list_max_size = get_option( $keys['user_ban_list_max_size'] ); $counter = get_option( $keys['counter'] ); $max_link_count = get_option( $keys['max_link_count'] ); $max_consonant_sequence = get_option( $keys['max_consonant_sequence'] ); if ($ui_vis=="" || $force) redo_option( $keys['ui_vis'], 2, "Visibility threshold. Items with this many, or more matches, may be included in the UI."); if ($ui_preselect=="" || $force) redo_option( $keys['ui_preselect'], 4, "Items with this many, or more matches are pre-selected for banning."); if ($ui_list_size=="" || $force) redo_option($keys['ui_list_size'], 20, "The maximum number of rows to display."); if (!is_array($ban_list) || $force) reset_user_ban( ); if ($user_ban_list_max_size=="" || $force) redo_option($keys['user_ban_list_max_size'], 800, "The maximum number of IP addresses that can be banned."); if ($counter=="" || $force) update_option( $keys['counter'], 0); if ($max_link_count=="" || $force) redo_option( $keys['max_link_count'], 5, "The maximum number of links permitted in a message."); if ($max_consonant_sequence=="" || $force) redo_option( $keys['max_consonant_sequence'], 6, "The maximum number of consecutive consonants in a domain."); } function redo_option($key, $val, $desc) { delete_option($key); add_option($key, $val, $desc); } function wo_extract_domain($src) { if (!check_real_url($src)) { // if the URL cannot be parsed, no domain can be extracted // so a blank is returned. return ""; } // spot repeating domains $parsed = parse_url($src); $host = $parsed[host]; $domainBits = array_reverse(explode(".",$host)); if (strlen($domainBits[1]) < 1 ) return ""; if (strlen($domainBits[1]) < 3) { return $domainBits[2] .'.'. $domainBits[1] .'.'. $domainBits[0]; } return $domainBits[1] .'.'. $domainBits[0]; } // If desired, the lower limit parameter will stop "normal" (i.e. limited run) // offenders from appearing in the list. If it's called with 0 or 1, then // the list will just be clipped at the maximum length. This can be quite useful // because once the bad guys have been culled, the "next bad guys" can potentially // be spotted before they hit hard. Remember, these folks have already been // branded as spammers, so we only need to check that they're not // false positives before discarding them like stale mackerel. function wo_extract_interesting($culprits, $max_size, $lower_limit = 0) { // strip out the small time offenders foreach ($culprits as $k => $v) { if ($v < $lower_limit) unset($culprits[$k]); } // strip out any blanks unset($culprits[""]); // sort what remains arsort($culprits); // return the top-n return array_slice($culprits, 0, $max_size); } function wo_list_offenders($culprits, $action, $type, $ban, $explanation, $orig_comment, $max_size = 10, $lower_limit=4) { global $keys; $culprits = wo_extract_interesting($culprits, $max_size, $lower_limit); $mentioned = array(); if ($culprits) { $preselect_threshold = get_option( $keys['ui_preselect'] ); ?>
$v) { foreach($ban[$k] as $b) { $mentioned[$b]++; } // stuff to be encoded for form use $detail = array(); $detail["action"] = $action[$k]; $detail["offender"] = $k; $detail["type"] = $type[$k]; $detail["ban"] = array_unique($ban[$k]); $id=rawurlencode(serialize($detail)); unset($display["links"]); foreach($orig_comment[$k] as $cid) { $display["links"][] = make_comment_link($cid); } // stuff to be displayed $display["ban"] = annotate_banned($detail["ban"], $mentioned); $checked = ($v >= $preselect_threshold ? 'checked':""); //if (check_if_all_previously_mentioned($detail[ban], $mentioned)) { ?>
CountOffenceCommentsSource IP
value="" /> ". $detail["offender"] . " (?)"; ?>

Ban the listed IP Addresses from connecting in future.

'.__('The worst-offender-radar is currently set to spot anyone that has sent more than '.$lower_limit.' spam messages. Currently there are no such spam-cannon owners filling up your database. Oh happy day!').'

'; echo '

'; _e('Please help improve this plugin by leaving '); $feedback = __("feedback and suggestions"); echo ''.$feedback.'

'; } } function make_comment_link($cid) { return "$cid"; } function can_ban() { $home_path = get_home_path(); return(is_writable($home_path.".htaccess")); } function is_banned($ip, $ip_list) { if (array_search($ip, $ip_list) === FALSE) return false; return true; } function check_if_all_previously_mentioned($candidates_list, $mentioned) { foreach ($candidates_list as $candidate) { if ($mentioned[$candidate] > 1) { return false; } } return true; } function annotate_banned($candidates_list, $mentioned) { global $keys; $result = array(); $user_list = load_user_ban(); foreach ($candidates_list as $candidate) { $classes[] = array(); if (is_banned($candidate, $user_list)) { $classes[] = "userban"; } if (sizeof($classes) == 0) { $classes[] = "noban"; } if ($mentioned[$candidate]>1) { $classes[] = "mentioned"; } $result[] = "$candidate"; } return $result; } /** * This function takes a comment and looks for any other * comments in the spam queue that have the same domain. */ function save_user_ban($list) { global $keys; update_option( $keys['user_ban_list'], $list ); update_option( $keys['user_ban_list_size'], sizeof($list)); } function load_user_ban() { global $keys; $list = get_option( $keys['user_ban_list'] ); if (!is_array($list)) return array(); return $list; } function reset_user_ban() { save_user_ban( array() ); } function debug($message) { global $keys; $debug = get_option( $keys['debug'] ); if (!is_array($debug)) $debug=array(); $debug[] = gmstrftime ("%a, %d %b %Y %T %Z", time ())." ".$message; update_option( $keys['debug'], $debug ); } function dump_debug() { global $keys; $debug = get_option( $keys['debug'] ); echo "
    "; echo("

    ".gmstrftime ("%a, %d %b %Y %T %Z", time ())." (now)

    "); foreach ($debug as $line) { echo("
  1. ".$line."
  2. "); } echo "
"; reset_debug(); } function reset_debug() { global $keys; $debug[] = gmstrftime ("%a, %d %b %Y %T %Z", time ())." reset"; update_option( $keys['debug'], $debug ); } /** * given an array of IP addresses this function will remove any bans which */ function remove_ban_from_file($ips, $section = "worst-offenders") { global $keys; // get the current ban strings from the htaccess file $currently_banned = extract_from_markers($home_path.".htaccess", $section); // remove the supplied IPs list from the current ban file $updated_currently_banned = remove_ban($ips, $currently_banned); // update the htaccess file insert_with_markers($home_path.".htaccess", $section, $updated_currently_banned); } /** * given an array of IP addresses this function will remove any bans which */ function remove_ban($ips, $currently_banned) { global $keys; // remove the supplied IPs list from the current ban file // then update the htaccess file $updated_currently_banned = remove_matches((array)$currently_banned, (array)$ips); return $updated_currently_banned; } /** * Given a list of ip addresses and a list of ban statements * remove any ban statments that match the listed IP addresses. * * This is more complete than using array_merge to remove a ban * because it works with partial IP addresses, so for example, removing * a ban on "192.168.0" would find all matcing Ip addreses such as * "192.168.0.100" and "192.168.0.234" - it's a blanket removal. */ function remove_matches($haystack, $needles) { $result = array(); //$discard = array(); foreach ($haystack as $ban) { $keep_it_in = true; foreach((array)$needles as $ip) { if (strstr($ban, $ip) or ($ban==$ip)) { $keep_it_in = false; } } if ($keep_it_in) { $result[] = $ban; } //else { // $discard[] = $ban; //} } //debug("keeping ".implode(", ",$result)); //debug("discarding ".implode(", ",$discard)); return $result; } function banSelected($selected_ips, $offender) { global $keys; //debug("Banning ".$offender." from ".sizeof($selected_ips)); $banned = array(); $doBan = isset($_POST['worstoffenders_ban']); $today = date("Y-m-d H:m:s"); if ($doBan) { // fixme? - there is a small window here where a banned IP address could poke // spam through. foreach ($selected_ips as $ip) { $banned[] = "Deny from ".$ip; } $previously_banned = load_user_ban(); $now_banned = array_merge((array)$previously_banned, (array)$selected_ips); $now_banned = array_unique($now_banned); save_user_ban( $now_banned ); $user_ban_list_max_size = get_option($keys["user_ban_list_max_size"]); $ban_size = wo_invoke_ban($banned, $user_ban_list_max_size, "worst-offenders", false, $selected_ips); $updates[] = 'In total, '.$ban_size.' IP addresses are now banned ('.sizeof($banned).' added).'; } return $updates; } function check_real_url($str) { return preg_match( '/^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}'.'((:[0-9]{1,5})?\/.*)?$/i',$str); } ?>