array( 'title' => t('Protected from spambot scans') ), ); } /** * Implements hook_menu(). */ function spambot_menu() { $items = array(); $items['admin/config/system/spambot'] = array( 'title' => 'Spambot', 'description' => 'Configure the spambot module', 'page callback' => 'drupal_get_form', 'page arguments' => array('spambot_settings_form'), 'access arguments' => array('administer site configuration'), 'file' => 'spambot.admin.inc', ); $items['user/%user/spambot'] = array( 'title' => 'Spam', 'page callback' => 'drupal_get_form', 'page arguments' => array('spambot_user_spam_admin_form', 1), 'access arguments' => array('administer users'), 'type' => MENU_LOCAL_TASK, 'file' => 'spambot.pages.inc', ); return $items; } /** * Implementation of hook_form_FORM_ID_alter() */ function spambot_form_user_register_form_alter(&$form, &$form_state, $form_id) { if (variable_get('spambot_user_register_protect', TRUE) && !user_access('administer users')) { $form['#validate'][] = 'spambot_user_register_validate'; } } /** * Validate the user_register form */ function spambot_user_register_validate($form, &$form_state) { $email_threshold = variable_get('spambot_criteria_email', 1); $username_threshold = variable_get('spambot_criteria_username', 0); $ip_threshold = variable_get('spambot_criteria_ip', 20); // Build request parameters according to the criteria to use $request = array(); if (!empty($form_state['values']['mail']) && $email_threshold > 0) { $request['email'] = $form_state['values']['mail']; } if (!empty($form_state['values']['name']) && $username_threshold > 0) { $request['username'] = $form_state['values']['name']; } if ($ip_threshold > 0) { $ip = ip_address(); // Don't check the loopback interface if ($ip != '127.0.0.1') { $request['ip'] = $ip; } } // Only do a remote API request if there is anything to check if (count($request)) { $data = array(); if (spambot_sfs_request($request, $data)) { $substitutions = array( '@email' => $form_state['values']['mail'], '%email' => $form_state['values']['mail'], '@username' => $form_state['values']['name'], '%username' => $form_state['values']['name'], '@ip' => ip_address(), '%ip' => ip_address(), ); $reasons = array(); if ($email_threshold > 0 && !empty($data['email']['appears']) && $data['email']['frequency'] >= $email_threshold) { form_set_error('mail', t(variable_get('spambot_blocked_message_email', t(SPAMBOT_DEFAULT_BLOCKED_MESSAGE)), $substitutions)); $reasons[] = t('email=@value', array('@value' => $request['email'])); } if ($username_threshold > 0 && !empty($data['username']['appears']) && $data['username']['frequency'] >= $username_threshold) { form_set_error('name', t(variable_get('spambot_blocked_message_username', t(SPAMBOT_DEFAULT_BLOCKED_MESSAGE)), $substitutions)); $reasons[] = t('username=@value', array('@value' => $request['username'])); } if ($ip_threshold > 0 && !empty($data['ip']['appears']) && $data['ip']['frequency'] >= $ip_threshold) { form_set_error('', t(variable_get('spambot_blocked_message_ip', t(SPAMBOT_DEFAULT_BLOCKED_MESSAGE)), $substitutions)); $reasons[] = t('ip=@value', array('@value' => $request['ip'])); } if (count($reasons)) { watchdog('spambot', 'Blocked registration: @reasons', array('@reasons' => join(',', $reasons))); // Slow them down if configured $delay = variable_get('spambot_blacklisted_delay', 0); if ($delay) { sleep($delay); } } } } } /** * Implementation of hook_node_insert * * Keeps table node_spambot up to date */ function spambot_node_insert($node) { db_insert('node_spambot')->fields(array('nid' => $node->nid, 'uid' => $node->uid, 'hostname' => ip_address()))->execute(); } /** * Implementation of hook_node_delete * * Keeps table node_spambot up to date */ function spambot_node_delete($node) { db_delete('node_spambot')->condition('nid', $node->nid)->execute(); } /** * Implementation of hook_cron */ function spambot_cron() { $limit = variable_get('spambot_cron_user_limit', 0); if ($limit) { $last_uid = variable_get('spambot_last_checked_uid', 0); if ($last_uid < 1) { // Skip scanning the first account $last_uid = 1; } $uids = db_select('users')->fields('users', array('uid')) ->condition('uid', $last_uid, '>')->orderBy('uid') ->range(0, $limit)->execute()->fetchCol(); $action = variable_get('spambot_spam_account_action', SPAMBOT_ACTION_NONE); foreach ($uids as $uid) { $account = user_load($uid); if ($account->status || variable_get('spambot_check_blocked_accounts', FALSE)) { $result = spambot_account_is_spammer($account); if ($result > 0) { $link = l(t('spammer'), 'user/' . $account->uid); switch (user_access('protected from spambot scans', $account) ? SPAMBOT_ACTION_NONE : $action) { case SPAMBOT_ACTION_BLOCK: if ($account->status) { user_save($account, array('status' => 0)); watchdog('spambot', 'Blocked spam account: @name <@email> (uid @uid)', array('@name' => $account->name, '@email' => $account->mail, '@uid' => $account->uid), WATCHDOG_NOTICE, $link); } else { // Don't block an already blocked account watchdog('spambot', t('Spam account already blocked: @name <@email> (uid @uid)', array('@name' => $account->name, '@email' => $account->mail, '@uid' => $account->uid)), array(), WATCHDOG_NOTICE, $link); } break; case SPAMBOT_ACTION_DELETE: user_delete($account->uid); watchdog('spambot', 'Deleted spam account: @name <@email> (uid @uid)', array('@name' => $account->name, '@email' => $account->mail, '@uid' => $account->uid), WATCHDOG_NOTICE, $link); break; default: watchdog('spambot', 'Found spam account: @name <@email> (uid @uid)', array('@name' => $account->name, '@email' => $account->mail, '@uid' => $account->uid), WATCHDOG_NOTICE, $link); break; } // Mark this uid as successfully checked variable_set('spambot_last_checked_uid', $uid); } else if ($result == 0) { // Mark this uid as successfully checked variable_set('spambot_last_checked_uid', $uid); } else if ($result < 0) { // Error contacting service, so pause processing break; } } } } } /** * Invoke www.stopforumspam.com's api * * @param $query * A keyed array of url parameters ie. array('email' => 'blah@blah.com') * @param $data * An array that will be filled with the data from www.stopforumspam.com. * * @return * TRUE on successful request (and $data will contain the data), FALSE if error * * $data should be an array of the following form: * Array * ( * [success] => 1 * [email] => Array * ( * [lastseen] => 2010-01-10 08:41:26 * [frequency] => 2 * [appears] => 1 * ) * * [username] => Array * ( * [frequency] => 0 * [appears] => 0 * ) * ) * */ function spambot_sfs_request($query, &$data) { // An empty request results in no match if (empty($query)) { return FALSE; } // Use php serialisation format $query['f'] = 'serial'; $url = 'http://www.stopforumspam.com/api?' . http_build_query($query, '', '&'); $result = drupal_http_request($url); if (!empty($result->code) && $result->code == 200 && empty($result->error) && !empty($result->data)) { $data = unserialize($result->data); if (!empty($data['success'])) { return TRUE; } else { watchdog('spambot', "Request unsuccessful: @url
\n@dump", array('@url' => $url, '@dump' => print_r($data, TRUE))); } } else { watchdog('spambot', "Error contacting service: @url
\n@dump", array('@url' => $url, '@dump' => print_r($result, TRUE))); } return FALSE; } /** * Checks an account to see if it's a spammer. * This one uses configurable automated criteria checking of email and username only * * @return * positive if spammer, 0 if not spammer, negative if error */ function spambot_account_is_spammer($account) { $email_threshold = variable_get('spambot_criteria_email', 1); $username_threshold = variable_get('spambot_criteria_username', 0); $ip_threshold = variable_get('spambot_criteria_ip', 20); // Build request parameters according to the criteria to use $request = array(); if (!empty($account->mail) && $email_threshold > 0) { $request['email'] = $account->mail; } if (!empty($account->name) && $username_threshold > 0) { $request['username'] = $account->name; } // Only do a remote API request if there is anything to check if (count($request)) { $data = array(); if (spambot_sfs_request($request, $data)) { if (($email_threshold > 0 && !empty($data['email']['appears']) && $data['email']['frequency'] >= $email_threshold) || ($username_threshold > 0 && !empty($data['username']['appears']) && $data['username']['frequency'] >= $username_threshold)) { return 1; } } else { // Return error return -1; } } // Now check IP's // If any IP matches the threshold, then flag as a spammer if ($ip_threshold > 0) { $ips = spambot_account_ip_addresses($account); foreach ($ips as $ip) { // Skip the loopback interface if ($ip == '127.0.0.1') { continue; } $request = array('ip' => $ip); $data = array(); if (spambot_sfs_request($request, $data)) { if (!empty($data['ip']['appears']) && $data['ip']['frequency'] >= $ip_threshold) { return 1; } } else { // Abort on error return -1; } } } // Return no match return 0; } /** * Retrieves a list of IP addresses for an account * * @param $account * Account to retrieve IP addresses for * * @return * An array of IP addresses, or an empty array if none found */ function spambot_account_ip_addresses($account) { $hostnames = array(); // Retrieve IPs from node_spambot table $items = db_select('node_spambot')->fields('node_spambot', array('hostname')) ->condition('uid', $account->uid, '=')->distinct()->execute()->fetchCol(); $hostnames = array_merge($hostnames, $items); // Retrieve IPs from any sessions which may still exist $items = db_select('sessions')->fields('sessions', array('hostname')) ->condition('uid', $account->uid, '=')->distinct()->execute()->fetchCol(); $hostnames = array_merge($hostnames, $items); // Retrieve IPs from comments if (module_exists('comment')) { $items = db_select('comment')->fields('comment', array('hostname')) ->condition('uid', $account->uid, '=')->distinct()->execute()->fetchCol(); $hostnames = array_merge($hostnames, $items); } // Retrieve IPs from statistics if (module_exists('statistics')) { $items = db_select('accesslog')->fields('accesslog', array('hostname')) ->condition('uid', $account->uid, '=')->distinct()->execute()->fetchCol(); $hostnames = array_merge($hostnames, $items); } // Retrieve IPs from user stats if (module_exists('user_stats')) { $items = db_select('user_stats_ips')->fields('user_stats_ips', array('ip_address')) ->condition('uid', $account->uid, '=')->distinct()->execute()->fetchCol(); $hostnames = array_merge($hostnames, $items); } $hostnames = array_unique($hostnames); return $hostnames; } /** * Reports an account as a spammer. Requires ip address and evidence of a single incident * * @param $account * Account to report * @param $ip * IP address to report * @param $evidence * Evidence to report * * @return * TRUE if successful, FALSE if error */ function spambot_report_account($account, $ip, $evidence) { $success = FALSE; $key = variable_get('spambot_sfs_api_key', FALSE); if ($key) { $query['api_key'] = $key; $query['email'] = $account->mail; $query['username'] = $account->name; $query['ip_addr'] = $ip; $query['evidence'] = $evidence; $url = 'http://www.stopforumspam.com/add.php?' . http_build_query($query, '', '&'); $result = drupal_http_request($url); if (!empty($result->code) && $result->code == 200 && !empty($result->data) && stripos($result->data, 'data submitted successfully') !== FALSE) { $success = TRUE; } else if (stripos($result->data, 'duplicate') !== FALSE) { // www.stopforumspam.com can return a 503 code with data = '
recent duplicate entry
' // which we will treat as successful. $success = TRUE; } else { watchdog('spambot', "Error reporting account: @url\n@dump", array('@url' => $url, '@dump' => print_r($result, TRUE))); } } return $success; }