From 7f0de936082fb35d4e18fbadfe9849acdfc03ab6 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 6 Sep 2013 20:12:04 +1000 Subject: Cleaner check to make sure inc/ files aren't accessed directly. --- inc/filters.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'inc/filters.php') diff --git a/inc/filters.php b/inc/filters.php index cd512821..253a86b6 100644 --- a/inc/filters.php +++ b/inc/filters.php @@ -4,10 +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; class Filter { private $condition; -- cgit v1.3 From f309e4037c11197e4392e402992882358e4678c7 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Fri, 6 Sep 2013 23:09:18 +1000 Subject: Better and faster basic flood prevention, while merging it into $config['filters']. --- inc/config.php | 177 +++++++++++++++++++++++++++++++++++------------------- inc/filters.php | 109 +++++++++++++++++++++++++++++++-- inc/functions.php | 58 ++++++++---------- install.php | 17 +++++- install.sql | 21 +++++++ post.php | 23 ++++--- 6 files changed, 294 insertions(+), 111 deletions(-) (limited to 'inc/filters.php') diff --git a/inc/config.php b/inc/config.php index e8b65967..81cae233 100644 --- a/inc/config.php +++ b/inc/config.php @@ -168,13 +168,6 @@ * ==================== */ - // Minimum time between between each post by the same IP address. - $config['flood_time'] = 10; - // Minimum time between between each post with the exact same content AND same IP address. - $config['flood_time_ip'] = 120; - // Same as above but by a different IP address. (Same content, not necessarily same IP address.) - $config['flood_time_same'] = 30; - /* * To further prevent spam and abuse, you can use DNS blacklists (DNSBL). A DNSBL is a list of IP * addresses published through the Internet Domain Name Service (DNS) either as a zone file that can be @@ -283,6 +276,118 @@ $config['recaptcha_public'] = '6LcXTcUSAAAAAKBxyFWIt2SO8jwx4W7wcSMRoN3f'; $config['recaptcha_private'] = '6LcXTcUSAAAAAOGVbVdhmEM1_SyRF4xTKe8jbzf_'; + /* + * Custom filters detect certain posts and reject/ban accordingly. They are made up of a condition and an + * action (for when ALL conditions are met). As every single post has to be put through each filter, + * having hundreds probably isn't ideal as it could slow things down. + * + * By default, the custom filters array is populated with basic flood prevention conditions. This + * includes forcing users to wait at least 5 seconds between posts. To disable (or amend) these flood + * prevention settings, you will need to empty the $config['filters'] array first. You can do so by + * adding "$config['filters'] = array();" to inc/instance-config.php. Basic flood prevention used to be + * controlled solely by config variables such as $config['flood_time'] and $config['flood_time_ip'], and + * it still is, as long as you leave the relevant $config['filters'] intact. These old config variables + * still exist for backwards-compatability and general convenience. + * + * Read more: http://tinyboard.org/docs/index.php?p=Config/Filters + */ + + // Minimum time between between each post by the same IP address. + $config['flood_time'] = 10; + // Minimum time between between each post with the exact same content AND same IP address. + $config['flood_time_ip'] = 120; + // Same as above but by a different IP address. (Same content, not necessarily same IP address.) + $config['flood_time_same'] = 30; + + // Minimum time between posts by the same IP address (all boards). + $config['filters'][] = array( + 'condition' => array( + 'flood-match' => array('ip'), // Only match IP address + 'flood-time' => &$config['flood_time'] // 10 seconds minimum + ), + 'action' => 'reject', + 'message' => &$config['error']['flood'] + ); + + // Minimum time between posts by the same IP address with the same text. + $config['filters'][] = array( + 'condition' => array( + 'flood-match' => array('ip', 'body'), // Match IP address and post body + 'flood-time' => &$config['flood_time_ip'] // 2 minutes minimum + ), + 'action' => 'reject', + 'message' => &$config['error']['flood'] + ); + + // Minimum time between posts with the same text. (Same content, but not always the same IP address.) + $config['filters'][] = array( + 'condition' => array( + 'flood-match' => array('body'), // Match IP address and post body + 'flood-time' => &$config['flood_time_same'] // 30 seconds minimum + ), + 'action' => 'reject', + 'message' => &$config['error']['flood'] + ); + + // Example: Minimum time between posts with the same file hash. + // $config['filters'][] = array( + // 'condition' => array( + // 'flood-match' => array('file'), // Match file hash + // 'flood-time' => 60 * 2 // 2 minutes minimum + // ), + // 'action' => 'reject', + // 'message' => &$config['error']['flood'] + // ); + + // An example of blocking an imaginary known spammer, who keeps posting a reply with the name "surgeon", + // ending his posts with "regards, the surgeon" or similar. + // $config['filters'][] = array( + // 'condition' => array( + // 'name' => '/^surgeon$/', + // 'body' => '/regards,\s+(the )?surgeon$/i', + // 'OP' => false + // ), + // 'action' => 'reject', + // 'message' => 'Go away, spammer.' + // ); + + // Same as above, but issuing a 3-hour ban instead of just reject the post. + // $config['filters'][] = array( + // 'condition' => array( + // 'name' => '/^surgeon$/', + // 'body' => '/regards,\s+(the )?surgeon$/i', + // 'OP' => false + // ), + // 'action' => 'ban', + // 'expires' => 60 * 60 * 3, // 3 hours + // 'reason' => 'Go away, spammer.' + // ); + + // PHP 5.3+ (anonymous functions) + // There is also a "custom" condition, making the possibilities of this feature pretty much endless. + // This is a bad example, because there is already a "name" condition built-in. + // $config['filters'][] = array( + // 'condition' => array( + // 'body' => '/h$/i', + // 'OP' => false, + // 'custom' => function($post) { + // if($post['name'] == 'Anonymous') + // return true; + // else + // return false; + // } + // ), + // 'action' => 'reject' + // ); + + // Filter flood prevention conditions ("flood-match") depend on a table which contains a cache of recent + // posts across all boards. This table is automatically purged of older posts, determining the maximum + // "age" by looking at each filter. However, when determining the maximum age, Tinyboard does not look + // outside the current board. This means that if you have a special flood condition for a specific board + // (contained in a board configuration file) which has a flood-time greater than any of those in the + // global configuration, you need to set the following variable to the maximum flood-time condition value. + // $config['flood_cache'] = 60 * 60 * 24; // 24 hours + /* * ==================== * Post settings @@ -401,57 +506,6 @@ // Require users to see the ban page at least once for a ban even if it has since expired. $config['require_ban_view'] = true; - /* - * Custom filters detect certain posts and reject/ban accordingly. They are made up of a - * condition and an action (for when ALL conditions are met). As every single post has to - * be put through each filter, having hundreds probably isn’t ideal as it could slow things down. - * - * Read more: http://tinyboard.org/docs/index.php?p=Config/Filters - * - * This used to be named $config['flood_filters'] (still exists as an alias). - */ - - // An example of blocking an imaginary known spammer, who keeps posting a reply with the name "surgeon", - // ending his posts with "regards, the surgeon" or similar. - // $config['filters'][] = array( - // 'condition' => array( - // 'name' => '/^surgeon$/', - // 'body' => '/regards,\s+(the )?surgeon$/i', - // 'OP' => false - // ), - // 'action' => 'reject', - // 'message' => 'Go away, spammer.' - // ); - - // Same as above, but issuing a 3-hour ban instead of just reject the post. - // $config['filters'][] = array( - // 'condition' => array( - // 'name' => '/^surgeon$/', - // 'body' => '/regards,\s+(the )?surgeon$/i', - // 'OP' => false - // ), - // 'action' => 'ban', - // 'expires' => 60 * 60 * 3, // 3 hours - // 'reason' => 'Go away, spammer.' - // ); - - // PHP 5.3+ (anonymous functions) - // There is also a "custom" condition, making the possibilities of this feature pretty much endless. - // This is a bad example, because there is already a "name" condition built-in. - // $config['filters'][] = array( - // 'condition' => array( - // 'body' => '/h$/i', - // 'OP' => false, - // 'custom' => function($post) { - // if($post['name'] == 'Anonymous') - // return true; - // else - // return false; - // } - // ), - // 'action' => 'reject' - // ); - /* * ==================== * Markup settings @@ -593,10 +647,6 @@ // that as a thumbnail instead of resizing/redrawing. $config['minimum_copy_resize'] = false; - // Image hashing function. There's really no reason to change this. - // sha1_file, md5_file, etc. You can also define your own similar function. - $config['file_hash'] = 'sha1_file'; - // Maximum image upload size in bytes. $config['max_filesize'] = 10 * 1024 * 1024; // 10MB // Maximum image dimensions. @@ -1156,7 +1206,8 @@ // Post bypass unoriginal content check on robot-enabled boards $config['mod']['postunoriginal'] = ADMIN; // Bypass flood check - $config['mod']['flood'] = ADMIN; + $config['mod']['bypass_filters'] = ADMIN; + $config['mod']['flood'] = &$config['mod']['bypass_filters']; // Raw HTML posting $config['mod']['rawhtml'] = ADMIN; diff --git a/inc/filters.php b/inc/filters.php index 253a86b6..81a33116 100644 --- a/inc/filters.php +++ b/inc/filters.php @@ -7,6 +7,7 @@ defined('TINYBOARD') or exit; class Filter { + public $flood_check; private $condition; public function __construct(array $arr) { @@ -22,6 +23,56 @@ class Filter { if (!is_callable($match)) error('Custom condition for filter is not callable!'); return $match($post); + case 'flood-match': + if (!is_array($match)) + error('Filter condition "flood-match" must be an array.'); + + // Filter out "flood" table entries which do not match this filter. + + $flood_check_matched = array(); + + foreach ($this->flood_check as $flood_post) { + foreach ($match as $flood_match_arg) { + switch ($flood_match_arg) { + case 'ip': + if ($flood_post['ip'] != $_SERVER['REMOTE_ADDR']) + continue 3; + break; + case 'body': + if ($flood_post['posthash'] != md5($post['body_nomarkup'])) + continue 3; + break; + case 'file': + if (!isset($post['filehash'])) + return false; + if ($flood_post['filehash'] != $post['filehash']) + continue 3; + break; + case 'board': + if ($flood_post['board'] != $post['board']) + continue 3; + break; + case 'isreply': + if ($flood_post['isreply'] == $post['op']) + continue 3; + break; + default: + error('Invalid filter flood condition: ' . $flood_match_arg); + } + } + $flood_check_matched[] = $flood_post; + } + + $this->flood_check = $flood_check_matched; + + return !empty($this->flood_check); + case 'flood-time': + foreach ($this->flood_check as $flood_post) { + if (time() - $flood_post['time'] <= $match) { + return true; + } + } + return false; case 'name': return preg_match($match, $post['name']); case 'trip': @@ -31,7 +82,7 @@ class Filter { case 'subject': return preg_match($match, $post['subject']); case 'body': - return preg_match($match, $post['body']); + return preg_match($match, $post['body_nomarkup']); case 'filename': if (!$post['has_file']) return false; @@ -126,14 +177,64 @@ class Filter { } } +function purge_flood_table() { + global $config; + + // Determine how long we need to keep a cache of posts for flood prevention. Unfortunately, it is not + // aware of flood filters in other board configurations. You can solve this problem by settings the + // config variable $config['flood_cache'] (seconds). + + if (isset($config['flood_cache'])) { + $max_time = &$config['flood_cache']; + } else { + $max_time = 0; + foreach ($config['filters'] as $filter) { + if (isset($filter['condition']['flood-time'])) + $max_time = max($max_time, $filter['condition']['flood-time']); + } + } + + $time = time() - $max_time; + + query("DELETE FROM ``flood`` WHERE ``time`` < $time") or error(db_error()); +} + function do_filters(array $post) { global $config; - if (!isset($config['filters'])) + if (!isset($config['filters']) || empty($config['filters'])) return; - foreach ($config['filters'] as $arr) { - $filter = new Filter($arr); + foreach ($config['filters'] as $filter) { + if (isset($filter['condition']['flood-match'])) { + $has_flood = true; + break; + } + } + + purge_flood_table(); + + if (isset($has_flood)) { + if ($post['has_file']) { + $query = prepare("SELECT * FROM ``flood`` WHERE `ip` = :ip OR `posthash` = :posthash OR `filehash` = :filehash"); + $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); + $query->bindValue(':posthash', md5($post['body_nomarkup'])); + $query->bindValue(':filehash', $post['filehash']); + } else { + $query = prepare("SELECT * FROM ``flood`` WHERE `ip` = :ip OR `posthash` = :posthash"); + $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); + $query->bindValue(':posthash', md5($post['body_nomarkup'])); + + } + $query->execute() or error(db_error($query)); + $flood_check = $query->fetchAll(PDO::FETCH_ASSOC); + } else { + $flood_check = false; + } + + foreach ($config['filters'] as $filter_array) { + $filter = new Filter($filter_array); + $filter->flood_check = $flood_check; if ($filter->check($post)) $filter->action(); } diff --git a/inc/functions.php b/inc/functions.php index 9601c0f8..a6963c91 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -572,30 +572,6 @@ function listBoards() { return $boards; } -function checkFlood($post) { - global $board, $config; - - $query = prepare(sprintf("SELECT COUNT(*) FROM ``posts_%s`` WHERE - (`ip` = :ip AND `time` >= :floodtime) - OR - (`ip` = :ip AND :body != '' AND `body_nomarkup` = :body AND `time` >= :floodsameiptime) - OR - (:body != '' AND `body_nomarkup` = :body AND `time` >= :floodsametime) LIMIT 1", $board['uri'])); - $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); - $query->bindValue(':body', $post['body']); - $query->bindValue(':floodtime', time()-$config['flood_time'], PDO::PARAM_INT); - $query->bindValue(':floodsameiptime', time()-$config['flood_time_ip'], PDO::PARAM_INT); - $query->bindValue(':floodsametime', time()-$config['flood_time_same'], PDO::PARAM_INT); - $query->execute() or error(db_error($query)); - - $flood = (bool) $query->fetchColumn(); - - if (event('check-flood', $post)) - return true; - - return $flood; -} - function until($timestamp) { $difference = $timestamp - time(); if ($difference < 60) { @@ -780,6 +756,22 @@ function threadExists($id) { return false; } +function insertFloodPost(array $post) { + global $board; + + $query = prepare("INSERT INTO ``flood`` VALUES (NULL, :ip, :board, :time, :posthash, :filehash, :isreply)"); + $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); + $query->bindValue(':board', $board['uri']); + $query->bindValue(':time', time()); + $query->bindValue(':posthash', md5($post['body_nomarkup'])); + if ($post['has_file']) + $query->bindValue(':filehash', $post['filehash']); + else + $query->bindValue(':filehash', null, PDO::PARAM_NULL); + $query->bindValue(':isreply', !$post['op']); + $query->execute() or error(db_error($query)); +} + function post(array $post) { global $pdo, $board; $query = prepare(sprintf("INSERT INTO ``posts_%s`` VALUES ( NULL, :thread, :subject, :email, :name, :trip, :capcode, :body, :body_nomarkup, :time, :time, :thumb, :thumbwidth, :thumbheight, :file, :width, :height, :filesize, :filename, :filehash, :password, :ip, :sticky, :locked, 0, :embed)", $board['uri'])); @@ -788,19 +780,19 @@ function post(array $post) { if (!empty($post['subject'])) { $query->bindValue(':subject', $post['subject']); } else { - $query->bindValue(':subject', NULL, PDO::PARAM_NULL); + $query->bindValue(':subject', null, PDO::PARAM_NULL); } if (!empty($post['email'])) { $query->bindValue(':email', $post['email']); } else { - $query->bindValue(':email', NULL, PDO::PARAM_NULL); + $query->bindValue(':email', null, PDO::PARAM_NULL); } if (!empty($post['trip'])) { $query->bindValue(':trip', $post['trip']); } else { - $query->bindValue(':trip', NULL, PDO::PARAM_NULL); + $query->bindValue(':trip', null, PDO::PARAM_NULL); } $query->bindValue(':name', $post['name']); @@ -811,27 +803,27 @@ function post(array $post) { $query->bindValue(':ip', isset($post['ip']) ? $post['ip'] : $_SERVER['REMOTE_ADDR']); if ($post['op'] && $post['mod'] && isset($post['sticky']) && $post['sticky']) { - $query->bindValue(':sticky', 1, PDO::PARAM_INT); + $query->bindValue(':sticky', true, PDO::PARAM_INT); } else { - $query->bindValue(':sticky', 0, PDO::PARAM_INT); + $query->bindValue(':sticky', false, PDO::PARAM_INT); } if ($post['op'] && $post['mod'] && isset($post['locked']) && $post['locked']) { - $query->bindValue(':locked', 1, PDO::PARAM_INT); + $query->bindValue(':locked', true, PDO::PARAM_INT); } else { - $query->bindValue(':locked', 0, PDO::PARAM_INT); + $query->bindValue(':locked', false, PDO::PARAM_INT); } if ($post['mod'] && isset($post['capcode']) && $post['capcode']) { $query->bindValue(':capcode', $post['capcode'], PDO::PARAM_INT); } else { - $query->bindValue(':capcode', NULL, PDO::PARAM_NULL); + $query->bindValue(':capcode', null, PDO::PARAM_NULL); } if (!empty($post['embed'])) { $query->bindValue(':embed', $post['embed']); } else { - $query->bindValue(':embed', NULL, PDO::PARAM_NULL); + $query->bindValue(':embed', null, PDO::PARAM_NULL); } if ($post['op']) { diff --git a/install.php b/install.php index e8bdda8b..0011a274 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ Date: Sat, 7 Sep 2013 02:57:42 +1000 Subject: purge flood cache table after filter stuff, not before --- inc/filters.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'inc/filters.php') diff --git a/inc/filters.php b/inc/filters.php index 81a33116..212118bf 100644 --- a/inc/filters.php +++ b/inc/filters.php @@ -212,8 +212,6 @@ function do_filters(array $post) { } } - purge_flood_table(); - if (isset($has_flood)) { if ($post['has_file']) { $query = prepare("SELECT * FROM ``flood`` WHERE `ip` = :ip OR `posthash` = :posthash OR `filehash` = :filehash"); @@ -224,7 +222,6 @@ function do_filters(array $post) { $query = prepare("SELECT * FROM ``flood`` WHERE `ip` = :ip OR `posthash` = :posthash"); $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); $query->bindValue(':posthash', md5($post['body_nomarkup'])); - } $query->execute() or error(db_error($query)); $flood_check = $query->fetchAll(PDO::FETCH_ASSOC); @@ -238,5 +235,7 @@ function do_filters(array $post) { if ($filter->check($post)) $filter->action(); } + + purge_flood_table(); } -- cgit v1.3 From 55dc5cedc338f56268d8e302cdc52e97206543de Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Sat, 7 Sep 2013 12:50:32 +1000 Subject: Steal make_comment_hex() from plainib --- inc/filters.php | 6 +++--- inc/functions.php | 22 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) (limited to 'inc/filters.php') diff --git a/inc/filters.php b/inc/filters.php index 212118bf..27b167ea 100644 --- a/inc/filters.php +++ b/inc/filters.php @@ -39,7 +39,7 @@ class Filter { continue 3; break; case 'body': - if ($flood_post['posthash'] != md5($post['body_nomarkup'])) + if ($flood_post['posthash'] != make_comment_hex($post['body_nomarkup'])) continue 3; break; case 'file': @@ -216,12 +216,12 @@ function do_filters(array $post) { if ($post['has_file']) { $query = prepare("SELECT * FROM ``flood`` WHERE `ip` = :ip OR `posthash` = :posthash OR `filehash` = :filehash"); $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); - $query->bindValue(':posthash', md5($post['body_nomarkup'])); + $query->bindValue(':posthash', make_comment_hex($post['body_nomarkup'])); $query->bindValue(':filehash', $post['filehash']); } else { $query = prepare("SELECT * FROM ``flood`` WHERE `ip` = :ip OR `posthash` = :posthash"); $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); - $query->bindValue(':posthash', md5($post['body_nomarkup'])); + $query->bindValue(':posthash', make_comment_hex($post['body_nomarkup'])); } $query->execute() or error(db_error($query)); $flood_check = $query->fetchAll(PDO::FETCH_ASSOC); diff --git a/inc/functions.php b/inc/functions.php index df3a4eeb..ba1b04c8 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -763,7 +763,7 @@ function insertFloodPost(array $post) { $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); $query->bindValue(':board', $board['uri']); $query->bindValue(':time', time()); - $query->bindValue(':posthash', md5($post['body_nomarkup'])); + $query->bindValue(':posthash', make_comment_hex($post['body_nomarkup'])); if ($post['has_file']) $query->bindValue(':filehash', $post['filehash']); else @@ -1191,6 +1191,26 @@ function getPages($mod=false) { return $pages; } +// Stolen with permission from PlainIB (by Frank Usrs) +function make_comment_hex($str) { + // remove cross-board citations + // the numbers don't matter + $str = preg_replace('!>>>/[A-Za-z0-9]+/!', '', $str); + + if (function_exists('iconv')) { + // remove diacritics and other noise + // FIXME: this removes cyrillic entirely + $str = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str); + } + + $str = strtolower($str); + + // strip all non-alphabet characters + $str = preg_replace('/[^a-z]/', '', $str); + + return md5($str); +} + function makerobot($body) { global $config; $body = strtolower($body); -- cgit v1.3 From b7f16dee0f4554f5e607e50f773af6852e716513 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Sat, 7 Sep 2013 12:58:23 +1000 Subject: Add ! syntax (NOT) to filters. Don't throttle duplicate post bodies when they are empty --- inc/config.php | 7 ++++--- inc/filters.php | 9 ++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'inc/filters.php') diff --git a/inc/config.php b/inc/config.php index 43550dc3..9f231e30 100644 --- a/inc/config.php +++ b/inc/config.php @@ -305,7 +305,7 @@ $config['filters'][] = array( 'condition' => array( 'flood-match' => array('ip'), // Only match IP address - 'flood-time' => &$config['flood_time'] // 10 seconds minimum + 'flood-time' => &$config['flood_time'] ), 'action' => 'reject', 'message' => &$config['error']['flood'] @@ -315,7 +315,8 @@ $config['filters'][] = array( 'condition' => array( 'flood-match' => array('ip', 'body'), // Match IP address and post body - 'flood-time' => &$config['flood_time_ip'] // 2 minutes minimum + 'flood-time' => &$config['flood_time_ip'], + '!body' => '/^$/', // Post body is NOT empty ), 'action' => 'reject', 'message' => &$config['error']['flood'] @@ -325,7 +326,7 @@ $config['filters'][] = array( 'condition' => array( 'flood-match' => array('body'), // Match IP address and post body - 'flood-time' => &$config['flood_time_same'] // 30 seconds minimum + 'flood-time' => &$config['flood_time_same'] ), 'action' => 'reject', 'message' => &$config['error']['flood'] diff --git a/inc/filters.php b/inc/filters.php index 27b167ea..5c934532 100644 --- a/inc/filters.php +++ b/inc/filters.php @@ -168,11 +168,14 @@ class Filter { public function check(array $post) { foreach ($this->condition as $condition => $value) { - if (!$this->match($post, $condition, $value)) + if ($condition[0] == '!') { + $NOT = true; + $condition = substr($condition, 1); + } else $NOT = false; + + if ($this->match($post, $condition, $value) == $NOT) return false; } - - /* match */ return true; } } -- cgit v1.3 From 0e23a6a2b43ccb388e7fcc62d5d73890c5bef019 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Sat, 7 Sep 2013 13:14:55 +1000 Subject: "flood filter" becomes "filter" --- inc/filters.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/filters.php') diff --git a/inc/filters.php b/inc/filters.php index 5c934532..d2802264 100644 --- a/inc/filters.php +++ b/inc/filters.php @@ -107,7 +107,7 @@ class Filter { switch($this->action) { case 'reject': - error(isset($this->message) ? $this->message : 'Posting throttled by flood filter.'); + error(isset($this->message) ? $this->message : 'Posting throttled by filter.'); case 'ban': if (!isset($this->reason)) error('The ban action requires a reason.'); -- cgit v1.3 From d4cf4c7afb975cead41768c9e0d1960a46183409 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Sun, 8 Sep 2013 15:07:55 +1000 Subject: flood-count condition --- inc/config.php | 19 ++++++++++++++++--- inc/filters.php | 8 ++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) (limited to 'inc/filters.php') diff --git a/inc/config.php b/inc/config.php index 0cdbe709..0eac92b6 100644 --- a/inc/config.php +++ b/inc/config.php @@ -342,7 +342,20 @@ // 'message' => &$config['error']['flood'] // ); - // An example of blocking an imaginary known spammer, who keeps posting a reply with the name "surgeon", + // Example: Use the "flood-count" condition to only match if the user has made at least two posts with + // the same content and IP address in the past 2 minutes. + // $config['filters'][] = array( + // 'condition' => array( + // 'flood-match' => array('ip', 'body'), // Match IP address and post body + // 'flood-time' => 60 * 2, // 2 minutes + // 'flood-count' => 2 // At least two recent posts + // ), + // '!body' => '/^$/', + // 'action' => 'reject', + // 'message' => &$config['error']['flood'] + // ); + + // Example: Blocking an imaginary known spammer, who keeps posting a reply with the name "surgeon", // ending his posts with "regards, the surgeon" or similar. // $config['filters'][] = array( // 'condition' => array( @@ -354,7 +367,7 @@ // 'message' => 'Go away, spammer.' // ); - // Same as above, but issuing a 3-hour ban instead of just reject the post. + // Example: Same as above, but issuing a 3-hour ban instead of just reject the post. // $config['filters'][] = array( // 'condition' => array( // 'name' => '/^surgeon$/', @@ -366,7 +379,7 @@ // 'reason' => 'Go away, spammer.' // ); - // PHP 5.3+ (anonymous functions) + // Example: PHP 5.3+ (anonymous functions) // There is also a "custom" condition, making the possibilities of this feature pretty much endless. // This is a bad example, because there is already a "name" condition built-in. // $config['filters'][] = array( diff --git a/inc/filters.php b/inc/filters.php index d2802264..d5353afb 100644 --- a/inc/filters.php +++ b/inc/filters.php @@ -73,6 +73,14 @@ class Filter { } } return false; + case 'flood-count': + $count = 0; + foreach ($this->flood_check as $flood_post) { + if (time() - $flood_post['time'] <= $this->condition['flood-time']) { + ++$count; + } + } + return $count >= $match; case 'name': return preg_match($match, $post['name']); case 'trip': -- cgit v1.3 From 99fa2f7cda5c8c912ef9e2779c2d75c7d45658c9 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 10 Sep 2013 03:54:23 +1000 Subject: Bugfix: SQL error, issue #141 --- inc/filters.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'inc/filters.php') diff --git a/inc/filters.php b/inc/filters.php index d5353afb..aa6f9025 100644 --- a/inc/filters.php +++ b/inc/filters.php @@ -207,7 +207,7 @@ function purge_flood_table() { $time = time() - $max_time; - query("DELETE FROM ``flood`` WHERE ``time`` < $time") or error(db_error()); + query("DELETE FROM ``flood`` WHERE `time` < $time") or error(db_error()); } function do_filters(array $post) { -- cgit v1.3 From 3e57bb04d7b8fc62761c0fa50b4d062701b1a82a Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 17 Sep 2013 09:15:24 +1000 Subject: Begin upgrade to much better bans table. DO NOT PULL YET; It won't work. --- inc/config.php | 12 +----- inc/database.php | 2 +- inc/filters.php | 44 +++----------------- inc/functions.php | 66 +++++++++--------------------- inc/mod/ban.php | 99 +-------------------------------------------- inc/mod/config-editor.php | 6 +++ inc/mod/pages.php | 64 +++++++---------------------- install.php | 57 +++++++++++++++++++++++++- js/ajax.js | 21 ++++++++-- templates/banned.html | 2 +- templates/mod/ban_list.html | 12 +++--- templates/mod/view_ip.html | 4 +- 12 files changed, 132 insertions(+), 257 deletions(-) (limited to 'inc/filters.php') diff --git a/inc/config.php b/inc/config.php index e6856341..d7bb8ecc 100644 --- a/inc/config.php +++ b/inc/config.php @@ -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 diff --git a/inc/database.php b/inc/database.php index 861d4c1e..84a050f2 100644 --- a/inc/database.php +++ b/inc/database.php @@ -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); } diff --git a/inc/filters.php b/inc/filters.php index aa6f9025..f06154ba 100644 --- a/inc/filters.php +++ b/inc/filters.php @@ -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); diff --git a/inc/functions.php b/inc/functions.php index 4cbd8625..f7421f68 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -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)) - )"; - - $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)); + $bans = Bans::find($_SERVER['REMOTE_ADDR'], $board); - 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']) { - displayBan($ban); + if (!isset($_POST['json_response'])) { + displayBan($ban); + } else { + header('Content-Type: text/json'); + die(json_encode(array('error' => true, 'banned' => true))); + } } } else { - displayBan($ban); + 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()); diff --git a/inc/mod/ban.php b/inc/mod/ban.php index 71173326..82260585 100644 --- a/inc/mod/ban.php +++ b/inc/mod/ban.php @@ -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 ? "$mask" : utf8tohtml($mask)) . - ' (#' . $pdo->lastInsertId() . ')' . - ' 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 ? "$mask" : utf8tohtml($mask))); -} +// This file is no longer used. diff --git a/inc/mod/config-editor.php b/inc/mod/config-editor.php index 4493fd2b..0f6dbc9a 100644 --- a/inc/mod/config-editor.php +++ b/inc/mod/config-editor.php @@ -1,5 +1,11 @@ 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()); + 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))); - foreach ($unban as $id) { - modLog("Removed ban #{$id}"); - } + foreach ($unban as $id) { + Bans::delete($id, true); } - } else { - 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}"); - } - } - - } 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 diff --git a/install.php b/install.php index 639a90c9..69be6300 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ 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); diff --git a/js/ajax.js b/js/ajax.js index a063890b..74cfe9c0 100644 --- a/js/ajax.js +++ b/js/ajax.js @@ -43,9 +43,23 @@ $(window).ready(function() { }, success: function(post_response) { if (post_response.error) { - alert(post_response.error); - $(form).find('input[type="submit"]').val(submit_txt); - $(form).find('input[type="submit"]').removeAttr('disabled'); + 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 = $(''); + $replacement.attr('name', $(this).attr('name')); + $replacement.val(submit_txt); + $(this) + .after($replacement) + .replaceWith($('').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 = $(''); diff --git a/templates/banned.html b/templates/banned.html index a4498cf5..065d61e2 100644 --- a/templates/banned.html +++ b/templates/banned.html @@ -30,7 +30,7 @@ {% endif %}

