The version of vichan running on lainchan.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 line
6.8KB

  1. <?php
  2. /*
  3. * Copyright (c) 2010-2013 Tinyboard Development Group
  4. */
  5. defined('TINYBOARD') or exit;
  6. class Filter {
  7. public $flood_check;
  8. private $condition;
  9. private $post;
  10. public function __construct(array $arr) {
  11. foreach ($arr as $key => $value)
  12. $this->$key = $value;
  13. }
  14. public function match($condition, $match) {
  15. $condition = strtolower($condition);
  16. $post = &$this->post;
  17. switch($condition) {
  18. case 'custom':
  19. if (!is_callable($match))
  20. error('Custom condition for filter is not callable!');
  21. return $match($post);
  22. case 'flood-match':
  23. if (!is_array($match))
  24. error('Filter condition "flood-match" must be an array.');
  25. // Filter out "flood" table entries which do not match this filter.
  26. $flood_check_matched = array();
  27. foreach ($this->flood_check as $flood_post) {
  28. foreach ($match as $flood_match_arg) {
  29. switch ($flood_match_arg) {
  30. case 'ip':
  31. if ($flood_post['ip'] != $_SERVER['REMOTE_ADDR'])
  32. continue 3;
  33. break;
  34. case 'body':
  35. if ($flood_post['posthash'] != make_comment_hex($post['body_nomarkup']))
  36. continue 3;
  37. break;
  38. case 'file':
  39. if (!isset($post['filehash']))
  40. return false;
  41. if ($flood_post['filehash'] != $post['filehash'])
  42. continue 3;
  43. break;
  44. case 'board':
  45. if ($flood_post['board'] != $post['board'])
  46. continue 3;
  47. break;
  48. case 'isreply':
  49. if ($flood_post['isreply'] == $post['op'])
  50. continue 3;
  51. break;
  52. default:
  53. error('Invalid filter flood condition: ' . $flood_match_arg);
  54. }
  55. }
  56. $flood_check_matched[] = $flood_post;
  57. }
  58. $this->flood_check = $flood_check_matched;
  59. return !empty($this->flood_check);
  60. case 'flood-time':
  61. foreach ($this->flood_check as $flood_post) {
  62. if (time() - $flood_post['time'] <= $match) {
  63. return true;
  64. }
  65. }
  66. return false;
  67. case 'flood-count':
  68. $count = 0;
  69. foreach ($this->flood_check as $flood_post) {
  70. if (time() - $flood_post['time'] <= $this->condition['flood-time']) {
  71. ++$count;
  72. }
  73. }
  74. return $count >= $match;
  75. case 'name':
  76. return preg_match($match, $post['name']);
  77. case 'trip':
  78. return $match === $post['trip'];
  79. case 'email':
  80. return preg_match($match, $post['email']);
  81. case 'subject':
  82. return preg_match($match, $post['subject']);
  83. case 'body':
  84. return preg_match($match, $post['body_nomarkup']);
  85. case 'filehash':
  86. return $match === $post['filehash'];
  87. case 'filename':
  88. if (!$post['files'])
  89. return false;
  90. foreach ($post['files'] as $file) {
  91. if (preg_match($match, $file['filename'])) {
  92. return true;
  93. }
  94. }
  95. return false;
  96. case 'extension':
  97. if (!$post['files'])
  98. return false;
  99. foreach ($post['files'] as $file) {
  100. if (preg_match($match, $file['extension'])) {
  101. return true;
  102. }
  103. }
  104. return false;
  105. case 'ip':
  106. return preg_match($match, $_SERVER['REMOTE_ADDR']);
  107. case 'op':
  108. return $post['op'] == $match;
  109. case 'has_file':
  110. return $post['has_file'] == $match;
  111. case 'board':
  112. return $post['board'] == $match;
  113. case 'password':
  114. return $post['password'] == $match;
  115. default:
  116. error('Unknown filter condition: ' . $condition);
  117. }
  118. }
  119. public function action() {
  120. global $board;
  121. $this->add_note = isset($this->add_note) ? $this->add_note : false;
  122. if ($this->add_note) {
  123. $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
  124. $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
  125. $query->bindValue(':mod', -1);
  126. $query->bindValue(':time', time());
  127. $query->bindValue(':body', "Autoban message: ".$this->post['body']);
  128. $query->execute() or error(db_error($query));
  129. }
  130. if (isset ($this->action)) switch($this->action) {
  131. case 'reject':
  132. error(isset($this->message) ? $this->message : 'Posting blocked by filter.');
  133. case 'ban':
  134. if (!isset($this->reason))
  135. error('The ban action requires a reason.');
  136. $this->expires = isset($this->expires) ? $this->expires : false;
  137. $this->reject = isset($this->reject) ? $this->reject : true;
  138. $this->all_boards = isset($this->all_boards) ? $this->all_boards : false;
  139. Bans::new_ban($_SERVER['REMOTE_ADDR'], $this->reason, $this->expires, $this->all_boards ? false : $board['uri'], -1);
  140. if ($this->reject) {
  141. if (isset($this->message))
  142. error($message);
  143. checkBan($board['uri']);
  144. exit;
  145. }
  146. break;
  147. default:
  148. error('Unknown filter action: ' . $this->action);
  149. }
  150. }
  151. public function check(array $post) {
  152. $this->post = $post;
  153. foreach ($this->condition as $condition => $value) {
  154. if ($condition[0] == '!') {
  155. $NOT = true;
  156. $condition = substr($condition, 1);
  157. } else $NOT = false;
  158. if ($this->match($condition, $value) == $NOT)
  159. return false;
  160. }
  161. return true;
  162. }
  163. }
  164. function purge_flood_table() {
  165. global $config;
  166. // Determine how long we need to keep a cache of posts for flood prevention. Unfortunately, it is not
  167. // aware of flood filters in other board configurations. You can solve this problem by settings the
  168. // config variable $config['flood_cache'] (seconds).
  169. if (isset($config['flood_cache'])) {
  170. $max_time = &$config['flood_cache'];
  171. } else {
  172. $max_time = 0;
  173. foreach ($config['filters'] as $filter) {
  174. if (isset($filter['condition']['flood-time']))
  175. $max_time = max($max_time, $filter['condition']['flood-time']);
  176. }
  177. }
  178. $time = time() - $max_time;
  179. query("DELETE FROM ``flood`` WHERE `time` < $time") or error(db_error());
  180. }
  181. function do_filters(array $post) {
  182. global $config;
  183. if (!isset($config['filters']) || empty($config['filters']))
  184. return;
  185. foreach ($config['filters'] as $filter) {
  186. if (isset($filter['condition']['flood-match'])) {
  187. $has_flood = true;
  188. break;
  189. }
  190. }
  191. if (isset($has_flood)) {
  192. if ($post['has_file']) {
  193. $query = prepare("SELECT * FROM ``flood`` WHERE `ip` = :ip OR `posthash` = :posthash OR `filehash` = :filehash");
  194. $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
  195. $query->bindValue(':posthash', make_comment_hex($post['body_nomarkup']));
  196. $query->bindValue(':filehash', $post['filehash']);
  197. } else {
  198. $query = prepare("SELECT * FROM ``flood`` WHERE `ip` = :ip OR `posthash` = :posthash");
  199. $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
  200. $query->bindValue(':posthash', make_comment_hex($post['body_nomarkup']));
  201. }
  202. $query->execute() or error(db_error($query));
  203. $flood_check = $query->fetchAll(PDO::FETCH_ASSOC);
  204. } else {
  205. $flood_check = false;
  206. }
  207. foreach ($config['filters'] as $filter_array) {
  208. $filter = new Filter($filter_array);
  209. $filter->flood_check = $flood_check;
  210. if ($filter->check($post))
  211. $filter->action();
  212. }
  213. purge_flood_table();
  214. }