Begin upgrade to much better bans table. DO NOT PULL YET; It won't work.

This commit is contained in:
Michael Foster 2013-09-17 09:15:24 +10:00
parent 3714f37073
commit 3e57bb04d7
12 changed files with 132 additions and 257 deletions

View File

@ -1052,8 +1052,8 @@
* ====================
*/
// Limit how many bans can be removed via the ban list. Set to -1 for no limit.
$config['mod']['unban_limit'] = -1;
// Limit how many bans can be removed via the ban list. Set to false (or zero) for no limit.
$config['mod']['unban_limit'] = false;
// Whether or not to lock moderator sessions to IP addresses. This makes cookie theft ineffective.
$config['mod']['lock_ip'] = true;
@ -1098,14 +1098,6 @@
// 'color:red;font-weight:bold' // Change tripcode style; optional
//);
// Enable IP range bans (eg. "127.*.0.1", "127.0.0.*", and "12*.0.0.1" all match "127.0.0.1"). Puts a
// little more load on the database
$config['ban_range'] = true;
// Enable CDIR netmask bans (eg. "10.0.0.0/8" for 10.0.0.0.0 - 10.255.255.255). Useful for stopping
// persistent spammers and ban evaders. Again, a little more database load.
$config['ban_cidr'] = true;
// How often (minimum) to purge the ban list of expired bans (which have been seen). Only works when
// $config['cache'] is enabled and working.
$config['purge_bans'] = 60 * 60 * 12; // 12 hours

View File