{% trans %}Your ban was filed on{% endtrans %} - {{ ban.set|date(config.ban_date) }} {% trans %}and{% endtrans %} + {{ ban.created|date(config.ban_date) }} {% trans %}and{% endtrans %} {% if ban.expires and time() >= ban.expires %} {% trans %} has since expired. Refresh the page to continue.{% endtrans %} {% elseif ban.expires %} diff --git a/templates/mod/ban_list.html b/templates/mod/ban_list.html index f8826ade..83674544 100644 --- a/templates/mod/ban_list.html +++ b/templates/mod/ban_list.html @@ -17,10 +17,10 @@ - {% if ban.real_ip %} - {{ ban.ip }} + {% if ban.single_addr %} + {{ ban.mask }} {% else %} - {{ ban.ip|e }} + {{ ban.mask }} {% endif %} @@ -38,8 +38,8 @@ {% endif %} - - {{ ban.set|ago }} ago + + {{ ban.created|ago }} ago @@ -77,7 +77,7 @@ {% endif %} {% endif %} - {% elseif ban.mod == -1 %} + {% elseif ban.creator == -1 %} system {% else %} {% trans 'deleted?' %} diff --git a/templates/mod/view_ip.html b/templates/mod/view_ip.html index e2dad7af..f73c5d5a 100644 --- a/templates/mod/view_ip.html +++ b/templates/mod/view_ip.html @@ -100,7 +100,7 @@ {% trans 'IP' %} - {{ ban.ip }} + {{ ban.mask }} {% trans 'Reason' %} @@ -124,7 +124,7 @@ {% trans 'Set' %} - {{ ban.set|date(config.post_date) }} + {{ ban.created|date(config.post_date) }} {% trans 'Expires' %} -- cgit v1.3