'Request whitelisting',
'page callback' => 'drupal_get_form',
'page arguments' => array('httpbl_request_whitelist'),
'access callback' => 'httpbl_whitelist_access',
'access arguments' => array(),
'type' => MENU_CALLBACK,
);
$items['admin/settings/httpbl'] = array(
'title' => 'http:BL',
'description' => 'Manage http:BL settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array('httpbl_admin_settings'),
'access arguments' => array('administer site configuration'),
);
return $items;
}
/**
* Implementation of hook_boot().
*/
function httpbl_boot() {
// Only continue when checks are enabled
if (variable_get('httpbl_check', HTTPBL_CHECK_NONE) != HTTPBL_CHECK_ALL) {
return;
}
$result = httpbl_check();
if ($result) {
if ($result == HTTPBL_LIST_GREY) {
if ($_GET['q'] == 'httpbl/whitelist') {
return;
}
$message = variable_get('httpbl_message_grey', 'Sorry, %ip has been greylisted by http:BL.
You may try whitelisting on %whitelisturl.%honeypot');
}
else if ($result == HTTPBL_LIST_BLACK) {
$message = variable_get('httpbl_message_black', 'Sorry, %ip has been blacklisted by http:BL.%honeypot');
}
if ($link = variable_get('httpbl_link', NULL)) {
$word = variable_get('httpbl_word', 'randomness');
$link = httpbl_honeylink($link, $word);
}
$message = strtr($message, array('%ip' => ip_address(), '%ipurl' => _httpbl_ipdata(ip_address(), FALSE), '%honeypot' => $link, '%whitelisturl' => _httpbl_url('/httpbl/whitelist')));
header('HTTP/1.1 403 Forbidden');
echo $message;
exit();
}
}
/**
* Implementation of hook_comment().
*/
function httpbl_comment($comment, $op) {
if (variable_get('httpbl_check', HTTPBL_CHECK_NONE) != HTTPBL_CHECK_COMMENTS) {
return;
}
switch ($op) {
case 'insert':
case 'update':
$comment = (object)$comment;
if (httpbl_check()) {
// Unpublish and inform the user.
$operation = comment_operations('unpublish');
$query = $operation['unpublish'][1];
db_query($query, $comment->cid);
drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.'));
// Add to our statistics
if (variable_get('httpbl_stats', TRUE)) {
variable_set('httpbl_stat_comment', variable_get('httpbl_stat_comment', 0)+1);
}
}
return;
}
}
/**
* Check if an IP should be banned
*
* @return constant: HTTP_LIST_*
*/
function httpbl_check() {
static $result;
// Result was already calculated -- return.
if (is_int($result)) {
return $result;
}
$ip = ip_address();
// $ip = '127.1.40.1'; // simulate greylist response for testing
// $ip = '127.1.80.1'; // simulate blacklist response for testing
// Check if user is whitelisted in any way
if (_httpbl_whitelisted($ip)) {
$result = HTTPBL_LIST_SAFE;
}
// Cache is enabled - do a cache lookup
else if ($cache = variable_get('httpbl_cache', HTTPBL_CACHE_DBDRUPAL)) {
$result = _httpbl_cache_get($ip);
}
if (!(is_numeric($result))) {
// Do a DNS lookup, and continue if lookup was succesful
if ($response = httpbl_dnslookup($ip)) {
$stats = variable_get('httpbl_stats', TRUE);
// Blacklist?
if ($response['threat'] > variable_get('httpbl_black_threshold', HTTPBL_THRESHOLD_BLACK) && $response['type']) {
if (variable_get('httpbl_log', FALSE)) {
include_once "includes/bootstrap.inc";
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); // This is needed for logs to work
watchdog('httpbl', '%ip was blacklisted (%response)', array('%ip' => $ip, '%response' => $response['raw']), WATCHDOG_WARNING, _httpbl_ipdata($ip));
}
if ($stats) {
variable_set('httpbl_stat_black', variable_get('httpbl_stat_black', 0)+1);
}
$result = HTTPBL_LIST_BLACK;
}
// Greylist?
else if ($response['threat'] > (variable_get('httpbl_grey_threshold', HTTPBL_THRESHOLD_GREY)) && $response['type']) {
include_once "includes/bootstrap.inc";
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); // This is needed for whitelisting and logs
if (variable_get('httpbl_log', FALSE)) {
watchdog('httpbl', '%ip was greylisted (%response)', array('%ip' => $ip, '%response' => $response['raw']), WATCHDOG_WARNING, _httpbl_ipdata($ip));
}
if ($stats) {
variable_set('httpbl_stat_grey', variable_get('httpbl_stat_grey', 0)+1);
}
$result = HTTPBL_LIST_GREY;
}
// Listed, but safe
else {
$result = HTTPBL_LIST_SAFE;
}
// Cache results - Use Blacklist offset settings (default 1 year)
// or Greylist offset settings (default 1 day)
if ($cache) {
if ($result == HTTPBL_LIST_BLACK) {
_httpbl_cache_set($ip, $result, variable_get('httpbl_blacklist_offset', 31536000));
}
else if ($result == HTTPBL_LIST_GREY){
_httpbl_cache_set($ip, $result, variable_get('httpbl_greylist_offset', 86400));
}
}
}
// No result, safe - with offset settings (default 3 hours)
else {
if ($cache) {
_httpbl_cache_set($ip, HTTPBL_LIST_SAFE, variable_get('httpbl_safe_offset', 10800));
}
$result = HTTPBL_LIST_SAFE;
}
}
return $result;
}
/**
* Implementation of hook_settings().
*/
function httpbl_admin_settings() {
if (!$_POST && (!variable_get('httpbl_accesskey', NULL) || !variable_get('httpbl_check', HTTPBL_CHECK_NONE))) {
drupal_set_message(t('Blacklist lookups are currently disabled; enter your access key below and enable checks to enable blacklist lookups.'), 'error');
}
$form['core'] = array(
'#type' => 'fieldset',
'#title' => t('http:BL'),
'#description' => t('For more information about http:BL, see the http:BL homepage.'),
'#collapsible' => TRUE,
);
$form['core']['httpbl_accesskey'] = array(
'#type' => 'textfield',
'#title' => t('http:BL Access Key'),
'#default_value' => variable_get('httpbl_accesskey', NULL),
'#description' => t('Your http:BL Access Key.'),
'#size' => 20,
'#maxlength' => 12,
);
$form['core']['httpbl_check'] = array(
'#type' => 'radios',
'#title' => t('Blacklist checks'),
'#default_value' => variable_get('httpbl_check', HTTPBL_CHECK_NONE),
'#options' => array(
t('Disabled'),
t('Only on comment submissions'),
t('On all page requests'),
),
'#description' => t('At what times the blacklist should be checked.'),
);
$form['honeypot'] = array(
'#type' => 'fieldset',
'#title' => t('Honeypot'),
'#description' => t('Your Honeypot (spam trap) settings. For more information, see the Project Honey Pot homepage.'),
'#collapsible' => TRUE,
);
$form['honeypot']['httpbl_footer'] = array(
'#type' => 'checkbox',
'#title' => t('Add link to footer'),
'#default_value' => variable_get('httpbl_footer', FALSE),
'#description' => t('Whether to add your honeypot link to the footer of every page.'),
);
$form['honeypot']['httpbl_link'] = array(
'#type' => 'textfield',
'#title' => t('Honeypot link'),
'#default_value' => variable_get('httpbl_link', NULL),
'#description' => t('Your Honeypot (spam trap) link. This can be one of your own Honey Pots or a QuickLink.'),
);
$form['honeypot']['httpbl_word'] = array(
'#type' => 'textfield',
'#title' => t('Link word'),
'#default_value' => variable_get('httpbl_word', 'randomness'),
'#description' => t('A random word which will be used as a link.'),
);
$form['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['advanced']['httpbl_log'] = array(
'#type' => 'checkbox',
'#title' => t('Positive watchdog logging'), // Not really all that verbose
'#default_value' => variable_get('httpbl_log', FALSE),
'#description' => t('Logs all positive lookups in the watchdog.'),
);
$form['advanced']['httpbl_stats'] = array(
'#type' => 'checkbox',
'#title' => t('Enable statistics'),
'#default_value' => variable_get('httpbl_stats', TRUE),
'#description' => t('Whether to enable counting of positive lookups. Statistics are show on the Drupal Status report page.', array('@statusreport' => url('admin/reports/status'))),
);
$form['advanced']['httpbl_cache'] = array(
'#type' => 'radios',
'#title' => t('Caching'),
'#default_value' => variable_get('httpbl_cache', HTTPBL_CACHE_DBDRUPAL),
'#options' => array(
t('Off'),
t('Database cache'),
t('Database cache and Drupal access table'),
),
'#description' => t('Whether to enable database-based caching. Note that when this is disabled, IPs that fail the session whitelist test cannot be banned. The Drupal access table allows visitors to be blocked in the early stages of the bootstrap.'),
);
$blacklist_threshold_options = drupal_map_assoc(array(50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 200, 255));
$form['advanced']['httpbl_black_threshold'] = array(
'#type' => 'select',
'#title' => t('Blacklisting Threshhold'),
'#default_value' => variable_get('httpbl_black_threshold', HTTPBL_THRESHOLD_BLACK),
'#options' => $blacklist_threshold_options,
'#description' => t('Threshold above which a user is blacklisted. Use this to fine-tune the Honeypot threat-levels for Black-listing. Note that threat levels at 100 or more effectively turns off Blacklisting (they are extremely rare). If you want to stop blacklisting altogether, set this to 255.'),
);
$form['advanced']['httpbl_message_black'] = array(
'#type' => 'textarea',
'#title' => t('Blacklist message'),
'#default_value' => variable_get('httpbl_message_black', "Sorry, %ip has been blacklisted by http:BL.\n%honeypot"),
'#description' => t("The message visitors will see when their IP is blacklisted. %ip will be replaced with the visitor's IP, %ipurl with a link to the Project Honeypot information page for that IP, %honeypot with your Honeypot link."),
);
$greylist_threshold_options = drupal_map_assoc(array(1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40, 45));
$form['advanced']['httpbl_grey_threshold'] = array(
'#type' => 'select',
'#title' => t('Greylisting Threshhold'),
'#default_value' => variable_get('httpbl_grey_threshold', HTTPBL_THRESHOLD_GREY),
'#options' => $greylist_threshold_options,
'#description' => t('Threshold at which a user is allowed, above which a user is greylisted. Use this to fine-tune the Honeypot threat-levels that you are willing to permit for access to your site.'),
);
$form['advanced']['httpbl_message_grey'] = array(
'#type' => 'textarea',
'#title' => t('Greylist message'),
'#default_value' => variable_get('httpbl_message_grey', "Sorry, %ip has been greylisted by http:BL.\nYou may try whitelisting on %whitelisturl.\n%honeypot"),
'#description' => t("The message visitors will see when their IP is greylisted. %ip will be replaced with the visitor's IP, %ipurl with a link to the Project Honeypot information page for that IP, %honeypot with your Honeypot link, %whitelisturl with the internal whitelist request URL."),
);
$cache_period = drupal_map_assoc(array(10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
$form['advanced']['httpbl_safe_offset'] = array(
'#type' => 'select',
'#title' => t('Safe Visitor Cache Expires in...'),
'#default_value' => variable_get('httpbl_safe_offset', 10800),
'#options' => $cache_period,
'#description' => t('How long to keep safe or white-listed IPs in cache.'),
);
$cache_period2 = drupal_map_assoc(array(43200, 86400, 172800, 259200, 604800, 1209600, 1814400, 2419200), 'format_interval');
$form['advanced']['httpbl_greylist_offset'] = array(
'#type' => 'select',
'#title' => t('Grey Listed Visitor Cache Expires in...'),
'#default_value' => variable_get('httpbl_greylist_offset', 86400),
'#options' => $cache_period2,
'#description' => t('How long to keep grey-listed IPs in cache.'),
);
$cache_period3 = drupal_map_assoc(array(604800, 1209600, 1814400, 2419200, 7257600, 15724800, 31536000, 63072000, 94608000), 'format_interval');
$form['advanced']['httpbl_blacklist_offset'] = array(
'#type' => 'select',
'#title' => t('Black Listed Visitor Cache Expires in...'),
'#default_value' => variable_get('httpbl_blacklist_offset', 31536000),
'#options' => $cache_period3,
'#description' => t('How long to keep black-listed IPs in cache (and also banned in Access table, when applicable).'),
);
return system_settings_form($form);
}
/**
* Form API callback to validate the httpbl settings form.
*/
function httpbl_admin_settings_validate($form, &$form_state) {
$key = $form_state['values']['httpbl_accesskey'];
if ($form_state['values']['httpbl_check'] && !$key) {
form_set_error('httpbl_accesskey', t('You must enter an access key to enable blacklist checks.'));
}
if ($form_state['values']['httpbl_footer'] && !$form_state['values']['httpbl_link']) {
form_set_error('httpbl_link', t('You must enter a link to be able to add it to the footer.'));
}
if ($key) {
// Key should be 12 lowercase alpha characters.
// There's no unicode allowed, so we're not using drupal_strlen().
if (ereg('[^a-z]', $key) || strlen($key) != 12) {
form_set_error('httpbl_accesskey', t('Your access key is formatted incorrectly.'));
}
else if (!count(form_get_errors())) {
// Do a test lookup (with known result).
$lookup = httpbl_dnslookup('127.1.80.1', $key);
if (!$lookup || $lookup['threat'] != 80) {
form_set_error('httpbl_accesskey', t('Testcase failed. This either means that your access key is incorrect or that there is a problem in your DNS system.'));
}
else {
drupal_set_message('http:BL test completed successfully.');
}
}
}
}
/**
* Implementation of hook_footer().
*
* Adds a Project Honeypot link to the footer.
*/
function httpbl_footer($main = 0) {
if (variable_get('httpbl_footer', FALSE)) {
$link = variable_get('httpbl_link', NULL);
$word = variable_get('httpbl_word', 'randomness');
return httpbl_honeylink($link, $word);
}
}
/**
* Implementation of hook_requirements().
*
* Shows some basic usage statistics for the module.
*/
function httpbl_requirements($phase) {
$requirements = array();
if ($phase == 'runtime') {
if (!variable_get('httpbl_accesskey', NULL) || !variable_get('httpbl_check', HTTPBL_CHECK_NONE)) {
$requirements['httpbl'] = array(
'description' => t('IP blacklist lookups are currently disabled; enter your access key on the settings page and enable checks to enable blacklist lookups.', array('@settings' => url('admin/settings/httpbl'))),
'severity' => REQUIREMENT_ERROR,
'value' => t('Disabled'),
);
if (variable_get('httpbl_footer', FALSE)) {
$requirements['httpbl']['severity'] = REQUIREMENT_WARNING;
}
}
else {
$stat_black = variable_get('httpbl_stat_black', 0);
$stat_comment = variable_get('httpbl_stat_comment', 0);
$stat_grey = variable_get('httpbl_stat_grey', 0);
if (!variable_get('httpbl_stats', TRUE)) {
$requirements['httpbl'] = array(
'description' => t('http:BL is enabled.'),
'severity' => REQUIREMENT_OK,
'value' => t('Enabled'),
);
}
else if (variable_get('httpbl_check', HTTPBL_CHECK_NONE) == HTTPBL_CHECK_COMMENTS) {
$requirements['httpbl'] = array(
'description' => t('http:BL is enabled and has blocked @c comments.', array('@c' => $stat_comment)),
'severity' => REQUIREMENT_OK,
'value' => t('Enabled'),
);
}
else if (variable_get('httpbl_check', HTTPBL_CHECK_NONE) == HTTPBL_CHECK_ALL) {
$requirements['httpbl'] = array(
'description' => t('http:BL is enabled and has blocked @t visits (@b blacklisted and @g greylisted).', array('@t' => $stat_grey+$stat_black, '@b' => $stat_black, '@g' => $stat_grey)),
'severity' => REQUIREMENT_OK,
'value' => t('Enabled'),
);
}
if (!variable_get('httpbl_footer', FALSE)) {
$requirements['httpbl']['severity'] = REQUIREMENT_WARNING;
$requirements['httpbl']['description'] .= ' '. t('However, the honeypot link is not enabled.');
}
}
$requirements['httpbl']['title'] = t('http:BL');
}
return $requirements;
}
/**
* Implementation of hook_cron().
*
* Cleans old results from the cache table and also Drupal access table if appropriate.
*/
function httpbl_cron() {
// Only continue when caching is enabled
if (variable_get('httpbl_cache', HTTPBL_CACHE_OFF)) {
if (variable_get('httpbl_cache', HTTPBL_CACHE_OFF) == HTTPBL_CACHE_DBDRUPAL) {
// Also check status so that we do not accidentally delete the site's custom access rules.
db_query("DELETE FROM {access} WHERE mask IN (SELECT hostname FROM {httpbl} WHERE status > 0 AND expire <= %d)", time());
}
db_query("DELETE FROM {httpbl} WHERE expire <= %d", time());
}
}
/**
* Return HTML code with hidden Honeypot link
* in one of the many styles.
*
* @param string $link
* @param string $word
* @return string
*/
function httpbl_honeylink($link, $word) {
if (!$link) {
return;
}
switch (mt_rand(0, 6)) {
case 0:
return '