diff --git a/inc/config.php b/inc/config.php index 746fcad6..f4656328 100644 --- a/inc/config.php +++ b/inc/config.php @@ -236,6 +236,30 @@ // To prevent bump atacks; returns the thread to last position after the last post is deleted. $config['anti_bump_flood'] = false; + // Use 3rd part APIs to check if an IP should be blocked. + $config['ip_api'] = [ + 'iphub' => [ + // Enable or disable the IPHub API backend. + 'enabled' => false, + // IPHub API key for checking for bad proxies (https://iphub.info/api). + 'key' => '', + ], + // If true, add a note to the IP explaining why it was banned. + 'add_note' => false, + // If you find that a lot of spammers are coming from a specific ISP, or ASN, + // you can put the ISP in isp_blacklist and/or the ASN in asn_blacklist + // USE THIS WITH CAUTION, IT MAY PREVENT NON-SPAMMERS FROM POSTING + 'isp_blacklist' => [], + 'asn_blacklist' => [], + // IPs in this array won't be checked. + // This can be used if known legitimate posters are getting blocked by a backend. + 'ip_whitelist' => [], + // Block according to the API suggestion. + // Use 0 to ignore this field, 1 to block those which are strongly suggested to be blocked, + // 2 to block all which aren't known good actors. + 'ip_block' => 0 + ]; + /* * Introduction to Tinyboard's spam filter: * @@ -1198,6 +1222,7 @@ // Error messages $config['error']['bot'] = _('You look like a bot.'); + $config['error']['proxy'] = _('You seem to be using an unathorized proxy.'); $config['error']['referer'] = _('Your browser sent an invalid or no HTTP referer.'); $config['error']['toolong'] = _('The %s field was too long.'); $config['error']['toolong_body'] = _('The body was too long.'); diff --git a/inc/functions.php b/inc/functions.php index 644cefbe..7745c2e8 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -1823,6 +1823,139 @@ function buildJavascript() { file_write($config['file_script'], $script); } +/** + * @param string $ip The IP to check. + * @return bool True if the IP should be blocked. + */ +function checkIPAPI(string $ip): bool { + function add_note(string $ip, string $note) { + $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)'); + $query->bindValue(':ip', $ip); + $query->bindValue(':mod', -1, PDO::PARAM_INT); + $query->bindValue(':time', time(), PDO::PARAM_INT); + $query->bindValue(':body', "IP API automatic note: $note"); + $query->execute() or error(db_error($query)); + } + + global $config; + + if (!$config['ip_api']['iphub']['enabled']) { + return false; + } + $iphub_key = $config['ip_api']['iphub']['key']; + if (empty($iphub_key)) { + error_log('IP api backend is enabled but IPHub API is empty!'); + return false; + } + + // Query IPHub's database with the poster's IP. + if (array_search($ip, $config['ip_api']['ip_whitelist']) !== false) { + // IP is whitelisted, don't bother querying. + return false; + } + + $ret = false; + if ($config['cache']['enabled']) { + $ret = cache::get("ip_api_block_$ip"); + } + + if ($ret === false) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "http://v2.api.iphub.info/ip/$ip"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ "X-Key: $iphub_key" ]); + $reply = curl_exec($ch); + if ($reply === false) { + $curl_err = curl_error($ch); + curl_close($ch); + error_log("IPHub query failed: api call failed with curl error $curl_err"); + + if ($config['cache']['enabled']) { + // Store the result for 4 hours. + cache::set("ip_api_block_$ip", 'api_error', 14400); + } + + return false; + } + curl_close($ch); + if (empty($reply)) { + error_log('IPHub query failed: api returned an empty response'); + + if ($config['cache']['enabled']) { + // Store the result for 4 hours. + cache::set("ip_api_block_$ip", 'api_error', 14400); + } + + return false; + } + + $json = json_decode($reply); + if ($json === null) { + error_log('IPHub query failed: api returned incorrect json'); + + if ($config['cache']['enabled']) { + // Store the result for 4 hours. + cache::set("ip_api_block_$ip", 'api_error', 14400); + } + + return false; + } + + if (isset($json->error)) { + error_log("IPHub query failed: $json->error"); + + if ($config['cache']['enabled']) { + // Store the result for 4 hours. + cache::set("ip_api_block_$ip", 'api_error', 14400); + } + + return false; + } + + $ret = [ + 'block' => $json['block'], + 'isp' => $json['isp'], + 'asn' => $json['asn'] + ]; + + if ($config['cache']['enabled']) { + // Store the result for 2 days. + cache::set("ip_api_block_$ip", $ret, 172800); + } + } elseif (!is_array($ret)) { + // Cache previously reported an error, just return. + return false; + } + + $add_note_mode = (int)$config['ip_api']['add_note']; + + if (($config['ip_api']['ip_block'] == 1 && $ret['block'] == 1) || ($config['ip_api']['ip_block'] == 2 && $ret['block'] != 0)) { + if ($add_note_mode !== 0) { + add_note($ip, _('IP was rejected for being blacklisted by the API')); + } + error($config['error']['proxy']); + return true; + } + + if (array_search($ret['isp'], $config['ip_api']['isp_blacklist']) !== false) { + if ($add_note_mode !== 0) { + add_note($ip, _("IP was rejected because the \"{$ret['isp']}\" ISP is blacklisted.")); + } + error($config['error']['proxy']); + return true; + } + + if (array_search($ret['asn'], $config['ip_api']['asn_blacklist']) !== false) { + if ($add_note_mode !== 0) { + add_note($ip, _("IP was rejected because the \"{$ret['asn']}\" ASN is blacklisted.")); + } + error($config['error']['proxy']); + return true; + } + + return false; +} + function checkDNSBL() { global $config; diff --git a/post.php b/post.php index ae1361ce..a42c1959 100644 --- a/post.php +++ b/post.php @@ -469,6 +469,10 @@ function handle_delete() checkDNSBL(); + if (checkIPAPI($_SERVER['REMOTE_ADDR'])) { + error($config['error']['proxy']); + } + // Check if board exists if (!openBoard($_POST['board'])) { error($config['error']['noboard']); @@ -577,6 +581,10 @@ function handle_report() checkDNSBL(); + if (checkIPAPI($_SERVER['REMOTE_ADDR'])) { + error($config['error']['proxy']); + } + // Check if board exists. if (!openBoard($_POST['board'])) { error($config['error']['noboard']); @@ -855,6 +863,10 @@ function handle_post() checkDNSBL(); + if (checkIPAPI($_SERVER['REMOTE_ADDR'])) { + error($config['error']['proxy']); + } + // Check if banned checkBan($board['uri']);