";
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 "
");
}
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'] );
?>
'.__('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("
".$line."
");
}
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);
}
?>