diff -urpN drupal-6.x-dev-200708270329/includes/bootstrap.inc drupal-6.x-dev-access-0.6.3/includes/bootstrap.inc --- drupal-6.x-dev-200708270329/includes/bootstrap.inc 2007-08-27 00:00:29.000000000 +0800 +++ drupal-6.x-dev-access-0.6.3/includes/bootstrap.inc 2007-08-29 00:07:55.000000000 +0800 @@ -403,6 +403,204 @@ function drupal_get_filename($type, $nam } /** + * Load the persistent access table. + * + * The access table is composed of values that have been saved in the table + * with access_set(). + */ +function 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; +} + +/** + * Return a persistent access rule checking result. + * + * @param $type + * Type of access to check. + * @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 access_get($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; +} + +/** + * Set a persistent access rule. + * + * @param $type + * Type of access to set. + * @param $mask + * String or mask to set. + * @param $status + * Status of access to set. + * @param $aid + * Access rule ID to alter. + */ +function access_set($type, $mask, $status, $aid = 0) { + if ($aid) { + db_query("UPDATE {access} SET mask = '%s', type = '%s', status = '%s' WHERE aid = %d", $mask, $type, $status, $aid); + if (!db_affected_rows()) { + $aid = 0; + } + } + if ($aid == 0) { + db_query("INSERT INTO {access} (mask, type, status) VALUES ('%s', '%s', %d)", $mask, $type, $status); + } + cache_clear_all('accesses', 'cache'); +} + +/** + * Unset a persistent access rule. + * + * @param $aid + * Access rule ID to alter. + */ +function access_del($aid) { + db_query('DELETE FROM {access} WHERE aid = %d', $aid); + cache_clear_all('accesses', 'cache'); +} + +/** * Load the persistent variable table. * * The variable table is composed of values that have been saved in the table @@ -792,40 +990,6 @@ function drupal_get_messages($type = NUL } /** - * 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. - * - * @param $type string - * 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)); -} - -/** * Generates a default anonymous $user object. * * @return Object - the user object. @@ -878,7 +1042,7 @@ function drupal_bootstrap($phase) { } function _drupal_bootstrap($phase) { - global $conf; + global $access, $conf; switch ($phase) { @@ -899,8 +1063,11 @@ function _drupal_bootstrap($phase) { break; case DRUPAL_BOOTSTRAP_ACCESS: + // Initialize the access strategy. + $access = access_init(); + // Deny access to hosts which were banned - t() is not yet available. - if (drupal_is_denied('host', ip_address())) { + if (!access_get('host', ip_address())) { header('HTTP/1.1 403 Forbidden'); print 'Sorry, '. check_plain(ip_address()) .' has been banned.'; exit(); diff -urpN drupal-6.x-dev-200708270329/modules/user/user.module drupal-6.x-dev-access-0.6.3/modules/user/user.module --- drupal-6.x-dev-200708270329/modules/user/user.module 2007-08-26 16:00:49.000000000 +0800 +++ drupal-6.x-dev-access-0.6.3/modules/user/user.module 2007-08-29 00:08:53.000000000 +0800 @@ -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 (!access_get('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 (!access_get('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 (!access_get('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 (!access_get('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 (!access_get('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 (!access_get('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,7 @@ 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'); + access_set($edit['type'], $edit['mask'], $edit['status']); drupal_set_message(t('The access rule has been added.')); drupal_goto('admin/user/rules'); } @@ -2016,7 +2015,7 @@ function user_admin_access_add($mask = N /** * Menu callback: delete an access rule */ -function user_admin_access_delete_confirm($aid = 0) { +function user_admin_access_delete_confirm(&$form_state, $aid = 0) { $access_types = array('user' => t('username'), 'mail' => t('e-mail'), 'host' => t('host')); $edit = db_fetch_object(db_query('SELECT aid, type, status, mask FROM {access} WHERE aid = %d', $aid)); @@ -2032,7 +2031,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']); + access_del($form_state['values']['aid']); drupal_set_message(t('The access rule has been deleted.')); $form_state['redirect'] = 'admin/user/rules'; return; @@ -2047,7 +2046,7 @@ function user_admin_access_edit($aid = 0 form_set_error('mask', t('You must enter a mask.')); } else { - db_query("UPDATE {access} SET mask = '%s', type = '%s', status = '%s' WHERE aid = %d", $edit['mask'], $edit['type'], $edit['status'], $aid); + access_set($edit['type'], $edit['mask'], $edit['status'], $aid); drupal_set_message(t('The access rule has been saved.')); drupal_goto('admin/user/rules'); } @@ -2078,7 +2077,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 +2090,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)) {