diff --git a/inc/config.php b/inc/config.php index 38468f57..e6e81da3 100644 --- a/inc/config.php +++ b/inc/config.php @@ -371,6 +371,31 @@ 'raw' ); + // Custom flood filters. Detect flood attacks and reject new posts if there's a positive match. + //$config['flood_filters'] = Array( + // Array( + // 'condition' => Array( + // // 100 posts in the past 5 minutes (~20 p/m) + // 'posts_in_past_x_minutes' => Array(100, 5) + // ), + // // Don't allow the user to post + // 'action' => 'reject', + // // Display this message + // 'message' => 'Your post has been rejected on the suspicion of a flood attack on this board.' + // ), + // // Another filter + // Array( + // 'condition' => Array( + // // 10 new empty threads in the past 2 minutes + // 'threads_with_no_replies_in_past_x_minutes' => Array(10, 2), + // // Allow replies, but not new threads (ie. reject topics only). + // 'OP' => true + // ), + // 'action' => 'reject', + // 'message' => 'Your post has been rejected on the suspicion of a flood attack on this board (too many new threads); post a reply instead.' + // ) + //); + // A small file in the main directory indicating that the script has been ran and the board(s) have been generated. // This keeps the script from querying the database and causing strain when not needed. $config['has_installed'] = '.installed'; diff --git a/post.php b/post.php index 3306a58e..af592ae8 100644 --- a/post.php +++ b/post.php @@ -161,6 +161,8 @@ if(!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['url_match'], $_SERVER['HTTP_REFERER'])) error($config['error']['bot']); } + file_put_contents('test_a47.txt', print_r($_SERVER, true) . "\n" . print_r($_POST, true). "\n\n\n", FILE_APPEND); + // TODO: Since we're now using static HTML files, we can't give them cookies on their first page view // Find another anti-spam method. @@ -300,6 +302,58 @@ error($config['error']['flood']); } + // Custom anti-spam filters + if(isset($config['flood_filters'])) { + foreach($config['flood_filters'] as &$filter) { + // Set up default stuff + if(!isset($filter['action'])) + $filter['action'] = 'reject'; + if(!isset($filter['message'])) + $filter['message'] = 'Posting throttled by flood filter.'; + + foreach($filter['condition'] as $condition=>$value) { + if($condition == 'posts_in_past_x_minutes' && isset($value[0]) && isset($value[1])) { + // Check if there's been X posts in the past X minutes (on this board) + + $query = prepare(sprintf("SELECT COUNT(*) AS `posts` FROM `posts_%s` WHERE `time` >= :time", $board['uri'])); + $query->bindValue(':time', time() - ($value[1] * 60), PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + if(($count = $query->fetch()) && $count['posts'] >= $value[0]) { + // Matched filter + continue; + } + } elseif($condition == 'threads_with_no_replies_in_past_x_minutes' && isset($value[0]) && isset($value[1])) { + // Check if there's been X new empty threads posted in the past X minutes (on this board) + + // Confusing query. I couldn't think of anything simpler... + $query = prepare(sprintf("SELECT ((SELECT COUNT(*) FROM `posts_%s` WHERE `thread` IS NULL AND `time` >= :time) - COUNT(DISTINCT(`threads`.`id`))) AS `posts` FROM `posts_%s` AS `threads` INNER JOIN `posts_%s` AS `replies` ON `replies`.`thread` = `threads`.`id` WHERE `threads`.`thread` IS NULL AND `threads`.`time` >= :time", $board['uri'], $board['uri'], $board['uri'])); + $query->bindValue(':time', time() - ($value[1] * 60), PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + if(($count = $query->fetch()) && $count['posts'] >= $value[0]) { + // Matched filter + continue; + } + } elseif($condition == 'OP') { + // Am I OP? + if($OP) + continue; + } else { + // Unknown block + continue; + } + + $did_not_match = true; + break; + } + if(!isset($did_not_match)) { + // Matched filter! + if($filter['action'] == 'reject') { + error($filter['message']); + } + } + } + } + if($post['has_file']) { // Just trim the filename if it's too long if(strlen($post['filename']) > 30) $post['filename'] = substr($post['filename'], 0, 27).'…';