@ -22,7 +22,7 @@ class PreparedQueryDebug {
if ($config['debug'] && $function == 'execute') {
if ($this->explain_query) {
$this->explain_query->execute() or error(db_error($explain_query));
$this->explain_query->execute() or error(db_error($this->explain_query));
}
$start = microtime(true);
}

View File

@ -120,47 +120,13 @@ class Filter {
if (!isset($this->reason))
error('The ban action requires a reason.');
$reason = $this->reason;
$this->expires = isset($this->expires) ? $this->expires : false;
$this->reject = isset($this->reject) ? $this->reject : true;
$this->all_boards = isset($this->all_boards) ? $this->all_boards : false;
if (isset($this->expires))
$expires = time() + $this->expires;
else
$expires = 0; // Ban indefinitely
Bans::new_ban($_SERVER['REMOTE_ADDR'], $this->reason, $this->expires, $this->all_boards ? false : $board['uri'], -1);
if (isset($this->reject))
$reject = $this->reject;
else
$reject = true;
if (isset($this->all_boards))
$all_boards = $this->all_boards;
else
$all_boards = false;
$query = prepare("INSERT INTO ``bans`` VALUES (NULL, :ip, :mod, :set, :expires, :reason, :board, 0)");
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->bindValue(':mod', -1);
$query->bindValue(':set', time());
if ($expires)
$query->bindValue(':expires', $expires);
else
$query->bindValue(':expires', null, PDO::PARAM_NULL);
if ($reason)
$query->bindValue(':reason', $reason);
else
$query->bindValue(':reason', null, PDO::PARAM_NULL);
if ($all_boards)
$query->bindValue(':board', null, PDO::PARAM_NULL);
else
$query->bindValue(':board', $board['uri']);
$query->execute() or error(db_error($query));
if ($reject) {
if ($this->reject) {
if (isset($this->message))
error($message);

View File

@ -18,6 +18,7 @@ require_once 'inc/template.php';
require_once 'inc/database.php';
require_once 'inc/events.php';
require_once 'inc/api.php';
require_once 'inc/bans.php';
require_once 'inc/lib/gettext/gettext.inc';
// the user is not currently logged in as a moderator
@ -619,9 +620,7 @@ function displayBan($ban) {
global $config;
if (!$ban['seen']) {
$query = prepare("UPDATE ``bans`` SET `seen` = 1 WHERE `id` = :id");
$query->bindValue(':id', $ban['id'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
Bans::seen($ban['id']);
}
$ban['ip'] = $_SERVER['REMOTE_ADDR'];
@ -650,62 +649,37 @@ function checkBan($board = false) {
if (event('check-ban', $board))
return true;
$query_where = array();
// Simple ban
$query_where[] = "`ip` = :ip";
// Range ban
if ($config['ban_range'])
$query_where[] = ":ip LIKE REPLACE(REPLACE(`ip`, '%', '!%'), '*', '%') ESCAPE '!'";
// Subnet mask ban
if ($config['ban_cidr'] && !isIPv6())
$query_where[] = "(
`ip` REGEXP '^(\[0-9]+\.\[0-9]+\.\[0-9]+\.\[0-9]+\)\/(\[0-9]+)$'
AND
:iplong >= INET_ATON(SUBSTRING_INDEX(`ip`, '/', 1))
AND
:iplong < INET_ATON(SUBSTRING_INDEX(`ip`, '/', 1)) + POW(2, 32 - SUBSTRING_INDEX(`ip`, '/', -1))
)";
$bans = Bans::find($_SERVER['REMOTE_ADDR'], $board);
$query = prepare('SELECT * FROM ``bans`` WHERE (`board` IS NULL' . ($board ? ' OR `board` = :board' : '') .
') AND (' . implode(' OR ', $query_where) . ') ORDER BY `expires` IS NULL, `expires` DESC');
if ($board)
$query->bindValue(':board', $board);
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
if ($config['ban_cidr'] && !isIPv6())
$query->bindValue(':iplong', ip2long($_SERVER['REMOTE_ADDR']));
$query->execute() or error(db_error($query));
while ($ban = $query->fetch(PDO::FETCH_ASSOC)) {
foreach ($bans as &$ban) {
if ($ban['expires'] && $ban['expires'] < time()) {
// Ban expired
$query = prepare("DELETE FROM ``bans`` WHERE `id` = :id");
$query->bindValue(':id', $ban['id'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
Bans::delete($ban['id']);
if ($config['require_ban_view'] && !$ban['seen']) {
if (!isset($_POST['json_response'])) {
displayBan($ban);
} else {
header('Content-Type: text/json');
die(json_encode(array('error' => true, 'banned' => true)));
}
}
} else {
if (!isset($_POST['json_response'])) {
displayBan($ban);
} else {
header('Content-Type: text/json');
die(json_encode(array('error' => true, 'banned' => true)));
}
}
}
// I'm not sure where else to put this. It doesn't really matter where; it just needs to be called every now and then to keep the ban list tidy.
purge_bans();
}
// No reason to keep expired bans in the database (except those that haven't been viewed yet)
function purge_bans() {
global $config;
// I'm not sure where else to put this. It doesn't really matter where; it just needs to be called every
// now and then to keep the ban list tidy.
if ($config['cache']['enabled'] && $last_time_purged = cache::get('purged_bans_last')) {
if (time() - $last_time_purged < $config['purge_bans'] )
return;
}
$query = prepare("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < :time AND `seen` = 1");
$query->bindValue(':time', time());
$query->execute() or error(db_error($query));
Bans::purge();
if ($config['cache']['enabled'])
cache::set('purged_bans_last', time());

View File

@ -4,102 +4,7 @@
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) {
// You cannot request this file directly.
exit;
}
defined('TINYBOARD') or exit;
function parse_time($str) {
if (empty($str))
return false;
if (($time = @strtotime($str)) !== false)
return $time;
if (!preg_match('/^((\d+)\s?ye?a?r?s?)?\s?+((\d+)\s?mon?t?h?s?)?\s?+((\d+)\s?we?e?k?s?)?\s?+((\d+)\s?da?y?s?)?((\d+)\s?ho?u?r?s?)?\s?+((\d+)\s?mi?n?u?t?e?s?)?\s?+((\d+)\s?se?c?o?n?d?s?)?$/', $str, $matches))
return false;
$expire = 0;
if (isset($matches[2])) {
// Years
$expire += $matches[2]*60*60*24*365;
}
if (isset($matches[4])) {
// Months
$expire += $matches[4]*60*60*24*30;
}
if (isset($matches[6])) {
// Weeks
$expire += $matches[6]*60*60*24*7;
}
if (isset($matches[8])) {
// Days
$expire += $matches[8]*60*60*24;
}
if (isset($matches[10])) {
// Hours
$expire += $matches[10]*60*60;
}
if (isset($matches[12])) {
// Minutes
$expire += $matches[12]*60;
}
if (isset($matches[14])) {
// Seconds
$expire += $matches[14];
}
return time() + $expire;
}
function ban($mask, $reason, $length, $board) {
global $mod, $pdo;
$query = prepare("INSERT INTO ``bans`` VALUES (NULL, :ip, :mod, :time, :expires, :reason, :board, 0)");
$query->bindValue(':ip', $mask);
$query->bindValue(':mod', $mod['id']);
$query->bindValue(':time', time());
if ($reason !== '') {
$reason = escape_markup_modifiers($reason);
markup($reason);
$query->bindValue(':reason', $reason);
} else
$query->bindValue(':reason', null, PDO::PARAM_NULL);
if ($length > 0)
$query->bindValue(':expires', $length);
else
$query->bindValue(':expires', null, PDO::PARAM_NULL);
if ($board)
$query->bindValue(':board', $board);
else
$query->bindValue(':board', null, PDO::PARAM_NULL);
$query->execute() or error(db_error($query));
modLog('Created a new ' .
($length > 0 ? preg_replace('/^(\d+) (\w+?)s?$/', '$1-$2', until($length)) : 'permanent') .
' ban on ' .
($board ? '/' . $board . '/' : 'all boards') .
' for ' .
(filter_var($mask, FILTER_VALIDATE_IP) !== false ? "<a href=\"?/IP/$mask\">$mask</a>" : utf8tohtml($mask)) .
' (<small>#' . $pdo->lastInsertId() . '</small>)' .
' with ' . ($reason ? 'reason: ' . utf8tohtml($reason) . '' : 'no reason'));
}
function unban($id) {
$query = prepare("SELECT `ip` FROM ``bans`` WHERE `id` = :id");
$query->bindValue(':id', $id);
$query->execute() or error(db_error($query));
$mask = $query->fetchColumn();
$query = prepare("DELETE FROM ``bans`` WHERE `id` = :id");
$query->bindValue(':id', $id);
$query->execute() or error(db_error($query));
if ($mask)
modLog("Removed ban #{$id} for " . (filter_var($mask, FILTER_VALIDATE_IP) !== false ? "<a href=\"?/IP/$mask\">$mask</a>" : utf8tohtml($mask)));
}
// This file is no longer used.

View File

@ -1,5 +1,11 @@
<?php
/*
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
defined('TINYBOARD') or exit;
function permission_to_edit_config_var($varname) {
global $config, $mod;

View File

@ -289,7 +289,7 @@ function mod_search($type, $search_query_escaped, $page_no = 1) {
}
if ($type == 'bans') {
$query = 'SELECT ``bans``.*, `username` FROM ``bans`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE ' . $sql_like . ' ORDER BY (`expires` IS NOT NULL AND `expires` < UNIX_TIMESTAMP()), `set` DESC';
$query = 'SELECT ``bans``.*, `username` FROM ``bans`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE ' . $sql_like . ' ORDER BY (`expires` IS NOT NULL AND `expires` < UNIX_TIMESTAMP()), `created` DESC';
$sql_table = 'bans';
if (!hasPermission($config['mod']['view_banlist']))
error($config['error']['noaccess']);
@ -740,9 +740,7 @@ function mod_page_ip($ip) {
if (!hasPermission($config['mod']['unban']))
error($config['error']['noaccess']);
require_once 'inc/mod/ban.php';
unban($_POST['ban_id']);
Bans::delete($_POST['ban_id'], true);
header('Location: ?/IP/' . $ip . '#bans', true, $config['redirect_http']);
return;
@ -801,10 +799,7 @@ function mod_page_ip($ip) {
$args['token'] = make_secure_link_token('ban');
if (hasPermission($config['mod']['view_ban'])) {
$query = prepare("SELECT ``bans``.*, `username` FROM ``bans`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `ip` = :ip ORDER BY `set` DESC");
$query->bindValue(':ip', $ip);
$query->execute() or error(db_error($query));
$args['bans'] = $query->fetchAll(PDO::FETCH_ASSOC);
$args['bans'] = Bans::find($ip, false, true);
}
if (hasPermission($config['mod']['view_notes'])) {
@ -839,7 +834,7 @@ function mod_ban() {
require_once 'inc/mod/ban.php';
ban($_POST['ip'], $_POST['reason'], parse_time($_POST['length']), $_POST['board'] == '*' ? false : $_POST['board']);
Bans::new_ban($_POST['ip'], $_POST['reason'], $_POST['length'], $_POST['board'] == '*' ? false : $_POST['board']);
if (isset($_POST['redirect']))
header('Location: ' . $_POST['redirect'], true, $config['redirect_http']);
@ -865,58 +860,27 @@ function mod_bans($page_no = 1) {
if (preg_match('/^ban_(\d+)$/', $name, $match))
$unban[] = $match[1];
}
if (isset($config['mod']['unban_limit'])){
if (count($unban) <= $config['mod']['unban_limit'] || $config['mod']['unban_limit'] == -1){
if (!empty($unban)) {
query('DELETE FROM ``bans`` WHERE `id` = ' . implode(' OR `id` = ', $unban)) or error(db_error());
foreach ($unban as $id) {
modLog("Removed ban #{$id}");
}
}
} else {
if (isset($config['mod']['unban_limit']) && $config['mod']['unban_limit'] && count($unban) > $config['mod']['unban_limit'])
error(sprintf($config['error']['toomanyunban'], $config['mod']['unban_limit'], count($unban)));
}
} else {
if (!empty($unban)) {
query('DELETE FROM ``bans`` WHERE `id` = ' . implode(' OR `id` = ', $unban)) or error(db_error());
foreach ($unban as $id) {
modLog("Removed ban #{$id}");
}
}
Bans::delete($id, true);
}
header('Location: ?/bans', true, $config['redirect_http']);
return;
}
if ($config['mod']['view_banexpired']) {
$query = prepare("SELECT ``bans``.*, `username` FROM ``bans`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` ORDER BY (`expires` IS NOT NULL AND `expires` < :time), `set` DESC LIMIT :offset, :limit");
} else {
// Filter out expired bans
$query = prepare("SELECT ``bans``.*, `username` FROM ``bans`` INNER JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `expires` = 0 OR `expires` > :time ORDER BY `set` DESC LIMIT :offset, :limit");
}
$query->bindValue(':time', time(), PDO::PARAM_INT);
$query->bindValue(':limit', $config['mod']['banlist_page'], PDO::PARAM_INT);
$query->bindValue(':offset', ($page_no - 1) * $config['mod']['banlist_page'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
$bans = $query->fetchAll(PDO::FETCH_ASSOC);
$bans = Bans::list_all(($page_no - 1) * $config['mod']['banlist_page'], $config['mod']['banlist_page']);
if (empty($bans) && $page_no > 1)
error($config['error']['404']);
$query = prepare("SELECT COUNT(*) FROM ``bans``");
$query->execute() or error(db_error($query));
$count = $query->fetchColumn();
foreach ($bans as &$ban) {
if (filter_var($ban['ip'], FILTER_VALIDATE_IP) !== false)
$ban['real_ip'] = true;
if (filter_var($ban['mask'], FILTER_VALIDATE_IP) !== false)
$ban['single_addr'] = true;
}
mod_page(_('Ban list'), 'mod/ban_list.html', array('bans' => $bans, 'count' => $count));
mod_page(_('Ban list'), 'mod/ban_list.html', array('bans' => $bans, 'count' => Bans::count()));
}
@ -1217,7 +1181,7 @@ function mod_ban_post($board, $delete, $post, $token = false) {
if (isset($_POST['ip']))
$ip = $_POST['ip'];
ban($ip, $_POST['reason'], parse_time($_POST['length']), $_POST['board'] == '*' ? false : $_POST['board']);
Bans::new_ban($_POST['ip'], $_POST['reason'], $_POST['length'], $_POST['board'] == '*' ? false : $_POST['board']);
if (isset($_POST['public_message'], $_POST['message'])) {
// public ban message

View File

@ -1,7 +1,7 @@
<?php
// Installation/upgrade file
define('VERSION', 'v0.9.6-dev-20');
define('VERSION', 'v0.9.6-dev-21');
require 'inc/functions.php';
@ -412,6 +412,61 @@ if (file_exists($config['has_installed'])) {
query("UPDATE ``mods`` SET `type` = 20 WHERE `type` = 1") or error(db_error());
query("UPDATE ``mods`` SET `type` = 30 WHERE `type` = 2") or error(db_error());
query("ALTER TABLE ``mods`` CHANGE `type` `type` smallint(1) NOT NULL") or error(db_error());
case 'v0.9.6-dev-20':
query("CREATE TABLE IF NOT EXISTS `bans_new_temp` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ipstart` varbinary(16) NOT NULL,
`ipend` varbinary(16) DEFAULT NULL,
`created` int(10) unsigned NOT NULL,
`expires` int(10) unsigned DEFAULT NULL,
`board` varchar(58) DEFAULT NULL,
`creator` int(10) NOT NULL,
`reason` text,
`seen` tinyint(1) NOT NULL,
`post` blob,
PRIMARY KEY (`id`),
KEY `expires` (`expires`),
KEY `ipstart` (`ipstart`,`ipend`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1") or error(db_error());
$listquery = query("SELECT * FROM ``bans`` ORDER BY `id`") or error(db_error());
while ($ban = $listquery->fetch(PDO::FETCH_ASSOC)) {
$query = prepare("INSERT INTO ``bans_new_temp`` VALUES
(NULL, :ipstart, :ipend, :created, :expires, :board, :creator, :reason, :seen, NULL)");
$range = Bans::parse_range($ban['ip']);
if ($range === false) {
// Invalid retard ban; just skip it.
continue;
}
$query->bindValue(':ipstart', $range[0]);
if ($range[1] !== false && $range[1] != $range[0])
$query->bindValue(':ipend', $range[1]);
else
$query->bindValue(':ipend', null, PDO::PARAM_NULL);
$query->bindValue(':created', $ban['set']);
if ($ban['expires'])
$query->bindValue(':expires', $ban['expires']);
else
$query->bindValue(':expires', null, PDO::PARAM_NULL);
if ($ban['board'])
$query->bindValue(':board', $ban['board']);
else
$query->bindValue(':board', null, PDO::PARAM_NULL);
$query->bindValue(':creator', $ban['mod']);
if ($ban['reason'])
$query->bindValue(':reason', $ban['reason']);
else
$query->bindValue(':reason', null, PDO::PARAM_NULL);
$query->bindValue(':seen', $ban['seen']);
$query->execute() or error(db_error($query));
}
case false:
// Update version number
file_write($config['has_installed'], VERSION);

View File

@ -43,9 +43,23 @@ $(window).ready(function() {
},
success: function(post_response) {
if (post_response.error) {
if (post_response.banned) {
// You are banned. Must post the form normally so the user can see the ban message.
do_not_ajax = true;
$(form).find('input[type="submit"]').each(function() {
var $replacement = $('<input type="hidden">');
$replacement.attr('name', $(this).attr('name'));
$replacement.val(submit_txt);
$(this)
.after($replacement)
.replaceWith($('<input type="button">').val(submit_txt));
});
$(form).submit();
} else {
alert(post_response.error);
$(form).find('input[type="submit"]').val(submit_txt);
$(form).find('input[type="submit"]').removeAttr('disabled');
}
} else if (post_response.redirect && post_response.id) {
if (!$(form).find('input[name="thread"]').length) {
document.location = post_response.redirect;
@ -84,7 +98,6 @@ $(window).ready(function() {
},
error: function(xhr, status, er) {
// An error occured
// TODO
do_not_ajax = true;
$(form).find('input[type="submit"]').each(function() {
var $replacement = $('<input type="hidden">');

View File

@ -30,7 +30,7 @@
{% endif %}
<p>
{% trans %}Your ban was filed on{% endtrans %}
<strong>{{ ban.set|date(config.ban_date) }}</strong> {% trans %}and{% endtrans %} <span id="expires">
<strong>{{ ban.created|date(config.ban_date) }}</strong> {% trans %}and{% endtrans %} <span id="expires">
{% if ban.expires and time() >= ban.expires %}
{% trans %} has since expired. Refresh the page to continue.{% endtrans %}
{% elseif ban.expires %}

View File

@ -17,10 +17,10 @@
<tr{% if ban.expires != 0 and ban.expires < time() %} style="text-decoration:line-through"{% endif %}>
<td style="white-space: nowrap">
<input type="checkbox" name="ban_{{ ban.id }}">
{% if ban.real_ip %}
<a href="?/IP/{{ ban.ip }}">{{ ban.ip }}</a>
{% if ban.single_addr %}
<a href="?/IP/{{ ban.mask }}">{{ ban.mask }}</a>
{% else %}
{{ ban.ip|e }}
{{ ban.mask }}
{% endif %}
</td>
<td>
@ -38,8 +38,8 @@
{% endif %}
</td>
<td style="white-space: nowrap">
<span title="{{ ban.set|date(config.post_date) }}">
{{ ban.set|ago }} ago
<span title="{{ ban.created|date(config.post_date) }}">
{{ ban.created|ago }} ago
</span>
</td>
<td style="white-space: nowrap">
@ -77,7 +77,7 @@
{% endif %}
{% endif %}
{% elseif ban.mod == -1 %}
{% elseif ban.creator == -1 %}
<em>system</em>
{% else %}
<em>{% trans 'deleted?' %}</em>

View File

@ -100,7 +100,7 @@
</tr>
<tr>
<th>{% trans 'IP' %}</th>
<td>{{ ban.ip }}</td>
<td>{{ ban.mask }}</td>
</tr>
<tr>
<th>{% trans 'Reason' %}</th>
@ -124,7 +124,7 @@
</tr>
<tr>
<th>{% trans 'Set' %}</th>
<td>{{ ban.set|date(config.post_date) }}</td>
<td>{{ ban.created|date(config.post_date) }}</td>
</tr>
<tr>
<th>{% trans 'Expires' %}</th>