From 609da4354824393811ad7f6bff18f25667719620 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Tue, 9 Jul 2024 00:17:23 +0200 Subject: [PATCH 01/21] anti-bot.php: trim --- inc/anti-bot.php | 68 ++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/inc/anti-bot.php b/inc/anti-bot.php index 29279296..aa0eeb99 100644 --- a/inc/anti-bot.php +++ b/inc/anti-bot.php @@ -10,7 +10,7 @@ $hidden_inputs_twig = array(); class AntiBot { public $salt, $inputs = array(), $index = 0; - + public static function randomString($length, $uppercase = false, $special_chars = false, $unicode_chars = false) { $chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; if ($uppercase) @@ -22,11 +22,11 @@ class AntiBot { for ($n = 0; $n < $len; $n++) $chars .= mb_convert_encoding('&#' . mt_rand(0x2600, 0x26FF) . ';', 'UTF-8', 'HTML-ENTITIES'); } - + $chars = preg_split('//u', $chars, -1, PREG_SPLIT_NO_EMPTY); - + $ch = array(); - + // fill up $ch until we reach $length while (count($ch) < $length) { $n = $length - count($ch); @@ -39,40 +39,40 @@ class AntiBot { foreach ($keys as $key) $ch[] = $chars[$key]; } - + $chars = $ch; - + return implode('', $chars); } - + public static function make_confusing($string) { $chars = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); - + foreach ($chars as &$c) { if (mt_rand(0, 3) != 0) $c = utf8tohtml($c); else $c = mb_encode_numericentity($c, array(0, 0xffff, 0, 0xffff), 'UTF-8'); } - + return implode('', $chars); } - + public function __construct(array $salt = array()) { global $config; - + if (!empty($salt)) { // create a salted hash of the "extra salt" $this->salt = implode(':', $salt); - } else { + } else { $this->salt = ''; } - + shuffle($config['spam']['hidden_input_names']); - + $input_count = mt_rand($config['spam']['hidden_inputs_min'], $config['spam']['hidden_inputs_max']); $hidden_input_names_x = 0; - + for ($x = 0; $x < $input_count ; $x++) { if ($hidden_input_names_x === false || mt_rand(0, 2) == 0) { // Use an obscure name @@ -83,7 +83,7 @@ class AntiBot { if ($hidden_input_names_x >= count($config['spam']['hidden_input_names'])) $hidden_input_names_x = false; } - + if (mt_rand(0, 2) == 0) { // Value must be null $this->inputs[$name] = ''; @@ -96,16 +96,16 @@ class AntiBot { } } } - + public static function space() { if (mt_rand(0, 3) != 0) return ' '; return str_repeat(' ', mt_rand(1, 3)); } - + public function html($count = false) { global $config; - + $elements = array( '', '', @@ -119,13 +119,13 @@ class AntiBot { '', '' ); - + $html = ''; - + if ($count === false) { $count = mt_rand(1, (int)abs(count($this->inputs) / 15) + 1); } - + if ($count === true) { // all elements $inputs = array_slice($this->inputs, $this->index); @@ -133,7 +133,7 @@ class AntiBot { $inputs = array_slice($this->inputs, $this->index, $count); } $this->index += count($inputs); - + foreach ($inputs as $name => $value) { $element = false; while (!$element) { @@ -146,37 +146,37 @@ class AntiBot { $element = false; } } - + $element = str_replace('%name%', utf8tohtml($name), $element); - + if (mt_rand(0, 2) == 0) $value = $this->make_confusing($value); else $value = utf8tohtml($value); - + if (strpos($element, 'textarea') === false) $value = str_replace('"', '"', $value); - + $element = str_replace('%value%', $value, $element); - + $html .= $element; } - + return $html; } - + public function reset() { $this->index = 0; } - + public function hash() { global $config; - + // This is the tricky part: create a hash to validate it after // First, sort the keys in alphabetical order (A-Z) $inputs = $this->inputs; ksort($inputs); - + $hash = ''; // Iterate through each input foreach ($inputs as $name => $value) { @@ -184,7 +184,7 @@ class AntiBot { } // Add a salt to the hash $hash .= $config['cookies']['salt']; - + // Use SHA1 for the hash return sha1($hash . $this->salt); } From ee20bf574ae61b28119d9db1823777d35b07b9b2 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 11 Aug 2024 10:14:54 +0200 Subject: [PATCH 02/21] functions.php: format _create_antibot --- inc/functions.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index f1c2c26c..3530ab77 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1615,21 +1615,23 @@ function checkMute() { function _create_antibot($board, $thread) { global $config, $purged_old_antispam; - $antibot = new AntiBot(array($board, $thread)); + $antibot = new AntiBot([$board, $thread]); if (!isset($purged_old_antispam)) { $purged_old_antispam = true; query('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error()); } - if ($thread) + if ($thread) { $query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` = :thread AND `expires` IS NULL'); - else + } else { $query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` IS NULL AND `expires` IS NULL'); + } $query->bindValue(':board', $board); - if ($thread) + if ($thread) { $query->bindValue(':thread', $thread); + } $query->bindValue(':expires', $config['spam']['hidden_inputs_expire']); $query->execute() or error(db_error($query)); From 00cc1f434d30cd489141d2316876f42cc10ce15f Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 11 Jul 2024 21:16:55 +0200 Subject: [PATCH 03/21] anti-bot.php: add comments to _create_antibot --- inc/functions.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inc/functions.php b/inc/functions.php index 3530ab77..d8943530 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1617,11 +1617,15 @@ function _create_antibot($board, $thread) { $antibot = new AntiBot([$board, $thread]); + // Delete old expired antispam, skipping those with NULL expiration timestamps (infinite lifetime). if (!isset($purged_old_antispam)) { $purged_old_antispam = true; query('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error()); } + // Keep the now invalid timestamps around for a bit to enable users to post if they're still on an old version of + // the HTML page. + // By virtue of existing, we know that we're making a new version of the page, and the user from now on may just reload. if ($thread) { $query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` = :thread AND `expires` IS NULL'); } else { @@ -1635,6 +1639,7 @@ function _create_antibot($board, $thread) { $query->bindValue(':expires', $config['spam']['hidden_inputs_expire']); $query->execute() or error(db_error($query)); + // Insert an antispam with infinite life as the HTML page of a thread might last well beyond the expiry date. $query = prepare('INSERT INTO ``antispam`` VALUES (:board, :thread, :hash, UNIX_TIMESTAMP(), NULL, 0)'); $query->bindValue(':board', $board); $query->bindValue(':thread', $thread); From e4707ee2a8e8485432e2a1e160bc41a470926c7a Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 14 Jul 2024 00:25:52 +0200 Subject: [PATCH 04/21] Delete stale unreferenced ban appeals via foreign key constrain --- inc/mod/pages.php | 4 ---- install.sql | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 77fba803..e0eeabbc 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1044,10 +1044,6 @@ function mod_ban_appeals() { if (!hasPermission($config['mod']['view_ban_appeals'])) error($config['error']['noaccess']); - // Remove stale ban appeals - query("DELETE FROM ``ban_appeals`` WHERE NOT EXISTS (SELECT 1 FROM ``bans`` WHERE `ban_id` = ``bans``.`id`)") - or error(db_error()); - if (isset($_POST['appeal_id']) && (isset($_POST['unban']) || isset($_POST['deny']))) { if (!hasPermission($config['mod']['ban_appeals'])) error($config['error']['noaccess']); diff --git a/install.sql b/install.sql index ee005a99..fa6d223a 100644 --- a/install.sql +++ b/install.sql @@ -294,7 +294,8 @@ CREATE TABLE IF NOT EXISTS `ban_appeals` ( `message` text NOT NULL, `denied` tinyint(1) NOT NULL, PRIMARY KEY (`id`), - KEY `ban_id` (`ban_id`) + KEY `ban_id` (`ban_id`), + CONSTRAINT `fk_ban_id` FOREIGN KEY (`ban_id`) REFERENCES `bans`(`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; -- -------------------------------------------------------- From 980b2ef7bf09f5b0e4d6bdfafc683c9537c681aa Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 30 Jun 2024 16:15:47 +0200 Subject: [PATCH 05/21] bans.php: split findSingle implementations --- inc/bans.php | 92 +++++++++++++++++++++++++++++++++------------------- post.php | 2 +- 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/inc/bans.php b/inc/bans.php index 0da89640..ef9ff6e5 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -33,6 +33,59 @@ class Bans { } } + static private function findSingleAutoGc(string $ip, int $ban_id, bool $require_ban_view): array|null { + // Use OR in the query to also garbage collect bans. + $query = prepare( + 'SELECT ``bans``.* FROM ``bans`` + WHERE ((`ipstart` = :ip OR (:ip >= `ipstart` AND :ip <= `ipend`)) OR (``bans``.id = :id)) + ORDER BY `expires` IS NULL, `expires` DESC' + ); + + $query->bindValue(':id', $ban_id); + $query->bindValue(':ip', inet_pton($ip)); + + $query->execute() or error(db_error($query)); + + $found_ban = null; + $to_delete_list = []; + + while ($ban = $query->fetch(PDO::FETCH_ASSOC)) { + if ($ban['expires'] && ($ban['seen'] || !$require_ban_view) && $ban['expires'] < time()) { + $to_delete_list[] = $ban['id']; + } elseif ($ban['id'] === $ban_id) { + if ($ban['post']) { + $ban['post'] = json_decode($ban['post'], true); + } + $ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); + $found_ban = $ban; + } + } + + self::deleteBans($to_delete_list); + + return $found_ban; + } + + static private function findSingleNoGc(int $ban_id): array|null { + $query = prepare( + 'SELECT ``bans``.* FROM ``bans`` + WHERE ``bans``.id = :id + ORDER BY `expires` IS NULL, `expires` DESC + LIMIT 1' + ); + + $query->bindValue(':id', $ban_id); + + $query->execute() or error(db_error($query)); + $ret = $query->fetch(PDO::FETCH_ASSOC); + if ($query->rowCount() == 0) { + return null; + } else { + $ret['post'] = json_decode($ret['post'], true); + return $ret; + } + } + static public function range_to_string($mask) { list($ipstart, $ipend) = $mask; @@ -143,41 +196,12 @@ class Bans { return array($ipstart, $ipend); } - static public function findSingle(string $ip, int $ban_id, bool $require_ban_view): array|null { - /** - * Use OR in the query to also garbage collect bans. Ideally we should move the whole GC procedure to a separate - * script, but it will require a more important restructuring. - */ - $query = prepare( - 'SELECT ``bans``.* FROM ``bans`` - WHERE ((`ipstart` = :ip OR (:ip >= `ipstart` AND :ip <= `ipend`)) OR (``bans``.id = :id)) - ORDER BY `expires` IS NULL, `expires` DESC' - ); - - $query->bindValue(':id', $ban_id); - $query->bindValue(':ip', inet_pton($ip)); - - $query->execute() or error(db_error($query)); - - $found_ban = null; - $to_delete_list = []; - - while ($ban = $query->fetch(PDO::FETCH_ASSOC)) { - if ($ban['expires'] && ($ban['seen'] || !$require_ban_view) && $ban['expires'] < time()) { - $to_delete_list[] = $ban['id']; - } elseif ($ban['id'] === $ban_id) { - if ($ban['post']) { - $ban['post'] = json_decode($ban['post'], true); - } - $ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); - $ban['cmask'] = cloak_mask($ban['mask']); - $found_ban = $ban; - } + static public function findSingle(string $ip, int $ban_id, bool $require_ban_view, bool $auto_gc): array|null { + if ($auto_gc) { + return self::findSingleAutoGc($ip, $ban_id, $require_ban_view); + } else { + return self::findSingleNoGc($ban_id); } - - self::deleteBans($to_delete_list); - - return $found_ban; } static public function find($ip, $board = false, $get_mod_info = false, $banid = null) { diff --git a/post.php b/post.php index cc45ccb2..9d1c1783 100644 --- a/post.php +++ b/post.php @@ -1439,7 +1439,7 @@ if (isset($_POST['delete'])) { $ban_id = (int)$_POST['ban_id']; - $ban = Bans::findSingle($_SERVER['REMOTE_ADDR'], $ban_id, $config['require_ban_view']); + $ban = Bans::findSingle($_SERVER['REMOTE_ADDR'], $ban_id, $config['require_ban_view'], true); if (empty($ban)) { error($config['error']['noban']); From 36476f6341871d8f1bbcfc5f9a580c528c3893e4 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 30 Jun 2024 16:34:38 +0200 Subject: [PATCH 06/21] bans.php: split find implementations --- inc/bans.php | 105 +++++++++++++++++++++++++++++++--------------- inc/functions.php | 2 +- inc/mod/pages.php | 4 +- 3 files changed, 75 insertions(+), 36 deletions(-) diff --git a/inc/bans.php b/inc/bans.php index ef9ff6e5..ee0d086d 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -86,6 +86,73 @@ class Bans { } } + static private function findAutoGc(?string $ip, string|false $board, bool $get_mod_info, bool $require_ban_view, ?int $ban_id): array { + $query = prepare('SELECT ``bans``.*' . ($get_mod_info ? ', `username`' : '') . ' FROM ``bans`` + ' . ($get_mod_info ? 'LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`' : '') . ' + WHERE + (' . ($board !== false ? '(`board` IS NULL OR `board` = :board) AND' : '') . ' + (`ipstart` = :ip OR (:ip >= `ipstart` AND :ip <= `ipend`)) OR (``bans``.id = :id)) + ORDER BY `expires` IS NULL, `expires` DESC'); + + if ($board !== false) { + $query->bindValue(':board', $board, PDO::PARAM_STR); + } + + $query->bindValue(':id', $ban_id); + $query->bindValue(':ip', inet_pton($ip)); + $query->execute() or error(db_error($query)); + + $ban_list = []; + $to_delete_list = []; + + while ($ban = $query->fetch(PDO::FETCH_ASSOC)) { + if ($ban['expires'] && ($ban['seen'] || !$require_ban_view) && $ban['expires'] < time()) { + $to_delete_list[] = $ban['id']; + } else { + if ($ban['post']) { + $ban['post'] = json_decode($ban['post'], true); + } + $ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); + $ban_list[] = $ban; + } + } + + self::deleteBans($to_delete_list); + + return $ban_list; + } + + static private function findNoGc(?string $ip, string|false $board, bool $get_mod_info, ?int $ban_id): array { + $query = prepare('SELECT ``bans``.*' . ($get_mod_info ? ', `username`' : '') . ' FROM ``bans`` + ' . ($get_mod_info ? 'LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`' : '') . ' + WHERE + (' . ($board !== false ? '(`board` IS NULL OR `board` = :board) AND' : '') . ' + (`ipstart` = :ip OR (:ip >= `ipstart` AND :ip <= `ipend`)) OR (``bans``.id = :id)) + AND `expires` IS NULL OR `expires` >= :curr_time + ORDER BY `expires` IS NULL, `expires` DESC'); + + if ($board !== false) { + $query->bindValue(':board', $board, PDO::PARAM_STR); + } + + $query->bindValue(':id', $ban_id); + $query->bindValue(':ip', inet_pton($ip)); + $query->bindValue(':curr_time', time()); + $query->execute() or error(db_error($query)); + + $ban_list = []; + + while ($ban = $query->fetch(PDO::FETCH_ASSOC)) { + if ($ban['post']) { + $ban['post'] = json_decode($ban['post'], true); + } + $ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); + $ban_list[] = $ban; + } + + return $ban_list; + } + static public function range_to_string($mask) { list($ipstart, $ipend) = $mask; @@ -204,42 +271,14 @@ class Bans { } } - static public function find($ip, $board = false, $get_mod_info = false, $banid = null) { + static public function find(?string $ip, string|false $board = false, bool $get_mod_info = false, ?int $ban_id = null, bool $auto_gc = true) { global $config; - $query = prepare('SELECT ``bans``.*' . ($get_mod_info ? ', `username`' : '') . ' FROM ``bans`` - ' . ($get_mod_info ? 'LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`' : '') . ' - WHERE - (' . ($board !== false ? '(`board` IS NULL OR `board` = :board) AND' : '') . ' - (`ipstart` = :ip OR (:ip >= `ipstart` AND :ip <= `ipend`)) OR (``bans``.id = :id)) - ORDER BY `expires` IS NULL, `expires` DESC'); - - if ($board !== false) - $query->bindValue(':board', $board, PDO::PARAM_STR); - - $query->bindValue(':id', $banid); - $query->bindValue(':ip', inet_pton($ip)); - - $query->execute() or error(db_error($query)); - - $ban_list = array(); - $to_delete_list = []; - - while ($ban = $query->fetch(PDO::FETCH_ASSOC)) { - if ($ban['expires'] && ($ban['seen'] || !$config['require_ban_view']) && $ban['expires'] < time()) { - $to_delete_list[] = $ban['id']; - } else { - if ($ban['post']) - $ban['post'] = json_decode($ban['post'], true); - $ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); - $ban['cmask'] = cloak_mask($ban['mask']); - $ban_list[] = $ban; - } + if ($auto_gc) { + return self::findAutoGc($ip, $board, $get_mod_info, $config['require_ban_view'], $ban_id); + } else { + return self::findNoGc($ip, $board, $get_mod_info, $ban_id); } - - self::deleteBans($to_delete_list); - - return $ban_list; } static public function stream_json($out = false, $filter_ips = false, $filter_staff = false, $board_access = false) { diff --git a/inc/functions.php b/inc/functions.php index d8943530..908aff59 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -876,7 +876,7 @@ function checkBan($board = false) { } foreach ($ips as $ip) { - $bans = Bans::find($ip, $board, $config['show_modname']); + $bans = Bans::find($ip, $board, $config['show_modname'], null, true); foreach ($bans as &$ban) { if ($ban['expires'] && $ban['expires'] < time()) { diff --git a/inc/mod/pages.php b/inc/mod/pages.php index e0eeabbc..7483a27d 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -897,7 +897,7 @@ function mod_page_ip($cip) { $args['token'] = make_secure_link_token('ban'); if (hasPermission($config['mod']['view_ban'])) { - $args['bans'] = Bans::find($ip, false, true); + $args['bans'] = Bans::find($ip, false, true, null, true); } if (hasPermission($config['mod']['view_notes'])) { @@ -927,7 +927,7 @@ function mod_edit_ban($ban_id) { if (!hasPermission($config['mod']['edit_ban'])) error($config['error']['noaccess']); - $args['bans'] = Bans::find(null, false, true, $ban_id); + $args['bans'] = Bans::find(null, false, true, $ban_id, true); $args['ban_id'] = $ban_id; $args['boards'] = listBoards(); $args['current_board'] = isset($args['bans'][0]['board']) ? $args['bans'][0]['board'] : false; From 75714505a0f04b014c9e37dbfaf9939e11037e5f Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 30 Jun 2024 16:48:22 +0200 Subject: [PATCH 07/21] config.php: add auto_maintenance configuration option --- inc/config.php | 5 +++++ inc/functions.php | 2 +- inc/mod/pages.php | 4 ++-- post.php | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/inc/config.php b/inc/config.php index c2c25da2..880de88b 100644 --- a/inc/config.php +++ b/inc/config.php @@ -92,6 +92,11 @@ // to the environment path (seperated by :). $config['shell_path'] = '/usr/local/bin'; + // Automatically execute some maintenance tasks when some pages are opened, which may result in higher + // latencies. + // If set to false, ensure to periodically invoke the tools/maintenance.php script. + $config['auto_maintenance'] = true; + /* * ==================== * Database settings diff --git a/inc/functions.php b/inc/functions.php index 908aff59..31e5dfac 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -876,7 +876,7 @@ function checkBan($board = false) { } foreach ($ips as $ip) { - $bans = Bans::find($ip, $board, $config['show_modname'], null, true); + $bans = Bans::find($ip, $board, $config['show_modname'], null, $config['auto_maintenance']); foreach ($bans as &$ban) { if ($ban['expires'] && $ban['expires'] < time()) { diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 7483a27d..826e7c6f 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -897,7 +897,7 @@ function mod_page_ip($cip) { $args['token'] = make_secure_link_token('ban'); if (hasPermission($config['mod']['view_ban'])) { - $args['bans'] = Bans::find($ip, false, true, null, true); + $args['bans'] = Bans::find($ip, false, true, null, $config['auto_maintenance']); } if (hasPermission($config['mod']['view_notes'])) { @@ -927,7 +927,7 @@ function mod_edit_ban($ban_id) { if (!hasPermission($config['mod']['edit_ban'])) error($config['error']['noaccess']); - $args['bans'] = Bans::find(null, false, true, $ban_id, true); + $args['bans'] = Bans::find(null, false, true, $ban_id, $config['auto_maintenance']); $args['ban_id'] = $ban_id; $args['boards'] = listBoards(); $args['current_board'] = isset($args['bans'][0]['board']) ? $args['bans'][0]['board'] : false; diff --git a/post.php b/post.php index 9d1c1783..8eab4da2 100644 --- a/post.php +++ b/post.php @@ -1439,7 +1439,7 @@ if (isset($_POST['delete'])) { $ban_id = (int)$_POST['ban_id']; - $ban = Bans::findSingle($_SERVER['REMOTE_ADDR'], $ban_id, $config['require_ban_view'], true); + $ban = Bans::findSingle($_SERVER['REMOTE_ADDR'], $ban_id, $config['require_ban_view'], $config['auto_maintenance']); if (empty($ban)) { error($config['error']['noban']); From cbaf19cb7a4294a2c983b5407080dcd2a9845aca Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 30 Jun 2024 17:01:53 +0200 Subject: [PATCH 08/21] bans.php: make the purge configurable --- inc/bans.php | 17 ++++++++++++++--- inc/functions.php | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/inc/bans.php b/inc/bans.php index ee0d086d..a8f9118d 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -342,9 +342,20 @@ class Bans { rebuildThemes('bans'); } - static public function purge() { - $query = query("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < " . time() . " AND `seen` = 1") or error(db_error()); - rebuildThemes('bans'); + static public function purge($require_seen) { + if ($require_seen) { + $query = prepare("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < :curr_time AND `seen` = 1"); + } else { + $query = prepare("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < :curr_time"); + } + $query->bindValue(':curr_time', time()); + $query->execute() or error(db_error($query)); + + $affected = $query->rowCount(); + if ($affected > 0) { + rebuildThemes('bans'); + } + return $affected; } static public function delete($ban_id, $modlog = false, $boards = false, $dont_rebuild = false) { diff --git a/inc/functions.php b/inc/functions.php index 31e5dfac..369242cb 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -907,7 +907,7 @@ function checkBan($board = false) { return; } - Bans::purge(); + Bans::purge($config['require_ban_view']); if ($config['cache']['enabled']) cache::set('purged_bans_last', time()); From accca93084f004e8782aa0503c777b89809ad4c8 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 30 Jun 2024 17:02:21 +0200 Subject: [PATCH 09/21] Add maintenance.php to the tools --- tools/maintenance.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tools/maintenance.php diff --git a/tools/maintenance.php b/tools/maintenance.php new file mode 100644 index 00000000..14de7a94 --- /dev/null +++ b/tools/maintenance.php @@ -0,0 +1,13 @@ + Date: Mon, 1 Jul 2024 21:45:46 +0200 Subject: [PATCH 10/21] bans.php: use modify inplace --- inc/bans.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/inc/bans.php b/inc/bans.php index a8f9118d..528f8646 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -140,16 +140,12 @@ class Bans { $query->bindValue(':curr_time', time()); $query->execute() or error(db_error($query)); - $ban_list = []; - - while ($ban = $query->fetch(PDO::FETCH_ASSOC)) { + $ban_list = $query->fetchAll(PDO::FETCH_ASSOC); + array_walk($ban_list, function (&$ban, $_index) { if ($ban['post']) { $ban['post'] = json_decode($ban['post'], true); } - $ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); - $ban_list[] = $ban; - } - + }); return $ban_list; } From 82b8eb1e74bdede16f3ba7e2b35a1795ccb14867 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 1 Jul 2024 21:52:03 +0200 Subject: [PATCH 11/21] bans.php: group deletion policy --- inc/bans.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/inc/bans.php b/inc/bans.php index 528f8646..bc65ab8f 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -4,6 +4,10 @@ use Vichan\Functions\Format; use Lifo\IP\CIDR; class Bans { + static private function shouldDelete(array $ban, bool $require_ban_view) { + return $ban['expires'] && ($ban['seen'] || !$require_ban_view) && $ban['expires'] < time(); + } + static private function deleteBans(array $ban_ids) { $len = count($ban_ids); if ($len === 1) { @@ -50,7 +54,7 @@ class Bans { $to_delete_list = []; while ($ban = $query->fetch(PDO::FETCH_ASSOC)) { - if ($ban['expires'] && ($ban['seen'] || !$require_ban_view) && $ban['expires'] < time()) { + if (self::shouldDelete($ban, $require_ban_view)) { $to_delete_list[] = $ban['id']; } elseif ($ban['id'] === $ban_id) { if ($ban['post']) { @@ -106,7 +110,7 @@ class Bans { $to_delete_list = []; while ($ban = $query->fetch(PDO::FETCH_ASSOC)) { - if ($ban['expires'] && ($ban['seen'] || !$require_ban_view) && $ban['expires'] < time()) { + if (self::shouldDelete($ban, $require_ban_view)) { $to_delete_list[] = $ban['id']; } else { if ($ban['post']) { From cc5e96eb9d5344fe8e8f366dddcf00f50ccb678f Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 1 Jul 2024 21:56:35 +0200 Subject: [PATCH 12/21] bans.php: use modern array syntax --- inc/bans.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/inc/bans.php b/inc/bans.php index bc65ab8f..0e462a7b 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -60,7 +60,7 @@ class Bans { if ($ban['post']) { $ban['post'] = json_decode($ban['post'], true); } - $ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); + $ban['mask'] = self::range_to_string([$ban['ipstart'], $ban['ipend']]); $found_ban = $ban; } } @@ -116,7 +116,7 @@ class Bans { if ($ban['post']) { $ban['post'] = json_decode($ban['post'], true); } - $ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); + $ban['mask'] = self::range_to_string([$ban['ipstart'], $ban['ipend']]); $ban_list[] = $ban; } } @@ -176,7 +176,7 @@ class Bans { $cidr = new CIDR($mask); $range = $cidr->getRange(); - return array(inet_pton($range[0]), inet_pton($range[1])); + return [ inet_pton($range[0]), inet_pton($range[1]) ]; } public static function parse_time($str) { @@ -260,7 +260,7 @@ class Bans { return false; } - return array($ipstart, $ipend); + return [$ipstart, $ipend]; } static public function findSingle(string $ip, int $ban_id, bool $require_ban_view, bool $auto_gc): array|null { @@ -294,8 +294,7 @@ class Bans { $end = end($bans); foreach ($bans as &$ban) { - $uncloaked_mask = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); - $ban['mask'] = cloak_mask($uncloaked_mask); + $ban['mask'] = self::range_to_string([$ban['ipstart'], $ban['ipend']]); if ($ban['post']) { $post = json_decode($ban['post']); @@ -373,8 +372,7 @@ class Bans { if ($boards !== false && !in_array($ban['board'], $boards)) error($config['error']['noaccess']); - $mask = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); - $cloaked_mask = cloak_mask($mask); + $mask = self::range_to_string([$ban['ipstart'], $ban['ipend']]); modLog("Removed ban #{$ban_id} for " . (filter_var($mask, FILTER_VALIDATE_IP) !== false ? "$cloaked_mask" : $cloaked_mask)); From e5bbdb9d2839e7fa8c10d812c2436db4d9a59fad Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 8 Jul 2024 23:58:54 +0200 Subject: [PATCH 13/21] functions.php: make automated antispam puring optional --- inc/functions.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/inc/functions.php b/inc/functions.php index 369242cb..17b11723 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1612,13 +1612,19 @@ function checkMute() { } } +function purge_old_antispam() { + $query = prepare('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()'); + $query->execute() or error(db_error()); + return $query->rowCount(); +} + function _create_antibot($board, $thread) { global $config, $purged_old_antispam; $antibot = new AntiBot([$board, $thread]); // Delete old expired antispam, skipping those with NULL expiration timestamps (infinite lifetime). - if (!isset($purged_old_antispam)) { + if (!isset($purged_old_antispam) && $config['auto_maintenance']) { $purged_old_antispam = true; query('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error()); } From 4d97e69620fa1e37e33d08cb0b9eff046620b62f Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 8 Jul 2024 23:59:17 +0200 Subject: [PATCH 14/21] maintenance.php: add purging antispam to the tool --- tools/maintenance.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/maintenance.php b/tools/maintenance.php index 14de7a94..cdc1089d 100644 --- a/tools/maintenance.php +++ b/tools/maintenance.php @@ -10,4 +10,11 @@ $start = microtime(true); $deleted_count = Bans::purge($config['require_ban_view']); $delta = microtime(true) - $start; echo "Deleted $deleted_count expired bans in $delta seconds!"; -modLog('Deleted expired bans using tools/maintenance.php'); +modLog("Deleted expired bans in {$delta}s with tools/maintenance.php"); + +echo "Clearing old antispam..."; +$start = microtime(true); +$deleted_count = purge_old_antispam(); +$delta = microtime(true) - $start; +echo "Deleted $deleted_count expired antispam in $delta seconds!"; +modLog("Deleted expired antispam in {$delta}s with tools/maintenance.php"); From c057c6df298292934cde1c7e1bcacdf07ee918b2 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 11 Jul 2024 22:07:36 +0200 Subject: [PATCH 15/21] functions.php: skip ban deletion on auto_maintenance disabled --- inc/functions.php | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 17b11723..386f7b38 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -880,7 +880,9 @@ function checkBan($board = false) { foreach ($bans as &$ban) { if ($ban['expires'] && $ban['expires'] < time()) { - Bans::delete($ban['id']); + if ($config['auto_maintenance']) { + Bans::delete($ban['id']); + } if ($config['require_ban_view'] && !$ban['seen']) { if (!isset($_POST['json_response'])) { displayBan($ban); @@ -900,17 +902,20 @@ function checkBan($board = false) { } } - // 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; + if ($config['auto_maintenance']) { + // 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 ($last_time_purged !== false && time() - $last_time_purged > $config['purge_bans']) { + Bans::purge($config['require_ban_view']); + cache::set('purged_bans_last', time()); + } + } else { + // Purge every time. + Bans::purge($config['require_ban_view']); + } } - - Bans::purge($config['require_ban_view']); - - if ($config['cache']['enabled']) - cache::set('purged_bans_last', time()); } function threadLocked($id) { From ede7591702e0237ce476ae9ecce1424662ee840c Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 11 Jul 2024 22:48:19 +0200 Subject: [PATCH 16/21] bans.php: refactor to expose the moratorium on ban deletion from the database. Also fixes the 'purge_bans' configuration for non-cache deployments. --- inc/bans.php | 7 ++++--- inc/config.php | 3 +-- inc/functions.php | 4 ++-- tools/maintenance.php | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/inc/bans.php b/inc/bans.php index 0e462a7b..8566eb04 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -341,12 +341,13 @@ class Bans { rebuildThemes('bans'); } - static public function purge($require_seen) { + static public function purge($require_seen, $moratorium) { if ($require_seen) { - $query = prepare("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < :curr_time AND `seen` = 1"); + $query = prepare("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` + :moratorium < :curr_time AND `seen` = 1"); } else { - $query = prepare("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < :curr_time"); + $query = prepare("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` + :moratorium < :curr_time"); } + $query->bindValue(':moratorium', $moratorium); $query->bindValue(':curr_time', time()); $query->execute() or error(db_error($query)); diff --git a/inc/config.php b/inc/config.php index 880de88b..7828be88 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1569,8 +1569,7 @@ // Enable the moving of single replies $config['move_replies'] = false; - // How often (minimum) to purge the ban list of expired bans (which have been seen). Only works when - // $config['cache'] is enabled and working. + // How often (minimum) to purge the ban list of expired bans (which have been seen). $config['purge_bans'] = 60 * 60 * 12; // 12 hours // Do DNS lookups on IP addresses to get their hostname for the moderator IP pages (?/IP/x.x.x.x). diff --git a/inc/functions.php b/inc/functions.php index 386f7b38..2cb97c50 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -908,12 +908,12 @@ function checkBan($board = false) { if ($config['cache']['enabled']) { $last_time_purged = cache::get('purged_bans_last'); if ($last_time_purged !== false && time() - $last_time_purged > $config['purge_bans']) { - Bans::purge($config['require_ban_view']); + Bans::purge($config['require_ban_view'], $config['purge_bans']); cache::set('purged_bans_last', time()); } } else { // Purge every time. - Bans::purge($config['require_ban_view']); + Bans::purge($config['require_ban_view'], $config['purge_bans']); } } } diff --git a/tools/maintenance.php b/tools/maintenance.php index cdc1089d..3db3ea98 100644 --- a/tools/maintenance.php +++ b/tools/maintenance.php @@ -7,7 +7,7 @@ require dirname(__FILE__) . '/inc/cli.php'; echo "Clearing expired bans..."; $start = microtime(true); -$deleted_count = Bans::purge($config['require_ban_view']); +$deleted_count = Bans::purge($config['require_ban_view'], $config['purge_bans']); $delta = microtime(true) - $start; echo "Deleted $deleted_count expired bans in $delta seconds!"; modLog("Deleted expired bans in {$delta}s with tools/maintenance.php"); From c4e3541b1500d27617361d34fb684cbedce707ab Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 11 Jul 2024 22:52:18 +0200 Subject: [PATCH 17/21] config.php: purge_bans configuration into the proper section --- inc/config.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inc/config.php b/inc/config.php index 7828be88..7b8b1895 100644 --- a/inc/config.php +++ b/inc/config.php @@ -752,6 +752,9 @@ //); $config['premade_ban_reasons'] = false; + // How often (minimum) to purge the ban list of expired bans (which have been seen). + $config['purge_bans'] = 60 * 60 * 12; // 12 hours + // Allow users to appeal bans through vichan. $config['ban_appeals'] = false; @@ -1569,9 +1572,6 @@ // Enable the moving of single replies $config['move_replies'] = false; - // How often (minimum) to purge the ban list of expired bans (which have been seen). - $config['purge_bans'] = 60 * 60 * 12; // 12 hours - // Do DNS lookups on IP addresses to get their hostname for the moderator IP pages (?/IP/x.x.x.x). $config['mod']['dns_lookup'] = true; // How many recent posts, per board, to show in ?/IP/x.x.x.x. From 1e0a95ce832bd68f2ba1631ba3d1827864dee812 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 12 Jul 2024 00:02:15 +0200 Subject: [PATCH 18/21] maintenance.php: fix and update logs --- tools/maintenance.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tools/maintenance.php b/tools/maintenance.php index 3db3ea98..33c0a4d4 100644 --- a/tools/maintenance.php +++ b/tools/maintenance.php @@ -5,16 +5,21 @@ require dirname(__FILE__) . '/inc/cli.php'; -echo "Clearing expired bans..."; +echo "Clearing expired bans...\n"; $start = microtime(true); $deleted_count = Bans::purge($config['require_ban_view'], $config['purge_bans']); $delta = microtime(true) - $start; -echo "Deleted $deleted_count expired bans in $delta seconds!"; -modLog("Deleted expired bans in {$delta}s with tools/maintenance.php"); +echo "Deleted $deleted_count expired bans in $delta seconds!\n"; +$time_tot = $delta; +$deleted_tot = $deleted_count; -echo "Clearing old antispam..."; +echo "Clearing old antispam...\n"; $start = microtime(true); $deleted_count = purge_old_antispam(); $delta = microtime(true) - $start; -echo "Deleted $deleted_count expired antispam in $delta seconds!"; -modLog("Deleted expired antispam in {$delta}s with tools/maintenance.php"); +echo "Deleted $deleted_count expired antispam in $delta seconds!\n"; +$time_tot = $delta; +$deleted_tot = $deleted_count; + +$time_tot = number_format((float)$time_tot, 4, '.', ''); +modLog("Deleted $deleted_tot expired entries in {$time_tot}s with maintenance tool"); From ffe855222ecc573c622891104caa2c5fea451fe7 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 14 Jul 2024 19:13:51 +0200 Subject: [PATCH 19/21] bans.php: do not deserialize post that does not exist --- inc/bans.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inc/bans.php b/inc/bans.php index 8566eb04..8df9ae97 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -85,7 +85,9 @@ class Bans { if ($query->rowCount() == 0) { return null; } else { - $ret['post'] = json_decode($ret['post'], true); + if ($ret['post']) { + $ret['post'] = json_decode($ret['post'], true); + } return $ret; } } From 7e4acbb6d2cf69e797a87dd7948adc96c3820867 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 14 Jul 2024 19:23:14 +0200 Subject: [PATCH 20/21] bans.php: FIX every IP matching to any ban that was going to expire eventually --- inc/bans.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/bans.php b/inc/bans.php index 8df9ae97..8f396f0b 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -134,7 +134,7 @@ class Bans { WHERE (' . ($board !== false ? '(`board` IS NULL OR `board` = :board) AND' : '') . ' (`ipstart` = :ip OR (:ip >= `ipstart` AND :ip <= `ipend`)) OR (``bans``.id = :id)) - AND `expires` IS NULL OR `expires` >= :curr_time + AND (`expires` IS NULL OR `expires` >= :curr_time) ORDER BY `expires` IS NULL, `expires` DESC'); if ($board !== false) { From 51e0616eb8beb1542e2aeb77d85f028cfb9df17c Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 14 Jul 2024 20:46:50 +0200 Subject: [PATCH 21/21] bans.php: fix forgot to extract the mask --- inc/bans.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inc/bans.php b/inc/bans.php index 8f396f0b..a0ea5c76 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -88,6 +88,8 @@ class Bans { if ($ret['post']) { $ret['post'] = json_decode($ret['post'], true); } + $ret['mask'] = self::range_to_string([$ret['ipstart'], $ret['ipend']]); + return $ret; } } @@ -151,6 +153,7 @@ class Bans { if ($ban['post']) { $ban['post'] = json_decode($ban['post'], true); } + $ban['mask'] = self::range_to_string([$ban['ipstart'], $ban['ipend']]); }); return $ban_list; }