Index: drupal/includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.186
diff -u -p -r1.186 bootstrap.inc
--- drupal/includes/bootstrap.inc 30 Aug 2007 16:09:50 -0000 1.186
+++ drupal/includes/bootstrap.inc 31 Aug 2007 19:12:46 -0000
@@ -798,37 +798,172 @@ function drupal_get_messages($type = NUL
}
/**
+ * Load the persistent access table.
+ *
+ * The access table is composed of values that have been saved in the table.
+ */
+function _drupal_access_init() {
+ // NOTE: caching the access rules improves performance by 5% when serving cached pages.
+ $accesses = array();
+ if ($cached = cache_get('accesses', 'cache')) {
+ $accesses = $cached->data;
+ }
+ else {
+ $result = db_query('SELECT * FROM {access}');
+
+ // Classify, group and preprocess fetched access rules.
+ while ($rule = db_fetch_object($result)) {
+ // Mask is starting with '^': in regex expression. Use directly without
+ // additional processing.
+ if (substr($rule->mask, 0, 1) == '^') {
+ $array[$rule->type][$rule->status]['regex'][] = $rule->mask;
+ }
+ // Mask is in full IP address format. Use numeric handling.
+ else if (preg_match('/^\d+(\.\d+){3}$/', $rule->mask)) {
+ $pair['network'] = ip2long($rule->mask);
+ $pair['broadcast'] = ip2long($rule->mask);
+ $array[$rule->type][$rule->status]['ip'][] = $pair;
+ }
+ // Mask is in partial IP address format. Use numeric handling.
+ else if (preg_match('/^(\d+)\.?(\d+)?\.?(\d+)?\.?(\d+)?$/', $rule->mask, $matches)) {
+ $network = array();
+ $netmask = array();
+ for ($i = 1; $i < count($matches); $i++) {
+ $network[] = $matches[$i];
+ $netmask[] = '255';
+ }
+ for ($i = count($matches); $i <= 4; $i++) {
+ $network[] = '0';
+ $netmask[] = '0';
+ }
+ $network = implode('.', $network);
+ $netmask = implode('.', $netmask);
+ $lmask = ip2long($netmask);
+ $pair['network'] = ip2long($network) & $lmask;
+ $pair['broadcast'] = $pair['network'] | $lmask ^ ip2long('255.255.255.255');
+ $array[$rule->type][$rule->status]['ip'][] = $pair;
+ }
+ // Mask is in network/netmask pair format. Use numeric handling.
+ else if (preg_match('/^\d+(\.\d+){3}\/\d+(\.\d+){3}$/', $rule->mask)) {
+ list($network, $netmask) = explode('/', $rule->mask);
+ $lmask = ip2long($netmask);
+ $pair['network'] = ip2long($network) & $lmask;
+ $pair['broadcast'] = $pair['network'] | $lmask ^ ip2long('255.255.255.255');
+ $array[$rule->type][$rule->status]['ip'][] = $pair;
+ }
+ // Mask is in network/nnn CIDR specification. Use numeric handling.
+ else if (preg_match('/^\d+(\.\d+){3}\/\d+$/', $rule->mask)) {
+ $cidr = array("0.0.0.0", "128.0.0.0", "192.0.0.0", "224.0.0.0",
+ "240.0.0.0", "248.0.0.0", "252.0.0.0", "254.0.0.0",
+ "255.0.0.0", "255.128.0.0", "255.192.0.0", "255.224.0.0",
+ "255.240.0.0", "255.248.0.0", "255.252.0.0", "255.254.0.0",
+ "255.255.0.0", "255.255.128.0", "255.255.192.0", "255.255.224.0",
+ "255.255.240.0", "255.255.248.0", "255.255.252.0", "255.255.254.0",
+ "255.255.255.0", "255.255.255.128", "255.255.255.192", "255.255.255.224",
+ "255.255.255.240", "255.255.255.248", "255.255.255.252", "255.255.255.254",
+ "255.255.255.255");
+ list($network, $netmask) = explode('/', $rule->mask);
+ $netmask = $cidr[$netmask];
+ $lmask = ip2long($netmask);
+ $pair['network'] = ip2long($network) & $lmask;
+ $pair['broadcast'] = $pair['network'] | $lmask ^ ip2long('255.255.255.255');
+ $array[$rule->type][$rule->status]['ip'][] = $pair;
+ }
+ // Mask is in plain text format. Escape it with preg_quote().
+ else {
+ $array[$rule->type][$rule->status]['plain'][] = preg_quote($rule->mask, '/');
+ }
+ }
+
+ // Build rules for caching.
+ foreach ((array) $array as $type => $tmp) {
+ foreach ((array) $tmp as $status => $format) {
+ if ($format['plain']) {
+ $accesses[$type][$status]['plain'] = '/^(' . implode('|', $format['plain']) . ')$/Ds';
+ }
+ if ($format['regex']) {
+ $accesses[$type][$status]['regex'] = '/' . implode('|', $format['regex']) . '/Ds';
+ }
+ if ($format['ip']) {
+ $accesses[$type][$status]['ip'] = $format['ip'];
+ }
+ }
+ }
+
+ cache_set('accesses', $accesses);
+ }
+
+ return $accesses;
+}
+
+/**
* Perform an access check for a given mask and rule type. Rules are usually
* created via admin/user/rules page.
*
- * If any allow rule matches, access is allowed. Otherwise, if any deny rule
- * matches, access is denied. If no rule matches, access is allowed.
+ * First, all Deny directives are evaluated; if any match, the request
+ * is denied unless it also matches an Allow directive. Any requests
+ * which do not match any Allow or Deny directives are permitted.
*
- * @param $type string
+ * @param $type
* Type of access to check: Allowed values are:
* - 'host': host name or IP address
* - 'mail': e-mail address
* - 'user': username
- * @param $mask string
- * String or mask to test: '_' matches any character, '%' matches any
- * number of characters.
- * @return bool
- * TRUE if access is denied, FALSE if access is allowed.
- */
-function drupal_is_denied($type, $mask) {
- // Because this function is called for every page request, both cached
- // and non-cached pages, we tried to optimize it as much as possible.
- // We deny access if the only matching records in the {access} table have
- // status 0. If any have status 1, or if there are no matching records,
- // we allow access. So, select matching records in decreasing order of
- // 'status', returning NOT(status) for the first. If any have status 1,
- // they come first, and we return NOT(status) = 0 (allowed). Otherwise,
- // if we have some with status 0, we return 1 (denied). If no matching
- // records, we get no return from db_result, so we return (bool)NULL = 0
- // (allowed).
- // The use of ORDER BY / LIMIT is more efficient than "MAX(status) = 0"
- // in PostgreSQL <= 8.0.
- return (bool) db_result(db_query_range("SELECT CASE WHEN status=1 THEN 0 ELSE 1 END FROM {access} WHERE type = '%s' AND LOWER(mask) LIKE LOWER('%s') ORDER BY status DESC", $type, $mask, 0, 1));
+ * @param $mask
+ * String or mask to test.
+ * @param $order
+ * Controls the default access state and the order in which Allow and Deny
+ * are evaluated. Handle in Apache mod_authz_host style.
+ * @return
+ * TRUE if access is allowed, FALSE if access is denied.
+ */
+function drupal_is_allowed($type, $mask, $order = 'deny,allow') {
+ global $access;
+
+ switch(strtolower($order)) {
+ case 'allow,deny':
+ case 'mutual-failure':
+ // First, all Allow directives are evaluated; at least one must match,
+ // or the request is rejected. Next, all Deny directives are evaluated.
+ // If any matches, the request is rejected. Last, any requests which do
+ // not match an Allow or a Deny directive are denied by default.
+ $allow = FALSE;
+ $status = array(1, 0);
+ break;
+ case 'deny,allow':
+ default:
+ // First, all Deny directives are evaluated; if any match, the request
+ // is denied unless it also matches an Allow directive. Any requests
+ // which do not match any Allow or Deny directives are permitted.
+ $allow = TRUE;
+ $status = array(0, 1);
+ break;
+ }
+
+ while (!is_null($current_status = array_shift($status))) {
+ // Check with ip notation.
+ if ($mask == long2ip(ip2long($mask)) && isset($access[$type][$current_status]['ip']) && is_array($access[$type][$current_status]['ip'])) {
+ foreach ($access[$type][$current_status]['ip'] as $ip) {
+ if (ip2long($mask) >= $ip['network'] && ip2long($mask) <= $ip['broadcast']) {
+ $allow = $current_status ? TRUE : FALSE;
+ }
+ }
+ }
+ // Check with plain text notation.
+ if (isset($access[$type][$current_status]['plain']) && $access[$type][$current_status]['plain']) {
+ if (preg_match($access[$type][$current_status]['plain'], $mask)) {
+ $allow = $current_status ? TRUE : FALSE;
+ }
+ }
+ // Check with regex expression.
+ if (isset($access[$type][$current_status]['regex']) && $access[$type][$current_status]['regex']) {
+ if (preg_match($access[$type][$current_status]['regex'], $mask)) {
+ $allow = $current_status ? TRUE : FALSE;
+ }
+ }
+ }
+
+ return $allow;
}
/**
@@ -887,7 +1022,7 @@ function drupal_bootstrap($phase) {
}
function _drupal_bootstrap($phase) {
- global $conf;
+ global $access, $conf;
switch ($phase) {
@@ -908,8 +1043,11 @@ function _drupal_bootstrap($phase) {
break;
case DRUPAL_BOOTSTRAP_ACCESS:
+ // Initialize the access strategy.
+ $access = _drupal_access_init();
+
// Deny access to hosts which were banned - t() is not yet available.
- if (drupal_is_denied('host', ip_address())) {
+ if (!drupal_is_allowed('host', ip_address())) {
header('HTTP/1.1 403 Forbidden');
print 'Sorry, '. check_plain(ip_address()) .' has been banned.';
exit();
Index: drupal/modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.837
diff -u -p -r1.837 user.module
--- drupal/modules/user/user.module 29 Aug 2007 14:57:50 -0000 1.837
+++ drupal/modules/user/user.module 31 Aug 2007 19:13:02 -0000
@@ -1194,7 +1194,7 @@ function user_login_name_validate($form,
// blocked in user administration
form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name'])));
}
- else if (drupal_is_denied('user', $form_state['values']['name'])) {
+ else if (!drupal_is_allowed('user', $form_state['values']['name'])) {
// denied by access controls
form_set_error('name', t('The name %name is a reserved username.', array('%name' => $form_state['values']['name'])));
}
@@ -1663,7 +1663,7 @@ function _user_edit_validate($uid, &$edi
else if (db_result(db_query("SELECT COUNT(*) 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' => $edit['name'])));
}
- else if (drupal_is_denied('user', $edit['name'])) {
+ else if (!drupal_is_allowed('user', $edit['name'])) {
form_set_error('name', t('The name %name has been denied access.', array('%name' => $edit['name'])));
}
}
@@ -1675,7 +1675,7 @@ function _user_edit_validate($uid, &$edi
else if (db_result(db_query("SELECT COUNT(*) 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 registered. Have you forgotten your password?', array('%email' => $edit['mail'], '@password' => url('user/password'))));
}
- else if (drupal_is_denied('mail', $edit['mail'])) {
+ else if (!drupal_is_allowed('mail', $edit['mail'])) {
form_set_error('mail', t('The e-mail address %email has been denied access.', array('%email' => $edit['mail'])));
}
}
@@ -1963,7 +1963,7 @@ function user_admin_access_check_validat
function user_admin_access_check_submit($form, &$form_state) {
switch ($form_state['values']['type']) {
case 'user':
- if (drupal_is_denied('user', $form_state['values']['test'])) {
+ if (!drupal_is_allowed('user', $form_state['values']['test'])) {
drupal_set_message(t('The username %name is not allowed.', array('%name' => $form_state['values']['test'])));
}
else {
@@ -1971,7 +1971,7 @@ function user_admin_access_check_submit(
}
break;
case 'mail':
- if (drupal_is_denied('mail', $form_state['values']['test'])) {
+ if (!drupal_is_allowed('mail', $form_state['values']['test'])) {
drupal_set_message(t('The e-mail address %mail is not allowed.', array('%mail' => $form_state['values']['test'])));
}
else {
@@ -1979,7 +1979,7 @@ function user_admin_access_check_submit(
}
break;
case 'host':
- if (drupal_is_denied('host', $form_state['values']['test'])) {
+ if (!drupal_is_allowed('host', $form_state['values']['test'])) {
drupal_set_message(t('The hostname %host is not allowed.', array('%host' => $form_state['values']['test'])));
}
else {
@@ -2000,8 +2000,8 @@ function user_admin_access_add($mask = N
form_set_error('mask', t('You must enter a mask.'));
}
else {
- db_query("INSERT INTO {access} (aid, mask, type, status) VALUES ('%s', '%s', '%s', %d)", $aid, $edit['mask'], $edit['type'], $edit['status']);
- $aid = db_last_insert_id('access', 'aid');
+ db_query("INSERT INTO {access} (mask, type, status) VALUES ('%s', '%s', %d)", $edit['mask'], $edit['type'], $edit['status']);
+ cache_clear_all('accesses', 'cache');
drupal_set_message(t('The access rule has been added.'));
drupal_goto('admin/user/rules');
}
@@ -2033,6 +2033,7 @@ function user_admin_access_delete_confir
function user_admin_access_delete_confirm_submit($form, &$form_state) {
db_query('DELETE FROM {access} WHERE aid = %d', $form_state['values']['aid']);
+ cache_clear_all('accesses', 'cache');
drupal_set_message(t('The access rule has been deleted.'));
$form_state['redirect'] = 'admin/user/rules';
return;
@@ -2048,6 +2049,7 @@ function user_admin_access_edit($aid = 0
}
else {
db_query("UPDATE {access} SET mask = '%s', type = '%s', status = '%s' WHERE aid = %d", $edit['mask'], $edit['type'], $edit['status'], $aid);
+ cache_clear_all('accesses', 'cache');
drupal_set_message(t('The access rule has been saved.'));
drupal_goto('admin/user/rules');
}
@@ -2078,7 +2080,7 @@ function user_admin_access_form(&$form_s
'#size' => 30,
'#maxlength' => 64,
'#default_value' => $edit['mask'],
- '#description' => '%: '. t('Matches any number of characters, even zero characters') .'.
_: '. t('Matches exactly one character.'),
+ '#description' => t('Start the line with a ^ character to designate a regular expression match. Supported IP address formats: full, partial, network/netmask pair, and network/nnn CIDR specification. NOTE: specific partial domain-name in regular expression match.'),
'#required' => TRUE,
);
$form['submit'] = array('#type' => 'submit', '#value' => $submit);
@@ -2091,7 +2093,7 @@ function user_admin_access_form(&$form_s
*/
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));
+ $result = db_query("SELECT aid, type, status, mask FROM {access} WHERE type = '%s' OR type = '%s' OR type = '%s'". tablesort_sql($header), 'host', 'mail', 'user');
$access_types = array('user' => t('username'), 'mail' => t('e-mail'), 'host' => t('host'));
$rows = array();
while ($rule = db_fetch_object($result)) {