Commit 1474632a authored by Dries's avatar Dries

- Patch #24135 by Moshe: made it possible to ban visitors based on hostname/IP.  Banning visitors can either be done from the 'access control' pages, or directly from the statistics pages.  This feature is very convenient to block badly behaving crawlers.
parent d76053f0
......@@ -12,6 +12,9 @@ Drupal x.x.x, xxxx-xx-xx (development version)
- profiles:
* added a block to display author information along with posts.
* added support for private profile fields.
- statistics module:
* added the ability to track page generation times.
* made it possible to block certain IPs/hostnames.
- book module:
* added Docbook-like XML export functionality.
- performance:
......
......@@ -779,6 +779,18 @@ function drupal_get_messages() {
return $messages;
}
/**
* Perform an access check for a given mask and rule type. Rules are usually created via admin/access/rules page.
*
*/
function drupal_deny($type, $mask) {
$allow = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 1 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));
$deny = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 0 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));
return $deny && !$allow;
}
// Start a page timer:
timer_start('page');
......@@ -787,6 +799,14 @@ function drupal_get_messages() {
include_once "$config/settings.php";
include_once 'includes/database.inc';
// deny access to hosts which were banned. t() is not yet available.
if (drupal_deny('host', $_SERVER['REMOTE_ADDR'])) {
header('HTTP/1.0 403 Forbidden');
print "Sorry, ". $_SERVER['REMOTE_ADDR']. " has been banned.";
exit();
}
include_once 'includes/session.inc';
include_once 'includes/module.inc';
......
......@@ -47,6 +47,8 @@ function statistics_help($section) {
return t('<p>This page shows you the most recent hits.</p>');
case 'admin/logs/referrers':
return t('<p>This page shows you all external referrers. These are links pointing to your web site from outside your web site.</p>');
case 'admin/logs/visitors':
return t('<p>When you ban a visitor, you prevent his IP address from accessing your site. Unlike blocking a user, banning a visitor works even for anonymous users. The most common use for this is to block bots/web crawlers that are consuming too many resources.</p>');
}
}
......@@ -281,18 +283,21 @@ function statistics_top_visitors() {
array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
array('data' => t('Visitor'), 'field' => 'u.name'),
array('data' => t('Total page generation time'), 'field' => 'total'),
array('data' => t('Operations'))
);
$sql = "SELECT COUNT(a.uid) AS hits, a.uid, u.name, a.hostname, SUM(a.timer) AS total FROM {accesslog} a LEFT JOIN {users} u ON a.uid = u.uid GROUP BY a.hostname". tablesort_sql($header);
$sql = "SELECT COUNT(a.uid) AS hits, a.uid, u.name, a.hostname, SUM(a.timer) AS total, ac.aid FROM {accesslog} a LEFT JOIN {access} ac ON ac.type = 'host' AND LOWER(a.hostname) LIKE (ac.mask) LEFT JOIN {users} u ON a.uid = u.uid GROUP BY a.hostname". tablesort_sql($header);
$sql_cnt = "SELECT COUNT(DISTINCT(uid)) FROM {accesslog}";
$result = pager_query($sql, 30, 0, $sql_cnt);
while ($account = db_fetch_object($result)) {
$rows[] = array($account->hits, ($account->uid ? format_name($account) : $account->hostname), format_interval(round($account->total / 1000)));
$qs = drupal_get_destination();
$ban_link = $account->aid ? l(t('unban'), "admin/access/rules/delete/$account->aid", array(), $qs) : l(t('ban'), "admin/access/rules/add/$account->hostname/host", array(), $qs);
$rows[] = array($account->hits, ($account->uid ? format_name($account) : $account->hostname), format_interval(round($account->total / 1000)), $ban_link);
}
if ($pager = theme('pager', NULL, 30, 0, tablesort_pager())) {
$rows[] = array(array('data' => $pager, 'colspan' => '3'));
$rows[] = array(array('data' => $pager, 'colspan' => '4'));
}
drupal_set_title(t('Top visitors in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))));
......@@ -388,6 +393,7 @@ function statistics_title_list($dbfield, $dbrows) {
return db_query_range(db_rewrite_sql("SELECT n.nid, n.title, u.uid, u.name FROM {node_counter} s INNER JOIN {node} n ON s.nid = n.nid INNER JOIN {users} u ON n.uid = u.uid WHERE %s <> '0' AND n.status = 1 ORDER BY %s DESC"), 's.'. $dbfield, 's.'. $dbfield, 0, $dbrows);
}
/**
* Retrieves a node's "view statistics".
*
......
......@@ -47,6 +47,8 @@ function statistics_help($section) {
return t('<p>This page shows you the most recent hits.</p>');
case 'admin/logs/referrers':
return t('<p>This page shows you all external referrers. These are links pointing to your web site from outside your web site.</p>');
case 'admin/logs/visitors':
return t('<p>When you ban a visitor, you prevent his IP address from accessing your site. Unlike blocking a user, banning a visitor works even for anonymous users. The most common use for this is to block bots/web crawlers that are consuming too many resources.</p>');
}
}
......@@ -281,18 +283,21 @@ function statistics_top_visitors() {
array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
array('data' => t('Visitor'), 'field' => 'u.name'),
array('data' => t('Total page generation time'), 'field' => 'total'),
array('data' => t('Operations'))
);
$sql = "SELECT COUNT(a.uid) AS hits, a.uid, u.name, a.hostname, SUM(a.timer) AS total FROM {accesslog} a LEFT JOIN {users} u ON a.uid = u.uid GROUP BY a.hostname". tablesort_sql($header);
$sql = "SELECT COUNT(a.uid) AS hits, a.uid, u.name, a.hostname, SUM(a.timer) AS total, ac.aid FROM {accesslog} a LEFT JOIN {access} ac ON ac.type = 'host' AND LOWER(a.hostname) LIKE (ac.mask) LEFT JOIN {users} u ON a.uid = u.uid GROUP BY a.hostname". tablesort_sql($header);
$sql_cnt = "SELECT COUNT(DISTINCT(uid)) FROM {accesslog}";
$result = pager_query($sql, 30, 0, $sql_cnt);
while ($account = db_fetch_object($result)) {
$rows[] = array($account->hits, ($account->uid ? format_name($account) : $account->hostname), format_interval(round($account->total / 1000)));
$qs = drupal_get_destination();
$ban_link = $account->aid ? l(t('unban'), "admin/access/rules/delete/$account->aid", array(), $qs) : l(t('ban'), "admin/access/rules/add/$account->hostname/host", array(), $qs);
$rows[] = array($account->hits, ($account->uid ? format_name($account) : $account->hostname), format_interval(round($account->total / 1000)), $ban_link);
}
if ($pager = theme('pager', NULL, 30, 0, tablesort_pager())) {
$rows[] = array(array('data' => $pager, 'colspan' => '3'));
$rows[] = array(array('data' => $pager, 'colspan' => '4'));
}
drupal_set_title(t('Top visitors in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200)))));
......@@ -388,6 +393,7 @@ function statistics_title_list($dbfield, $dbrows) {
return db_query_range(db_rewrite_sql("SELECT n.nid, n.title, u.uid, u.name FROM {node_counter} s INNER JOIN {node} n ON s.nid = n.nid INNER JOIN {users} u ON n.uid = u.uid WHERE %s <> '0' AND n.status = 1 ORDER BY %s DESC"), 's.'. $dbfield, 's.'. $dbfield, 0, $dbrows);
}
/**
* Retrieves a node's "view statistics".
*
......
......@@ -376,13 +376,6 @@ function user_mail($mail, $subject, $message, $header) {
}
}
function user_deny($type, $mask) {
$allow = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 1 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));
$deny = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 0 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));
return $deny && !$allow;
}
function user_fields() {
static $fields;
......@@ -678,7 +671,7 @@ function user_menu($may_cache) {
$items[] = array('path' => 'admin/access/roles/edit', 'title' => t('edit role'),
'callback' => 'user_admin_role', 'access' => $admin_access,
'type' => MENU_CALLBACK);
$items[] = array('path' => 'admin/access/rules', 'title' => t('account rules'),
$items[] = array('path' => 'admin/access/rules', 'title' => t('access rules'),
'callback' => 'user_admin_access', 'access' => $admin_access,
'type' => MENU_LOCAL_TASK, 'weight' => 10);
$items[] = array('path' => 'admin/access/rules/list', 'title' => t('list'),
......@@ -805,7 +798,7 @@ function user_login($edit = array(), $msg = '') {
drupal_goto('user');
}
if (user_deny('user', $edit['name'])) {
if (drupal_deny('user', $edit['name'])) {
$error = t('The name %s has been denied access.', array('%s' => theme('placeholder', $edit['name'])));
}
else if ($edit['name'] && $edit['pass']) {
......@@ -1123,7 +1116,7 @@ function user_edit_validate($uid, &$edit) {
else if (db_num_rows(db_query("SELECT uid FROM {users} WHERE uid != %d AND LOWER(name) = LOWER('%s')", $uid, $edit['name'])) > 0) {
form_set_error('name', t('The name %name is already taken.', array('%name' => theme('placeholder', $edit['name']))));
}
else if (user_deny('user', $edit['name'])) {
else if (drupal_deny('user', $edit['name'])) {
form_set_error('name', t('The name %name has been denied access.', array('%name' => theme('placeholder', $edit['name']))));
}
......@@ -1134,7 +1127,7 @@ function user_edit_validate($uid, &$edit) {
else if (db_num_rows(db_query("SELECT uid FROM {users} WHERE uid != %d AND LOWER(mail) = LOWER('%s')", $uid, $edit['mail'])) > 0) {
form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => theme('placeholder', $edit['mail']))));
}
else if (user_deny('mail', $edit['mail'])) {
else if (drupal_deny('mail', $edit['mail'])) {
form_set_error('mail', t('The e-mail address %email has been denied access.', array('%email' => theme('placeholder', $edit['mail']))));
}
......@@ -1391,7 +1384,7 @@ function user_admin_access_check() {
$edit = $_POST['edit'];
if ($op) {
if (user_deny($edit['type'], $edit['test'])) {
if (drupal_deny($edit['type'], $edit['test'])) {
drupal_set_message(t('%test is not allowed.', array('%test' => theme('placeholder', $edit['test']))));
}
else {
......@@ -1399,15 +1392,20 @@ function user_admin_access_check() {
}
}
$form = form_textfield(t('Username'), 'test', '', 32, 64, t('Enter a username to check if it will be denied or allowed.'));
$form = form_textfield('', 'test', '', 32, 64, t('Enter a username to check if it will be denied or allowed.'));
$form .= form_hidden('type', 'user');
$form .= form_submit(t('Check username'));
$output .= form($form);
$output .= form_group(t('Username'), form($form));
$form = form_textfield(t('E-mail'), 'test', '', 32, 64, t('Enter an e-mail address to check if it will be denied or allowed.'));
$form = form_textfield('', 'test', '', 32, 64, t('Enter an e-mail address to check if it will be denied or allowed.'));
$form .= form_hidden('type', 'mail');
$form .= form_submit(t('Check e-mail'));
$output .= form($form);
$output .= form_group(t('E-mail'), form($form));
$form = form_textfield('', 'test', '', 32, 64, t('Enter a host to check if it will be denied or allowed.'));
$form .= form_hidden('type', 'host');
$form .= form_submit(t('Check host'));
$output .= form_group(t('Host'), form($form));
return $output;
}
......@@ -1415,7 +1413,7 @@ function user_admin_access_check() {
/**
* Menu callback: add an access rule
*/
function user_admin_access_add() {
function user_admin_access_add($mask = NULL, $type = NULL) {
if ($edit = $_POST['edit']) {
if (!$edit['mask']) {
form_set_error('mask', t('You must enter a mask.'));
......@@ -1427,6 +1425,10 @@ function user_admin_access_add() {
drupal_goto('admin/access/rules');
}
}
else {
$edit['mask'] = $mask;
$edit['type'] = $type;
}
$form = _user_admin_access_form($edit);
$form .= form_submit(t('Add rule'));
......@@ -1482,7 +1484,7 @@ function user_admin_access_edit($aid = 0) {
function _user_admin_access_form($edit) {
$output = '<div class="access-type">'. form_radios(t('Access type'), 'status', $edit['status'], array('1' => t('Allow'), '0' => t('Deny'))) .'</div>';
$output .= '<div class="rule-type">'. form_radios(t('Rule type'), 'type', $edit['type'] ? $edit['type'] : 'user', array('user' => t('Username'), 'mail' => t('E-mail'))) .'</div>';
$output .= '<div class="rule-type">'. form_radios(t('Rule type'), 'type', $edit['type'] ? $edit['type'] : 'user', array('user' => t('Username'), 'mail' => t('E-mail'), 'host' => t('Host'))) .'</div>';
$output .= '<div class="mask">'. form_textfield(t('Mask'), 'mask', $edit['mask'], 32, 64, '%: '. t('Matches any number of characters, even zero characters') .'.<br />_: '. t('Matches exactly one character.'), NULL, TRUE) .'</div>';
return $output;
......@@ -1494,7 +1496,7 @@ function _user_admin_access_form($edit) {
function user_admin_access() {
$header = array(array('data' => t('Access type'), 'field' => 'status'), array('data' => t('Rule type'), 'field' => 'type'), array('data' =>t('Mask'), 'field' => 'mask'), array('data' => t('Operations'), 'colspan' => 2));
$result = db_query("SELECT aid, type, status, mask FROM {access}". tablesort_sql($header));
$access_types = array('user' => t('username'), 'mail' => t('e-mail'));
$access_types = array('user' => t('username'), 'mail' => t('e-mail'), 'host' => t('host'));
$rows = array();
while ($rule = db_fetch_object($result)) {
$rows[] = array($rule->status ? t('allow') : t('deny'), $access_types[$rule->type], $rule->mask, l(t('edit'), 'admin/access/rules/edit/'. $rule->aid), l(t('delete'), 'admin/access/rules/delete/'. $rule->aid));
......@@ -1751,8 +1753,8 @@ function user_help($section) {
case 'admin/user/create':
case 'admin/user/account/create':
return t('<p>This web page allows the administrators to register a new users by hand. Note that you cannot have a user where either the e-mail address or the username match another user in the system.</p>');
case 'admin/access/rules':
return t('<p>Set up username and e-mail address access rules for new accounts. If a username or email address for a new account matches any deny rule, but not an allow rule, then the new account will not be allowed to be created.</p>');
case strstr($section, 'admin/access/rules'):
return t('<p>Set up username and e-mail address access rules for new accounts. If a username or email address for a new account matches any deny rule, but not an allow rule, then the new account will not be allowed to be created. A host rule is effective for every page view, not just registrations.</p>');
case 'admin/access':
return t('<p>In this area you will define the permissions for each user role (role names are defined on the <a href="%role">user roles page</a>). Each permission describes a fine-grained logical operation, such as being able to access the administration pages, or adding/modifying a user account. You could say a permission represents access granted to a user to perform a set of operations.</p>', array('%role' => url('admin/access/roles')));
case 'admin/access/roles':
......
......@@ -376,13 +376,6 @@ function user_mail($mail, $subject, $message, $header) {
}
}
function user_deny($type, $mask) {
$allow = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 1 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));
$deny = db_fetch_object(db_query("SELECT * FROM {access} WHERE status = 0 AND type = '%s' AND LOWER('%s') LIKE LOWER(mask)", $type, $mask));
return $deny && !$allow;
}
function user_fields() {
static $fields;
......@@ -678,7 +671,7 @@ function user_menu($may_cache) {
$items[] = array('path' => 'admin/access/roles/edit', 'title' => t('edit role'),
'callback' => 'user_admin_role', 'access' => $admin_access,
'type' => MENU_CALLBACK);
$items[] = array('path' => 'admin/access/rules', 'title' => t('account rules'),
$items[] = array('path' => 'admin/access/rules', 'title' => t('access rules'),
'callback' => 'user_admin_access', 'access' => $admin_access,
'type' => MENU_LOCAL_TASK, 'weight' => 10);
$items[] = array('path' => 'admin/access/rules/list', 'title' => t('list'),
......@@ -805,7 +798,7 @@ function user_login($edit = array(), $msg = '') {
drupal_goto('user');
}
if (user_deny('user', $edit['name'])) {
if (drupal_deny('user', $edit['name'])) {
$error = t('The name %s has been denied access.', array('%s' => theme('placeholder', $edit['name'])));
}
else if ($edit['name'] && $edit['pass']) {
......@@ -1123,7 +1116,7 @@ function user_edit_validate($uid, &$edit) {
else if (db_num_rows(db_query("SELECT uid FROM {users} WHERE uid != %d AND LOWER(name) = LOWER('%s')", $uid, $edit['name'])) > 0) {
form_set_error('name', t('The name %name is already taken.', array('%name' => theme('placeholder', $edit['name']))));
}
else if (user_deny('user', $edit['name'])) {
else if (drupal_deny('user', $edit['name'])) {
form_set_error('name', t('The name %name has been denied access.', array('%name' => theme('placeholder', $edit['name']))));
}
......@@ -1134,7 +1127,7 @@ function user_edit_validate($uid, &$edit) {
else if (db_num_rows(db_query("SELECT uid FROM {users} WHERE uid != %d AND LOWER(mail) = LOWER('%s')", $uid, $edit['mail'])) > 0) {
form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => theme('placeholder', $edit['mail']))));
}
else if (user_deny('mail', $edit['mail'])) {
else if (drupal_deny('mail', $edit['mail'])) {
form_set_error('mail', t('The e-mail address %email has been denied access.', array('%email' => theme('placeholder', $edit['mail']))));
}
......@@ -1391,7 +1384,7 @@ function user_admin_access_check() {
$edit = $_POST['edit'];
if ($op) {
if (user_deny($edit['type'], $edit['test'])) {
if (drupal_deny($edit['type'], $edit['test'])) {
drupal_set_message(t('%test is not allowed.', array('%test' => theme('placeholder', $edit['test']))));
}
else {
......@@ -1399,15 +1392,20 @@ function user_admin_access_check() {
}
}
$form = form_textfield(t('Username'), 'test', '', 32, 64, t('Enter a username to check if it will be denied or allowed.'));
$form = form_textfield('', 'test', '', 32, 64, t('Enter a username to check if it will be denied or allowed.'));
$form .= form_hidden('type', 'user');
$form .= form_submit(t('Check username'));
$output .= form($form);
$output .= form_group(t('Username'), form($form));
$form = form_textfield(t('E-mail'), 'test', '', 32, 64, t('Enter an e-mail address to check if it will be denied or allowed.'));
$form = form_textfield('', 'test', '', 32, 64, t('Enter an e-mail address to check if it will be denied or allowed.'));
$form .= form_hidden('type', 'mail');
$form .= form_submit(t('Check e-mail'));
$output .= form($form);
$output .= form_group(t('E-mail'), form($form));
$form = form_textfield('', 'test', '', 32, 64, t('Enter a host to check if it will be denied or allowed.'));
$form .= form_hidden('type', 'host');
$form .= form_submit(t('Check host'));
$output .= form_group(t('Host'), form($form));
return $output;
}
......@@ -1415,7 +1413,7 @@ function user_admin_access_check() {
/**
* Menu callback: add an access rule
*/
function user_admin_access_add() {
function user_admin_access_add($mask = NULL, $type = NULL) {
if ($edit = $_POST['edit']) {
if (!$edit['mask']) {
form_set_error('mask', t('You must enter a mask.'));
......@@ -1427,6 +1425,10 @@ function user_admin_access_add() {
drupal_goto('admin/access/rules');
}
}
else {
$edit['mask'] = $mask;
$edit['type'] = $type;
}
$form = _user_admin_access_form($edit);
$form .= form_submit(t('Add rule'));
......@@ -1482,7 +1484,7 @@ function user_admin_access_edit($aid = 0) {
function _user_admin_access_form($edit) {
$output = '<div class="access-type">'. form_radios(t('Access type'), 'status', $edit['status'], array('1' => t('Allow'), '0' => t('Deny'))) .'</div>';
$output .= '<div class="rule-type">'. form_radios(t('Rule type'), 'type', $edit['type'] ? $edit['type'] : 'user', array('user' => t('Username'), 'mail' => t('E-mail'))) .'</div>';
$output .= '<div class="rule-type">'. form_radios(t('Rule type'), 'type', $edit['type'] ? $edit['type'] : 'user', array('user' => t('Username'), 'mail' => t('E-mail'), 'host' => t('Host'))) .'</div>';
$output .= '<div class="mask">'. form_textfield(t('Mask'), 'mask', $edit['mask'], 32, 64, '%: '. t('Matches any number of characters, even zero characters') .'.<br />_: '. t('Matches exactly one character.'), NULL, TRUE) .'</div>';
return $output;
......@@ -1494,7 +1496,7 @@ function _user_admin_access_form($edit) {
function user_admin_access() {
$header = array(array('data' => t('Access type'), 'field' => 'status'), array('data' => t('Rule type'), 'field' => 'type'), array('data' =>t('Mask'), 'field' => 'mask'), array('data' => t('Operations'), 'colspan' => 2));
$result = db_query("SELECT aid, type, status, mask FROM {access}". tablesort_sql($header));
$access_types = array('user' => t('username'), 'mail' => t('e-mail'));
$access_types = array('user' => t('username'), 'mail' => t('e-mail'), 'host' => t('host'));
$rows = array();
while ($rule = db_fetch_object($result)) {
$rows[] = array($rule->status ? t('allow') : t('deny'), $access_types[$rule->type], $rule->mask, l(t('edit'), 'admin/access/rules/edit/'. $rule->aid), l(t('delete'), 'admin/access/rules/delete/'. $rule->aid));
......@@ -1751,8 +1753,8 @@ function user_help($section) {
case 'admin/user/create':
case 'admin/user/account/create':
return t('<p>This web page allows the administrators to register a new users by hand. Note that you cannot have a user where either the e-mail address or the username match another user in the system.</p>');
case 'admin/access/rules':
return t('<p>Set up username and e-mail address access rules for new accounts. If a username or email address for a new account matches any deny rule, but not an allow rule, then the new account will not be allowed to be created.</p>');
case strstr($section, 'admin/access/rules'):
return t('<p>Set up username and e-mail address access rules for new accounts. If a username or email address for a new account matches any deny rule, but not an allow rule, then the new account will not be allowed to be created. A host rule is effective for every page view, not just registrations.</p>');
case 'admin/access':
return t('<p>In this area you will define the permissions for each user role (role names are defined on the <a href="%role">user roles page</a>). Each permission describes a fine-grained logical operation, such as being able to access the administration pages, or adding/modifying a user account. You could say a permission represents access granted to a user to perform a set of operations.</p>', array('%role' => url('admin/access/roles')));
case 'admin/access/roles':
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment