PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/bans.php

https://gitlab.com/potion/librechan
PHP | 366 lines | 280 code | 76 blank | 10 comment | 89 complexity | 8df28e4e61d0553a1f8281ca5320ae00 MD5 | raw file
  1. <?php
  2. require 'inc/lib/IP/Lifo/IP/IP.php';
  3. require 'inc/lib/IP/Lifo/IP/BC.php';
  4. require 'inc/lib/IP/Lifo/IP/CIDR.php';
  5. use Lifo\IP\CIDR;
  6. class Bans {
  7. static public function range_to_string($mask) {
  8. list($ipstart, $ipend) = $mask;
  9. if (!isset($ipend) || $ipend === false) {
  10. // Not a range. Single IP address.
  11. $ipstr = inet_ntop($ipstart);
  12. return $ipstr;
  13. }
  14. if (strlen($ipstart) != strlen($ipend))
  15. return '???'; // What the fuck are you doing, son?
  16. $range = CIDR::range_to_cidr(inet_ntop($ipstart), inet_ntop($ipend));
  17. if ($range !== false)
  18. return $range;
  19. return '???';
  20. }
  21. private static function calc_cidr($mask) {
  22. $cidr = new CIDR($mask);
  23. $range = $cidr->getRange();
  24. return array(inet_pton($range[0]), inet_pton($range[1]));
  25. }
  26. public static function parse_time($str) {
  27. if (empty($str))
  28. return false;
  29. if (($time = @strtotime($str)) !== false)
  30. return $time;
  31. 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))
  32. return false;
  33. $expire = 0;
  34. if (isset($matches[2])) {
  35. // Years
  36. $expire += $matches[2]*60*60*24*365;
  37. }
  38. if (isset($matches[4])) {
  39. // Months
  40. $expire += $matches[4]*60*60*24*30;
  41. }
  42. if (isset($matches[6])) {
  43. // Weeks
  44. $expire += $matches[6]*60*60*24*7;
  45. }
  46. if (isset($matches[8])) {
  47. // Days
  48. $expire += $matches[8]*60*60*24;
  49. }
  50. if (isset($matches[10])) {
  51. // Hours
  52. $expire += $matches[10]*60*60;
  53. }
  54. if (isset($matches[12])) {
  55. // Minutes
  56. $expire += $matches[12]*60;
  57. }
  58. if (isset($matches[14])) {
  59. // Seconds
  60. $expire += $matches[14];
  61. }
  62. return time() + $expire;
  63. }
  64. static public function parse_range($mask) {
  65. $ipstart = false;
  66. $ipend = false;
  67. if (preg_match('@^(\d{1,3}\.){1,3}([\d*]{1,3})?$@', $mask) && substr_count($mask, '*') == 1) {
  68. // IPv4 wildcard mask
  69. $parts = explode('.', $mask);
  70. $ipv4 = '';
  71. foreach ($parts as $part) {
  72. if ($part == '*') {
  73. $ipstart = inet_pton($ipv4 . '0' . str_repeat('.0', 3 - substr_count($ipv4, '.')));
  74. $ipend = inet_pton($ipv4 . '255' . str_repeat('.255', 3 - substr_count($ipv4, '.')));
  75. break;
  76. } elseif(($wc = strpos($part, '*')) !== false) {
  77. $ipstart = inet_pton($ipv4 . substr($part, 0, $wc) . '0' . str_repeat('.0', 3 - substr_count($ipv4, '.')));
  78. $ipend = inet_pton($ipv4 . substr($part, 0, $wc) . '9' . str_repeat('.255', 3 - substr_count($ipv4, '.')));
  79. break;
  80. }
  81. $ipv4 .= "$part.";
  82. }
  83. } elseif (preg_match('@^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d+$@', $mask)) {
  84. list($ipv4, $bits) = explode('/', $mask);
  85. if ($bits > 32)
  86. return false;
  87. list($ipstart, $ipend) = self::calc_cidr($mask);
  88. } elseif (preg_match('@^[:a-z\d]+/\d+$@i', $mask)) {
  89. list($ipv6, $bits) = explode('/', $mask);
  90. if ($bits > 128)
  91. return false;
  92. list($ipstart, $ipend) = self::calc_cidr($mask);
  93. } else {
  94. if (($ipstart = @inet_pton($mask)) === false)
  95. return false;
  96. }
  97. return array($ipstart, $ipend);
  98. }
  99. static public function find($criteria, $board = false, $get_mod_info = false, $id = false) {
  100. global $config;
  101. $query = prepare('SELECT ``bans``.*' . ($get_mod_info ? ', `username`' : '') . ' FROM ``bans``
  102. ' . ($get_mod_info ? 'LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`' : '') . '
  103. WHERE ' . ($id ? 'id = :id' : '
  104. (' . ($board !== false ? '(`board` IS NULL OR `board` = :board) AND' : '') . '
  105. (`ipstart` = :ip OR (:ip >= `ipstart` AND :ip <= `ipend`)))') . '
  106. ORDER BY `expires` IS NULL, `expires` DESC');
  107. if ($board !== false)
  108. $query->bindValue(':board', $board, PDO::PARAM_STR);
  109. if (!$id) {
  110. $query->bindValue(':ip', inet_pton($criteria));
  111. } else {
  112. $query->bindValue(':id', $criteria);
  113. }
  114. $query->execute() or error(db_error($query));
  115. $ban_list = array();
  116. while ($ban = $query->fetch(PDO::FETCH_ASSOC)) {
  117. if ($ban['expires'] && ($ban['seen'] || !$config['require_ban_view']) && $ban['expires'] < time()) {
  118. self::delete($ban['id']);
  119. } else {
  120. if ($ban['post'])
  121. $ban['post'] = json_decode($ban['post'], true);
  122. $ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
  123. $ban_list[] = $ban;
  124. }
  125. }
  126. return $ban_list;
  127. }
  128. static public function stream_json($out = false, $filter_ips = false, $filter_staff = false, $board_access = false) {
  129. global $config, $pdo;
  130. if ($board_access && $board_access[0] == '*') $board_access = false;
  131. $query_addition = "";
  132. if ($board_access) {
  133. $boards = implode(", ", array_map(array($pdo, "quote"), $board_access));
  134. $query_addition .= "WHERE `board` IN (".$boards.")";
  135. }
  136. if ($board_access !== FALSE) {
  137. if (!$query_addition) {
  138. $query_addition .= " WHERE (`public_bans` IS TRUE) OR ``bans``.`board` IS NULL";
  139. }
  140. }
  141. $query = query("SELECT ``bans``.*, `username`, `type` FROM ``bans``
  142. LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`
  143. LEFT JOIN ``boards`` ON ``boards``.`uri` = ``bans``.`board`
  144. $query_addition
  145. ORDER BY `created` DESC") or error(db_error());
  146. $bans = $query->fetchAll(PDO::FETCH_ASSOC);
  147. $out ? fputs($out, "[") : print("[");
  148. $end = end($bans);
  149. foreach ($bans as &$ban) {
  150. $ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
  151. if ($ban['post']) {
  152. $post = json_decode($ban['post']);
  153. if ($post && isset($post->body)) {
  154. $ban['message'] = $post->body;
  155. }
  156. }
  157. unset($ban['ipstart'], $ban['ipend'], $ban['post'], $ban['creator']);
  158. if ($board_access === false || in_array ($ban['board'], $board_access)) {
  159. $ban['access'] = true;
  160. }
  161. if (filter_var($ban['mask'], FILTER_VALIDATE_IP) !== false) {
  162. $ban['single_addr'] = true;
  163. }
  164. if ($filter_staff || ($board_access !== false && !in_array($ban['board'], $board_access))) {
  165. switch ($ban['type']) {
  166. case ADMIN:
  167. $ban['username'] = 'Admin';
  168. break;
  169. case GLOBALVOLUNTEER:
  170. $ban['username'] = 'Global Volunteer';
  171. break;
  172. case MOD:
  173. $ban['username'] = 'Board Owner';
  174. break;
  175. case BOARDVOLUNTEER:
  176. $ban['username'] = 'Board Volunteer';
  177. break;
  178. default:
  179. $ban['username'] = '?';
  180. }
  181. $ban['vstaff'] = true;
  182. }
  183. unset($ban['type']);
  184. if ($filter_ips || ($board_access !== false && !in_array($ban['board'], $board_access))) {
  185. $ban['mask'] = @less_ip($ban['mask'], $ban['board']);
  186. $ban['masked'] = true;
  187. }
  188. $json = json_encode($ban);
  189. $out ? fputs($out, $json) : print($json);
  190. if ($ban['id'] != $end['id']) {
  191. $out ? fputs($out, ",") : print(",");
  192. }
  193. }
  194. $out ? fputs($out, "]") : print("]");
  195. }
  196. static public function seen($ban_id) {
  197. global $config;
  198. $query = query("UPDATE ``bans`` SET `seen` = 1 WHERE `id` = " . (int)$ban_id) or error(db_error());
  199. if (!$config['cron_bans']) rebuildThemes('bans');
  200. }
  201. static public function purge() {
  202. global $config;
  203. $query = query("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < " . time() . " AND `seen` = 1") or error(db_error());
  204. if (!$config['cron_bans']) rebuildThemes('bans');
  205. }
  206. static public function delete($ban_id, $modlog = false, $boards = false, $dont_rebuild = false) {
  207. global $config;
  208. if ($boards && $boards[0] == '*') $boards = false;
  209. if ($modlog) {
  210. $query = query("SELECT `ipstart`, `ipend`, `board` FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error());
  211. if (!$ban = $query->fetch(PDO::FETCH_ASSOC)) {
  212. // Ban doesn't exist
  213. return false;
  214. }
  215. if ($boards !== false && !in_array($ban['board'], $boards))
  216. error($config['error']['noaccess']);
  217. if ($ban['board'])
  218. openBoard($ban['board']);
  219. $mask = self::range_to_string(array($ban['ipstart'], $ban['ipend']));
  220. modLog("Removed ban #{$ban_id} for " .
  221. (filter_var($mask, FILTER_VALIDATE_IP) !== false ? "<a href=\"?/IP/$mask\">$mask</a>" : $mask));
  222. }
  223. query("DELETE FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error());
  224. if (!$dont_rebuild || !$config['cron_bans']) rebuildThemes('bans');
  225. return true;
  226. }
  227. static public function new_ban($mask, $reason, $length = false, $ban_board = false, $mod_id = false, $post = false) {
  228. global $config, $mod, $pdo, $board;
  229. if ($mod_id === false) {
  230. $mod_id = isset($mod['id']) ? $mod['id'] : -1;
  231. }
  232. if ($mod_id > 0 && !in_array($ban_board, $mod['boards']) && $mod['boards'][0] != '*')
  233. error($config['error']['noaccess']);
  234. $range = self::parse_range($mask);
  235. $mask = self::range_to_string($range);
  236. $query = prepare("INSERT INTO ``bans`` VALUES (NULL, :ipstart, :ipend, :time, :expires, :board, :mod, :reason, 0, :post)");
  237. $query->bindValue(':ipstart', $range[0]);
  238. if ($range[1] !== false && $range[1] != $range[0])
  239. $query->bindValue(':ipend', $range[1]);
  240. else
  241. $query->bindValue(':ipend', null, PDO::PARAM_NULL);
  242. $query->bindValue(':mod', $mod_id);
  243. $query->bindValue(':time', time());
  244. if ($reason !== '') {
  245. $reason = escape_markup_modifiers($reason);
  246. markup($reason);
  247. $query->bindValue(':reason', $reason);
  248. } else
  249. $query->bindValue(':reason', null, PDO::PARAM_NULL);
  250. if ($length) {
  251. if (is_int($length) || ctype_digit($length)) {
  252. $length = time() + $length;
  253. } else {
  254. $length = self::parse_time($length);
  255. }
  256. $query->bindValue(':expires', $length);
  257. } else {
  258. $query->bindValue(':expires', null, PDO::PARAM_NULL);
  259. }
  260. if ($ban_board)
  261. $query->bindValue(':board', $ban_board);
  262. else
  263. $query->bindValue(':board', null, PDO::PARAM_NULL);
  264. if ($post) {
  265. $post['board'] = $board['uri'];
  266. $match_urls = '(?xi)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))';
  267. $matched = array();
  268. preg_match_all("#$match_urls#im", $post['body_nomarkup'], $matched);
  269. if (isset($matched[0]) && $matched[0]) {
  270. $post['body'] = str_replace($matched[0], '###Link-Removed###', $post['body']);
  271. $post['body_nomarkup'] = str_replace($matched[0], '###Link-Removed###', $post['body_nomarkup']);
  272. }
  273. $query->bindValue(':post', json_encode($post));
  274. } else
  275. $query->bindValue(':post', null, PDO::PARAM_NULL);
  276. $query->execute() or error(db_error($query));
  277. if (isset($mod['id']) && $mod['id'] == $mod_id) {
  278. modLog('Created a new ' .
  279. ($length > 0 ? preg_replace('/^(\d+) (\w+?)s?$/', '$1-$2', until($length)) : 'permanent') .
  280. ' ban on ' .
  281. ($ban_board ? '/' . $ban_board . '/' : 'all boards') .
  282. ' for ' .
  283. (filter_var($mask, FILTER_VALIDATE_IP) !== false ? "<a href=\"?/IP/$mask\">$mask</a>" : $mask) .
  284. ' (<small>#' . $pdo->lastInsertId() . '</small>)' .
  285. ' with ' . ($reason ? 'reason: ' . utf8tohtml($reason) . '' : 'no reason'));
  286. }
  287. if (!$config['cron_bans']) rebuildThemes('bans');
  288. return $pdo->lastInsertId();
  289. }
  290. }