Browse Source
Conflicts: inc/config.php inc/display.php inc/mod/pages.php install.php js/quick-reply.js post.php templates/index.htmlpull/40/head
46 changed files with 2978 additions and 590 deletions
@ -4,24 +4,26 @@ |
|||
* 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; |
|||
|
|||
$hidden_inputs_twig = array(); |
|||
|
|||
class AntiBot { |
|||
public $salt, $inputs = array(), $index = 0; |
|||
|
|||
public static function randomString($length, $uppercase = false, $special_chars = false) { |
|||
public static function randomString($length, $uppercase = false, $special_chars = false, $unicode_chars = false) { |
|||
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; |
|||
if ($uppercase) |
|||
$chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
|||
if ($special_chars) |
|||
$chars .= ' [email protected]#$%^&*()_+,./;\'[]\\{}|:<>?=-` '; |
|||
if ($unicode_chars) { |
|||
$len = strlen($chars) / 10; |
|||
for ($n = 0; $n < $len; $n++) |
|||
$chars .= mb_convert_encoding('&#' . mt_rand(0x2600, 0x26FF) . ';', 'UTF-8', 'HTML-ENTITIES'); |
|||
} |
|||
|
|||
$chars = str_split($chars); |
|||
$chars = preg_split('//u', $chars, -1, PREG_SPLIT_NO_EMPTY); |
|||
|
|||
$ch = array(); |
|||
|
|||
@ -44,10 +46,10 @@ class AntiBot { |
|||
} |
|||
|
|||
public static function make_confusing($string) { |
|||
$chars = str_split($string); |
|||
$chars = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); |
|||
|
|||
foreach ($chars as &$c) { |
|||
if (rand(0, 2) != 0) |
|||
if (mt_rand(0, 3) != 0) |
|||
$c = utf8tohtml($c); |
|||
else |
|||
$c = mb_encode_numericentity($c, array(0, 0xffff, 0, 0xffff), 'UTF-8'); |
|||
@ -68,13 +70,13 @@ class AntiBot { |
|||
|
|||
shuffle($config['spam']['hidden_input_names']); |
|||
|
|||
$input_count = rand($config['spam']['hidden_inputs_min'], $config['spam']['hidden_inputs_max']); |
|||
$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 || rand(0, 2) == 0) { |
|||
if ($hidden_input_names_x === false || mt_rand(0, 2) == 0) { |
|||
// Use an obscure name |
|||
$name = $this->randomString(rand(10, 40)); |
|||
$name = $this->randomString(mt_rand(10, 40), false, false, $config['spam']['unicode']); |
|||
} else { |
|||
// Use a pre-defined confusing name |
|||
$name = $config['spam']['hidden_input_names'][$hidden_input_names_x++]; |
|||
@ -82,25 +84,33 @@ class AntiBot { |
|||
$hidden_input_names_x = false; |
|||
} |
|||
|
|||
if (rand(0, 2) == 0) { |
|||
if (mt_rand(0, 2) == 0) { |
|||
// Value must be null |
|||
$this->inputs[$name] = ''; |
|||
} elseif (rand(0, 4) == 0) { |
|||
} elseif (mt_rand(0, 4) == 0) { |
|||
// Numeric value |
|||
$this->inputs[$name] = (string)rand(0, 100); |
|||
$this->inputs[$name] = (string)mt_rand(0, 100000); |
|||
} else { |
|||
// Obscure value |
|||
$this->inputs[$name] = $this->randomString(rand(5, 100), true, true); |
|||
$this->inputs[$name] = $this->randomString(mt_rand(5, 100), true, true, $config['spam']['unicode']); |
|||
} |
|||
} |
|||
} |
|||
|
|||
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( |
|||
'<input type="hidden" name="%name%" value="%value%">', |
|||
'<input type="hidden" value="%value%" name="%name%">', |
|||
'<input name="%name%" value="%value%" type="hidden">', |
|||
'<input value="%value%" name="%name%" type="hidden">', |
|||
'<input style="display:none" type="text" name="%name%" value="%value%">', |
|||
'<input style="display:none" type="text" value="%value%" name="%name%">', |
|||
'<span style="display:none"><input type="text" name="%name%" value="%value%"></span>', |
|||
@ -113,7 +123,7 @@ class AntiBot { |
|||
$html = ''; |
|||
|
|||
if ($count === false) { |
|||
$count = rand(1, count($this->inputs) / 15); |
|||
$count = mt_rand(1, abs(count($this->inputs) / 15) + 1); |
|||
} |
|||
|
|||
if ($count === true) { |
|||
@ -128,6 +138,9 @@ class AntiBot { |
|||
$element = false; |
|||
while (!$element) { |
|||
$element = $elements[array_rand($elements)]; |
|||
$element = str_replace(' ', self::space(), $element); |
|||
if (mt_rand(0, 5) == 0) |
|||
$element = str_replace('>', self::space() . '>', $element); |
|||
if (strpos($element, 'textarea') !== false && $value == '') { |
|||
// There have been some issues with mobile web browsers and empty <textarea>'s. |
|||
$element = false; |
|||
@ -136,7 +149,7 @@ class AntiBot { |
|||
|
|||
$element = str_replace('%name%', utf8tohtml($name), $element); |
|||
|
|||
if (rand(0, 2) == 0) |
|||
if (mt_rand(0, 2) == 0) |
|||
$value = $this->make_confusing($value); |
|||
else |
|||
$value = utf8tohtml($value); |
|||
|
@ -0,0 +1,258 @@ |
|||
<?php |
|||
|
|||
require 'inc/lib/IP/Lifo/IP/IP.php'; |
|||
require 'inc/lib/IP/Lifo/IP/BC.php'; |
|||
require 'inc/lib/IP/Lifo/IP/CIDR.php'; |
|||
|
|||
use Lifo\IP\CIDR; |
|||
|
|||
class Bans { |
|||
static public function range_to_string($mask) { |
|||
list($ipstart, $ipend) = $mask; |
|||
|
|||
if (!isset($ipend) || $ipend === false) { |
|||
// Not a range. Single IP address. |
|||
$ipstr = inet_ntop($ipstart); |
|||
return $ipstr; |
|||
} |
|||
|
|||
if (strlen($ipstart) != strlen($ipend)) |
|||
return '???'; // What the fuck are you doing, son? |
|||
|
|||
$range = CIDR::range_to_cidr(inet_ntop($ipstart), inet_ntop($ipend)); |
|||
if ($range !== false) |
|||
return $range; |
|||
|
|||
return '???'; |
|||
} |
|||
|
|||
private static function calc_cidr($mask) { |
|||
$cidr = new CIDR($mask); |
|||
$range = $cidr->getRange(); |
|||
|
|||
return array(inet_pton($range[0]), inet_pton($range[1])); |
|||
} |
|||
|
|||
private static 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; |
|||
} |
|||
|
|||
static public function parse_range($mask) { |
|||
$ipstart = false; |
|||
$ipend = false; |
|||
|
|||
if (preg_match('@^(\d{1,3}\.){1,3}([\d*]{1,3})[email protected]', $mask) && substr_count($mask, '*') == 1) { |
|||
// IPv4 wildcard mask |
|||
$parts = explode('.', $mask); |
|||
$ipv4 = ''; |
|||
foreach ($parts as $part) { |
|||
if ($part == '*') { |
|||
$ipstart = inet_pton($ipv4 . '0' . str_repeat('.0', 3 - substr_count($ipv4, '.'))); |
|||
$ipend = inet_pton($ipv4 . '255' . str_repeat('.255', 3 - substr_count($ipv4, '.'))); |
|||
break; |
|||
} elseif(($wc = strpos($part, '*')) !== false) { |
|||
$ipstart = inet_pton($ipv4 . substr($part, 0, $wc) . '0' . str_repeat('.0', 3 - substr_count($ipv4, '.'))); |
|||
$ipend = inet_pton($ipv4 . substr($part, 0, $wc) . '9' . str_repeat('.255', 3 - substr_count($ipv4, '.'))); |
|||
break; |
|||
} |
|||
$ipv4 .= "$part."; |
|||
} |
|||
} elseif (preg_match('@^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\[email protected]', $mask)) { |
|||
list($ipv4, $bits) = explode('/', $mask); |
|||
if ($bits > 32) |
|||
return false; |
|||
|
|||
list($ipstart, $ipend) = self::calc_cidr($mask); |
|||
} elseif (preg_match('@^[:a-z\d]+/\[email protected]', $mask)) { |
|||
list($ipv6, $bits) = explode('/', $mask); |
|||
if ($bits > 128) |
|||
return false; |
|||
|
|||
list($ipstart, $ipend) = self::calc_cidr($mask); |
|||
} else { |
|||
if (($ipstart = @inet_pton($mask)) === false) |
|||
return false; |
|||
} |
|||
|
|||
return array($ipstart, $ipend); |
|||
} |
|||
|
|||
static public function find($ip, $board = false, $get_mod_info = false) { |
|||
global $config; |
|||
|
|||
$query = prepare('SELECT ``bans``.*' . ($get_mod_info ? ', `username`' : '') . ' FROM ``bans`` |
|||
' . ($get_mod_info ? 'LEFT JOIN ``mods`` ON ``mods``.`id` = `creator`' : '') . ' |
|||
WHERE |
|||
(' . ($board ? '(`board` IS NULL OR `board` = :board) AND' : '') . ' |
|||
(`ipstart` = :ip OR (:ip >= `ipstart` AND :ip <= `ipend`))) |
|||
ORDER BY `expires` IS NULL, `expires` DESC'); |
|||
|
|||
if ($board) |
|||
$query->bindValue(':board', $board); |
|||
|
|||
$query->bindValue(':ip', inet_pton($ip)); |
|||
$query->execute() or error(db_error($query)); |
|||
|
|||
$ban_list = array(); |
|||
|
|||
while ($ban = $query->fetch(PDO::FETCH_ASSOC)) { |
|||
if ($ban['expires'] && ($ban['seen'] || !$config['require_ban_view']) && $ban['expires'] < time()) { |
|||
$query = prepare("DELETE FROM ``bans`` WHERE `id` = :id"); |
|||
$query->bindValue(':id', $ban['id'], PDO::PARAM_INT); |
|||
$query->execute() or error(db_error($query)); |
|||
} else { |
|||
$ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); |
|||
$ban_list[] = $ban; |
|||
} |
|||
} |
|||
|
|||
return $ban_list; |
|||
} |
|||
|
|||
static public function list_all($offset = 0, $limit = 9001) { |
|||
$offset = (int)$offset; |
|||
$limit = (int)$limit; |
|||
|
|||
$query = query("SELECT ``bans``.*, `username` FROM ``bans`` |
|||
LEFT JOIN ``mods`` ON ``mods``.`id` = `creator` |
|||
ORDER BY `created` DESC LIMIT $offset, $limit") or error(db_error()); |
|||
$bans = $query->fetchAll(PDO::FETCH_ASSOC); |
|||
|
|||
foreach ($bans as &$ban) { |
|||
$ban['mask'] = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); |
|||
} |
|||
|
|||
return $bans; |
|||
} |
|||
|
|||
static public function count() { |
|||
$query = query("SELECT COUNT(*) FROM ``bans``") or error(db_error()); |
|||
return (int)$query->fetchColumn(); |
|||
} |
|||
|
|||
static public function seen($ban_id) { |
|||
$query = query("UPDATE ``bans`` SET `seen` = 1 WHERE `id` = " . (int)$ban_id) or error(db_error()); |
|||
} |
|||
|
|||
static public function purge() { |
|||
$query = query("DELETE FROM ``bans`` WHERE `expires` IS NOT NULL AND `expires` < " . time() . " AND `seen` = 1") or error(db_error()); |
|||
} |
|||
|
|||
static public function delete($ban_id, $modlog = false) { |
|||
if ($modlog) { |
|||
$query = query("SELECT `ipstart`, `ipend` FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error()); |
|||
if (!$ban = $query->fetch(PDO::FETCH_ASSOC)) { |
|||
// Ban doesn't exist |
|||
return false; |
|||
} |
|||
|
|||
$mask = self::range_to_string(array($ban['ipstart'], $ban['ipend'])); |
|||
|
|||
modLog("Removed ban #{$ban_id} for " . |
|||
(filter_var($mask, FILTER_VALIDATE_IP) !== false ? "<a href=\"?/IP/$mask\">$mask</a>" : $mask)); |
|||
} |
|||
|
|||
query("DELETE FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error()); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
static public function new_ban($mask, $reason, $length = false, $board = false, $mod_id = false) { |
|||
global $mod, $pdo; |
|||
|
|||
if ($mod_id === false) { |
|||
$mod_id = isset($mod['id']) ? $mod['id'] : -1; |
|||
} |
|||
|
|||
$range = self::parse_range($mask); |
|||
$mask = self::range_to_string($range); |
|||
|
|||
$query = prepare("INSERT INTO ``bans`` VALUES (NULL, :ipstart, :ipend, :time, :expires, :board, :mod, :reason, 0, NULL)"); |
|||
|
|||
$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(':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) { |
|||
if (is_int($length) || ctype_digit($length)) { |
|||
$length = time() + $length; |
|||
} else { |
|||
$length = self::parse_time($length); |
|||
} |
|||
$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)); |
|||
|
|||
if (isset($mod['id']) && $mod['id'] == $mod_id) { |
|||
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 ? "<a href=\"?/IP/$mask\">$mask</a>" : $mask) . |
|||
' (<small>#' . $pdo->lastInsertId() . '</small>)' . |
|||
' with ' . ($reason ? 'reason: ' . utf8tohtml($reason) . '' : 'no reason')); |
|||
} |
|||
return $pdo->lastInsertId(); |
|||
} |
|||
} |
@ -44,6 +44,8 @@ |
|||
$config['debug'] = false; |
|||
// For development purposes. Displays (and "dies" on) all errors and warnings. Turn on with the above. |
|||
$config['verbose_errors'] = true; |
|||
// EXPLAIN all SQL queries (when in debug mode). |
|||
$config['debug_explain'] = false; |
|||
|
|||
// Directory where temporary files will be created. |
|||
$config['tmp'] = sys_get_temp_dir(); |
|||
@ -168,13 +170,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 |
|||
@ -237,6 +232,9 @@ |
|||
|
|||
// How soon after regeneration do hashes expire (in seconds)? |
|||
$config['spam']['hidden_inputs_expire'] = 60 * 60 * 3; // three hours |
|||
|
|||
// Whether to use Unicode characters in hidden input names and values. |
|||
$config['spam']['unicode'] = true; |
|||
|
|||
// These are fields used to confuse the bots. Make sure they aren't actually used by Tinyboard, or it won't work. |
|||
$config['spam']['hidden_input_names'] = array( |
|||
@ -274,6 +272,7 @@ |
|||
'quick-reply', |
|||
'page', |
|||
'file_url', |
|||
'json_response', |
|||
); |
|||
|
|||
// Enable reCaptcha to make spam even harder. Rarely necessary. |
|||
@ -283,6 +282,132 @@ |
|||
$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'] |
|||
), |
|||
'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'], |
|||
'!body' => '/^$/', // Post body is NOT empty |
|||
), |
|||
'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 only post body |
|||
'flood-time' => &$config['flood_time_same'] |
|||
), |
|||
'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'] |
|||
// ); |
|||
|
|||
// 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( |
|||
// 'name' => '/^surgeon$/', |
|||
// 'body' => '/regards,\s+(the )?surgeon$/i', |
|||
// 'OP' => false |
|||
// ), |
|||
// 'action' => 'reject', |
|||
// 'message' => 'Go away, spammer.' |
|||
// ); |
|||
|
|||
// Example: 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.' |
|||
// ); |
|||
|
|||
// 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( |
|||
// '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 |
|||
@ -406,57 +531,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 |
|||
@ -598,10 +672,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. |
|||
@ -752,6 +822,12 @@ |
|||
// Whether or not to put brackets around the whole board list |
|||
$config['boardlist_wrap_bracket'] = false; |
|||
|
|||
// Show page navigation links at the top as well. |
|||
$config['page_nav_top'] = false; |
|||
|
|||
// Show "Catalog" link in page navigation. Use with the Catalog theme. |
|||
// $config['catalog_link'] = 'catalog.html'; |
|||
|
|||
// Board categories. Only used in the "Categories" theme. |
|||
// $config['categories'] = array( |
|||
// 'Group Name' => array('a', 'b', 'c'), |
|||
@ -1010,8 +1086,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; |
|||
@ -1056,14 +1132,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; |
|||
|
|||
// Enable the moving of single replies |
|||
$config['move_replies'] = false; |
|||
|
|||
@ -1130,18 +1198,28 @@ |
|||
* ==================== |
|||
*/ |
|||
|
|||
// Probably best not to change these: |
|||
if (!defined('JANITOR')) { |
|||
define('JANITOR', 0, true); |
|||
define('MOD', 1, true); |
|||
define('ADMIN', 2, true); |
|||
define('DISABLED', 3, true); |
|||
} |
|||
// Probably best not to change this unless you are smart enough to figure out what you're doing. If you |
|||
// decide to change it, remember that it is impossible to redefinite/overwrite groups; you may only add |
|||
// new ones. |
|||
$config['mod']['groups'] = array( |
|||
10 => 'Janitor', |
|||
20 => 'Mod', |
|||
30 => 'Admin', |
|||
// 98 => 'God', |
|||
99 => 'Disabled' |
|||
); |
|||
|
|||
// If you add stuff to the above, you'll need to call this function immediately after. |
|||
define_groups(); |
|||
|
|||
// Example: Adding a new permissions group. |
|||
// $config['mod']['groups'][0] = 'NearlyPowerless'; |
|||
// define_groups(); |
|||
|
|||
// Capcode permissions. |
|||
$config['mod']['capcode'] = array( |
|||
// JANITOR => array('Janitor'), |
|||
MOD => array('Mod'), |
|||
MOD => array('Mod'), |
|||
ADMIN => true |
|||
); |
|||
|
|||
@ -1193,7 +1271,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; |
|||
|
|||
@ -1279,18 +1358,14 @@ |
|||
$config['mod']['edit_config'] = ADMIN; |
|||
|
|||
// Config editor permissions |
|||
$config['mod']['config'] = array( |
|||
JANITOR => false, |
|||
MOD => false, |
|||
ADMIN => false, |
|||
DISABLED => false, |
|||
); |
|||
$config['mod']['config'] = array(); |
|||
|
|||
// Disable the following configuration variables from being changed via ?/config. The following default |
|||
// banned variables are considered somewhat dangerous. |
|||
$config['mod']['config'][DISABLED] = array( |
|||
'mod>config', |
|||
'mod>config_editor_php', |
|||
'mod>groups', |
|||
'convert_args', |
|||
'db>password', |
|||
); |
|||
@ -1421,6 +1496,3 @@ |
|||
// is the absolute maximum, because MySQL cannot handle table names greater than 64 characters. |
|||
$config['board_regex'] = '[0-9a-zA-Z$_\x{0080}-\x{FFFF}]{1,58}'; |
|||
|
|||
// Regex for URLs. |
|||
$config['url_regex'] = '@^(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?ยซยปโโโโ]))[email protected]'; |
|||
|
|||
|
@ -0,0 +1,20 @@ |
|||
Copyright (c) 2013 Jason Morriss |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is furnished |
|||
to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|||
THE SOFTWARE. |
|||
|
@ -0,0 +1,293 @@ |
|||
<?php |
|||
/** |
|||
* This file is part of the Lifo\IP PHP Library. |
|||
* |
|||
* (c) Jason Morriss <lifo2013@gmail.com> |
|||
* |
|||
* For the full copyright and license information, please view the LICENSE |
|||
* file that was distributed with this source code. |
|||
*/ |
|||
namespace Lifo\IP; |
|||
|
|||
/** |
|||
* BCMath helper class. |
|||
* |
|||
* Provides a handful of BCMath routines that are not included in the native |
|||
* PHP library. |
|||
* |
|||
* Note: The Bitwise functions operate on fixed byte boundaries. For example, |
|||
* comparing the following numbers uses X number of bits: |
|||
* 0xFFFF and 0xFF will result in comparison of 16 bits. |
|||
* 0xFFFFFFFF and 0xF will result in comparison of 32 bits. |
|||
* etc... |
|||
* |
|||
*/ |
|||
abstract class BC |
|||
{ |
|||
// Some common (maybe useless) constants |
|||
const MAX_INT_32 = '2147483647'; // 7FFFFFFF |
|||
const MAX_UINT_32 = '4294967295'; // FFFFFFFF |
|||
const MAX_INT_64 = '9223372036854775807'; // 7FFFFFFFFFFFFFFF |
|||
const MAX_UINT_64 = '18446744073709551615'; // FFFFFFFFFFFFFFFF |
|||
const MAX_INT_96 = '39614081257132168796771975167'; // 7FFFFFFFFFFFFFFFFFFFFFFF |
|||
const MAX_UINT_96 = '79228162514264337593543950335'; // FFFFFFFFFFFFFFFFFFFFFFFF |
|||
const MAX_INT_128 = '170141183460469231731687303715884105727'; // 7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
|||
const MAX_UINT_128 = '340282366920938463463374607431768211455'; // FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF |
|||
|
|||
/** |
|||
* BC Math function to convert a HEX string into a DECIMAL |
|||
*/ |
|||
public static function bchexdec($hex) |
|||
{ |
|||
if (strlen($hex) == 1) { |
|||
return hexdec($hex); |
|||
} |
|||
|
|||
$remain = substr($hex, 0, -1); |
|||
$last = substr($hex, -1); |
|||
return bcadd(bcmul(16, self::bchexdec($remain), 0), hexdec($last), 0); |
|||
} |
|||
|
|||
/** |
|||
* BC Math function to convert a DECIMAL string into a BINARY string |
|||
*/ |
|||
public static function bcdecbin($dec, $pad = null) |
|||
{ |
|||
$bin = ''; |
|||
while ($dec) { |
|||
$m = bcmod($dec, 2); |
|||
$dec = bcdiv($dec, 2, 0); |
|||
$bin = abs($m) . $bin; |
|||
} |
|||
return $pad ? sprintf("%0{$pad}s", $bin) : $bin; |
|||
} |
|||
|
|||
/** |
|||
* BC Math function to convert a BINARY string into a DECIMAL string |
|||
*/ |
|||
public static function bcbindec($bin) |
|||
{ |
|||
$dec = '0'; |
|||
for ($i=0, $j=strlen($bin); $i<$j; $i++) { |
|||
$dec = bcmul($dec, '2', 0); |
|||
$dec = bcadd($dec, $bin[$i], 0); |
|||
} |
|||
return $dec; |
|||
} |
|||
|
|||
/** |
|||
* BC Math function to convert a BINARY string into a HEX string |
|||
*/ |
|||
public static function bcbinhex($bin, $pad = 0) |
|||
{ |
|||
return self::bcdechex(self::bcbindec($bin)); |
|||
} |
|||
|
|||
/** |
|||
* BC Math function to convert a DECIMAL into a HEX string |
|||
*/ |
|||
public static function bcdechex($dec) |
|||
{ |
|||
$last = bcmod($dec, 16); |
|||
$remain = bcdiv(bcsub($dec, $last, 0), 16, 0); |
|||
return $remain == 0 ? dechex($last) : self::bcdechex($remain) . dechex($last); |
|||
} |
|||
|
|||
/** |
|||
* Bitwise AND two arbitrarily large numbers together. |
|||
*/ |
|||
public static function bcand($left, $right) |
|||
{ |
|||
$len = self::_bitwise($left, $right); |
|||
|
|||
$value = ''; |
|||
for ($i=0; $i<$len; $i++) { |
|||
$value .= (($left{$i} + 0) & ($right{$i} + 0)) ? '1' : '0'; |
|||
} |
|||
return self::bcbindec($value != '' ? $value : '0'); |
|||
} |
|||
|
|||
/** |
|||
* Bitwise OR two arbitrarily large numbers together. |
|||
*/ |
|||
public static function bcor($left, $right) |
|||
{ |
|||
$len = self::_bitwise($left, $right); |
|||
|
|||
$value = ''; |
|||
for ($i=0; $i<$len; $i++) { |
|||
$value .= (($left{$i} + 0) | ($right{$i} + 0)) ? '1' : '0'; |
|||
} |
|||
return self::bcbindec($value != '' ? $value : '0'); |
|||
} |
|||
|
|||
/** |
|||
* Bitwise XOR two arbitrarily large numbers together. |
|||
*/ |
|||
public static function bcxor($left, $right) |
|||
{ |
|||
$len = self::_bitwise($left, $right); |
|||
|
|||
$value = ''; |
|||
for ($i=0; $i<$len; $i++) { |
|||
$value .= (($left{$i} + 0) ^ ($right{$i} + 0)) ? '1' : '0'; |
|||
} |
|||
return self::bcbindec($value != '' ? $value : '0'); |
|||
} |
|||
|
|||
/** |
|||
* Bitwise NOT two arbitrarily large numbers together. |
|||
*/ |
|||
public static function bcnot($left, $bits = null) |
|||
{ |
|||
$right = 0; |
|||
$len = self::_bitwise($left, $right, $bits); |
|||
$value = ''; |
|||
for ($i=0; $i<$len; $i++) { |
|||
$value .= $left{$i} == '1' ? '0' : '1'; |
|||
} |
|||
return self::bcbindec($value); |
|||
} |
|||
|
|||
/** |
|||
* Shift number to the left |
|||
* |
|||
* @param integer $bits Total bits to shift |
|||
*/ |
|||
public static function bcleft($num, $bits) { |
|||
return bcmul($num, bcpow('2', $bits)); |
|||
} |
|||
|
|||
/** |
|||
* Shift number to the right |
|||
* |
|||
* @param integer $bits Total bits to shift |
|||
*/ |
|||
public static function bcright($num, $bits) { |
|||
return bcdiv($num, bcpow('2', $bits)); |
|||
} |
|||
|
|||
/** |
|||
* Determine how many bits are needed to store the number rounded to the |
|||
* nearest bit boundary. |
|||
*/ |
|||
public static function bits_needed($num, $boundary = 4) |
|||
{ |
|||
$bits = 0; |
|||
while ($num > 0) { |
|||
$num = bcdiv($num, '2', 0); |
|||
$bits++; |
|||
} |
|||
// round to nearest boundrary |
|||
return $boundary ? ceil($bits / $boundary) * $boundary : $bits; |
|||
} |
|||
|
|||
/** |
|||
* BC Math function to return an arbitrarily large random number. |
|||
*/ |
|||
public static function bcrand($min, $max = null) |
|||
{ |
|||
if ($max === null) { |
|||
$max = $min; |
|||
$min = 0; |
|||
} |
|||
|
|||
// swap values if $min > $max |
|||
if (bccomp($min, $max) == 1) { |
|||
list($min,$max) = array($max,$min); |
|||
} |
|||
|
|||
return bcadd( |
|||
bcmul( |
|||
bcdiv( |
|||
mt_rand(0, mt_getrandmax()), |
|||
mt_getrandmax(), |
|||
strlen($max) |
|||
), |
|||
bcsub( |
|||
bcadd($max, '1'), |
|||
$min |
|||
) |
|||
), |
|||
$min |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Computes the natural logarithm using a series. |
|||
* @author Thomas Oldbury. |
|||
* @license Public domain. |
|||
*/ |
|||
public static function bclog($num, $iter = 10, $scale = 100) |
|||
{ |
|||
$log = "0.0"; |
|||
for($i = 0; $i < $iter; $i++) { |
|||
$pow = 1 + (2 * $i); |
|||
$mul = bcdiv("1.0", $pow, $scale); |
|||