Add ability to block blacklisted ips
jdarwood007

jdarwood007 commited on 2019-12-08 12:17:24
Showing 2 changed files, with 260 additions and 8 deletions.

... ...
@@ -242,6 +242,7 @@ class SFS
242 242
 			'',
243 243
 				array('check', 'sfs_emailcheck'),
244 244
 				array('check', 'sfs_ipcheck'),
245
+				array('check', 'sfs_ipcheck_autoban'),
245 246
 				array('check', 'sfs_usernamecheck'),
246 247
 			'',
247 248
 				array('select', 'sfs_region', $this->sfsServerMapping('config')),
... ...
@@ -285,6 +286,10 @@ class SFS
285 286
 			$this->unloadDefaults();
286 287
 			checkSession();
287 288
 
289
+			// If we are automatically banning IPs, make sure we have a ban group.
290
+			if (isset($_POST['sfs_ipcheck_autoban']) || empty($modSettings['sfs_ipcheck_autoban_group']))
291
+				$this->createBanGroup(true);
292
+
288 293
 			saveDBSettings($config_vars);
289 294
 
290 295
 			writeLog();
... ...
@@ -692,8 +697,12 @@ class SFS
692 697
 			// This tells us what it matched on exactly.
693 698
 			if (strpos($row['result'], ',') !== false)
694 699
 			{
695
-				list($resultType, $resultMatch) = explode(',', $row['result']);
696
-				$entries[$row['id_sfs']]['result'] = 'Matched on ' . $resultType . ' [' . $resultMatch . ']';
700
+				list($resultType, $resultMatch, $extra) = explode(',', $row['result'] . ',,,');
701
+				$entries[$row['id_sfs']]['result'] = sprintf($txt['sfs_log_matched_on'], $resultType, $resultMatch);
702
+
703
+				// If this was a IP ban, note it.
704
+				if ($resultType == 'ip' && !empty($extra))
705
+					$entries[$row['id_sfs']]['result'] .= ' ' . $txt['sfs_log_auto_banned'];			
697 706
 			}
698 707
 			else
699 708
 				$entries[$row['id_sfs']]['result'] = $row['result'];
... ...
@@ -1061,11 +1070,16 @@ class SFS
1061 1070
 		{
1062 1071
 			foreach ($response['ip'] as $check)
1063 1072
 			{
1064
-				// !!! TODO: Frequency 255 is a blacklist, maybe add them to a generic ban list?
1073
+				// They appeared! Block this.
1065 1074
 				if (!empty($check['appears']))
1066 1075
 				{
1076
+					// Ban them because they are black listed?
1077
+					$autoBlackListResult = '0';
1078
+					if (!empty($modSettings['sfs_ipcheck_autoban']) && !empty($check['frequency']) && $check['frequency'] == 255)
1079
+						$autoBlackListResult = $this->BanNewIP($check['value']);
1080
+
1067 1081
 					$this->logBlockedStats('ip', $check);
1068
-					$requestBlocked = 'ip,' . $smcFunc['htmlspecialchars']($check['value']);
1082
+					$requestBlocked = 'ip,' . $smcFunc['htmlspecialchars']($check['value']) . ',' . ($autoBlackListResult ? 1 : 0);
1069 1083
 					break;
1070 1084
 				}
1071 1085
 			}
... ...
@@ -1108,7 +1122,7 @@ class SFS
1108 1122
 			$this->logAllStats('all', $checks, $requestBlocked);
1109 1123
 
1110 1124
 		// At this point, we have checked everything, do what needs to be done for our good person.
1111
-		if (!$requestBlocked)
1125
+		if (empty($requestBlocked))
1112 1126
 			return true;
1113 1127
 
1114 1128
 		// You are a bad spammer, don't tell them what was blocked.
... ...
@@ -1220,7 +1234,7 @@ class SFS
1220 1234
 				$user_info['ip'],
1221 1235
 				$user_info['ip2'],
1222 1236
 				json_encode($checks),
1223
-				$requestBlocked,
1237
+				$DebugMessage,
1224 1238
 				),
1225 1239
 			array('id_sfs', 'id_type')
1226 1240
 		);
... ...
@@ -1251,8 +1265,7 @@ class SFS
1251 1265
 		{
1252 1266
 			$data = @json_decode($requestData, true);
1253 1267
 
1254
-			// We got a error, return nothing.
1255
-			// !!! TODO: Log this?
1268
+			// We got a error, return nothing.  Don't log this, not worth it.
1256 1269
 			if (json_last_error() !== JSON_ERROR_NONE)
1257 1270
 				return array();
1258 1271
 			return $data;
... ...
@@ -1553,6 +1566,237 @@ class SFS
1553 1566
 			'type' => $this->search_params['type'],
1554 1567
 			'label' => $searchTypes[$this->search_params_type]['label'],
1555 1568
 		);
1569
+	}
1570
+
1571
+	/**
1572
+	 * Create a Ban Group if needed to handle automatic IP bans.
1573
+	 * We attempt to use the known ban function to create bans, otherwise we just fall back to a standard insert.
1574
+	 *
1575
+	 * @internal
1576
+	 * @CalledIn SMF 2.0, SMF 2.1
1577
+	 * @version 1.1
1578
+	 * @since 1.0
1579
+	 * @return bool True upon success, false otherwise.
1580
+	 */
1581
+	private function createBanGroup(bool $noChecks = false): bool
1582
+	{
1583
+		global $smcFunc, $modSettings, $sourcedir, $txt;
1584
+
1585
+		// Is this disabled? Don't do it.
1586
+		if (empty($noChecks) && empty($modSettings['sfs_ipcheck_autoban']))
1587
+			return false;
1588
+
1589
+		// Maybe just got unlinked, if we can find the matching name, relink it.
1590
+		$request = $smcFunc['db_query']('', '
1591
+			SELECT id_ban_group
1592
+			FROM {db_prefix}ban_groups
1593
+			WHERE name = {string:new_ban_name}' . '
1594
+			LIMIT 1',
1595
+			array(
1596
+				'new_ban_name' => $txt['sfs_ban_group_name'],
1597
+			)
1598
+		);
1599
+		if ($smcFunc['db_num_rows']($request) == 1)
1600
+		{
1601
+			$ban_data = $smcFunc['db_fetch_assoc']($result);
1602
+			$smcFunc['db_free_result']($request);
1603
+
1604
+			if (!empty($ban_data['id_ban_group']))
1605
+			{
1606
+				updateSettings(array('sfs_ipcheck_autoban_group' => $ban_data['id_ban_group']));
1607
+				return true;
1608
+			}
1609
+		}
1610
+		$smcFunc['db_free_result']($request);
1611
+
1612
+		require_once($sourcedir . '/ManageBans.php');
1613
+
1614
+		// Ban Information, this follows the format from the function.
1615
+		$ban_info = array(
1616
+			'name' => $txt['sfs_ban_group_name'],
1617
+			'cannot' => array(
1618
+				'access' => 1,
1619
+				'register' => 1,
1620
+				'post' => 1,
1621
+				'login' => 1,
1622
+			),
1623
+			'db_expiration' => 'NULL',
1624
+			'reason' => $txt['sfs_ban_group_reason'],
1625
+			'notes' => $txt['sfs_ban_group_notes']
1626
+		);
1627
+
1628
+		// If we can shortcut this..
1629
+		$ban_group_id = 0;
1630
+		if (function_exists('insertBanGroup'))
1631
+			$ban_group_id = insertBanGroup($ban_info);
1632
+
1633
+		// Fall back.
1634
+		if (is_array($ban_group_id) || empty($ban_group_id))
1635
+		{
1636
+			$smcFunc['db_insert']('',
1637
+				'{db_prefix}ban_groups',
1638
+				array(
1639
+					'name' => 'string-20', 'ban_time' => 'int', 'expire_time' => 'raw', 'cannot_access' => 'int', 'cannot_register' => 'int',
1640
+					'cannot_post' => 'int', 'cannot_login' => 'int', 'reason' => 'string-255', 'notes' => 'string-65534',
1641
+				),
1642
+				array(
1643
+					$ban_info['name'], time(), $ban_info['db_expiration'], $ban_info['cannot']['access'], $ban_info['cannot']['register'],
1644
+					$ban_info['cannot']['post'], $ban_info['cannot']['login'], $ban_info['reason'], $ban_info['notes'],
1645
+				),
1646
+				array('id_ban_group'),
1647
+				1
1648
+			);
1649
+			$ban_group_id = $smcFunc['db_insert_id']('{db_prefix}ban_groups', 'id_ban_group');
1650
+		}
1651
+
1652
+		// Didn't work? Try again later.
1653
+		if (empty($ban_group_id))
1654
+			return false;
1655
+
1656
+		updateSettings(array('sfs_ipcheck_autoban_group' => $ban_group_id));
1657
+		return true;
1658
+	}
1659
+
1660
+	/**
1661
+	 * They have triggered a automatic IP ban, lets do it.
1662
+	 * In newer versions we attempt to use more of the APIs, but fall back as needed.
1663
+	 *
1664
+	 * @param string $ip_address The IP address of the spammer.
1665
+	 *
1666
+	 * @internal
1667
+	 * @CalledIn SMF 2.0, SMF 2.1
1668
+	 * @version 1.1
1669
+	 * @since 1.0
1670
+	 * @return bool True upon success, false otherwise.
1671
+	 */
1672
+	private function BanNewIP(string $ip_address): bool
1673
+	{
1674
+		global $smcFunc, $modSettings, $sourcedir;
1675
+
1676
+		// Is this disabled? Don't do it.
1677
+		if (empty($modSettings['sfs_ipcheck_autoban']))
1678
+			return false;
1679
+
1680
+		// Did we loose our Ban Group? Try to fix this.
1681
+		if (empty($modSettings['sfs_ipcheck_autoban_group']))
1682
+			$this->createBanGroup();
1683
+
1684
+		// Still no Ban Group? Bail out.
1685
+		if (empty($modSettings['sfs_ipcheck_autoban_group']))
1686
+			return false;
1687
+
1688
+		require_once($sourcedir . '/ManageBans.php');
1689
+
1690
+		// If we have it, use the standard function.
1691
+		if (function_exists('insertBanGroup'))
1692
+		{
1693
+			// We don't call checkExistingTriggerIP as it induces a fatal error.
1694
+			$request = $smcFunc['db_query']('', '
1695
+				SELECT bg.id_ban_group, bg.name
1696
+				FROM {db_prefix}ban_groups AS bg
1697
+				INNER JOIN {db_prefix}ban_items AS bi ON
1698
+					(bi.id_ban_group = bg.id_ban_group)
1699
+					AND ip_low = {inet:ip_low} AND ip_high = {inet:ip_high}
1700
+				LIMIT 1',
1701
+				array(
1702
+					'ip_low' => $ip_address,
1703
+					'ip_high' => $ip_address,
1704
+				)
1705
+			);
1706
+			// Alredy exists, bail out.
1707
+			if ($smcFunc['db_num_rows']($request) != 0)
1708
+			{
1709
+				$smcFunc['db_free_result']($request);
1710
+				return false;
1711
+			}
1712
+
1713
+			// The trigger info.
1714
+			$triggers = array(
1715
+				array(
1716
+					'ip_low' => $ip_address,
1717
+					'ip_high' => $ip_address,
1718
+				)
1719
+			);
1720
+
1721
+			// Add it.
1722
+			addTriggers($modSettings['sfs_ipcheck_autoban_group'], $triggers);
1723
+		}
1724
+		// Go old school.
1725
+		else
1726
+		{
1727
+			$ip_parts = ip2range($ip_address);
1728
+
1729
+			// Not valid? Get out.
1730
+			if (count($ip_parts) != 4)
1731
+				return false;
1732
+
1733
+			// We don't call checkExistingTriggerIP as it induces a fatal error.
1734
+			$request = $smcFunc['db_query']('', '
1735
+				SELECT bg.id_ban_group, bg.name
1736
+				FROM {db_prefix}ban_groups AS bg
1737
+				INNER JOIN {db_prefix}ban_items AS bi ON
1738
+					(bi.id_ban_group = bg.id_ban_group)
1739
+					AND ip_low1 = {int:ip_low1} AND ip_high1 = {int:ip_high1}
1740
+					AND ip_low2 = {int:ip_low2} AND ip_high2 = {int:ip_high2}
1741
+					AND ip_low3 = {int:ip_low3} AND ip_high3 = {int:ip_high3}
1742
+					AND ip_low4 = {int:ip_low4} AND ip_high4 = {int:ip_high4}
1743
+				LIMIT 1',
1744
+				array(
1745
+					'ip_low1' => $ip_parts[0]['low'],
1746
+					'ip_high1' => $ip_parts[0]['high'],
1747
+					'ip_low2' => $ip_parts[1]['low'],
1748
+					'ip_high2' => $ip_parts[1]['high'],
1749
+					'ip_low3' => $ip_parts[2]['low'],
1750
+					'ip_high3' => $ip_parts[2]['high'],
1751
+					'ip_low4' => $ip_parts[3]['low'],
1752
+					'ip_high4' => $ip_parts[3]['high'],
1753
+				)
1754
+			);
1755
+			// Alredy exists, bail out.
1756
+			if ($smcFunc['db_num_rows']($request) != 0)
1757
+			{
1758
+				$smcFunc['db_free_result']($request);
1759
+				return false;
1760
+			}
1761
+
1762
+			$ban_triggers[] = array(
1763
+				$modSettings['sfs_ipcheck_autoban_group'],
1764
+				$ip_parts[0]['low'],
1765
+				$ip_parts[0]['high'],
1766
+				$ip_parts[1]['low'],
1767
+				$ip_parts[1]['high'],
1768
+				$ip_parts[2]['low'],
1769
+				$ip_parts[2]['high'],
1770
+				$ip_parts[3]['low'],
1771
+				$ip_parts[3]['high'],
1772
+				'',
1773
+				'',
1774
+				0,
1775
+			);
1776
+
1777
+			$smcFunc['db_insert']('',
1778
+				'{db_prefix}ban_items',
1779
+				array(
1780
+					'id_ban_group' => 'int', 'ip_low1' => 'int', 'ip_high1' => 'int', 'ip_low2' => 'int', 'ip_high2' => 'int',
1781
+					'ip_low3' => 'int', 'ip_high3' => 'int', 'ip_low4' => 'int', 'ip_high4' => 'int', 'hostname' => 'string-255',
1782
+					'email_address' => 'string-255', 'id_member' => 'int',
1783
+				),
1784
+				$ban_triggers,
1785
+				array('id_ban')
1786
+			);
1787
+		}
1556 1788
 
1789
+		// Log this.  The log will show from the user/guest and ip of spammer.
1790
+		logAction('ban', array(
1791
+			'ip_range' => $ip_address,
1792
+			'new' => 1,
1793
+			'source' => 'sfs'
1794
+		));
1795
+
1796
+		// Let things know we need updated ban data.
1797
+		updateSettings(array('banLastUpdated' => time()));
1798
+		updateBanMembers();
1799
+
1800
+		return true;
1557 1801
 	}
1558 1802
 }
1559 1803
\ No newline at end of file
... ...
@@ -11,6 +11,7 @@ $txt['sfs_general_title'] = 'General Configuration';
11 11
 $txt['sfs_enabled'] = 'Enable Stop Forum Spam?';
12 12
 $txt['sfs_log_debug'] = 'Enable Logging of all SFS requests (Debugging Only)?';
13 13
 $txt['sfs_ipcheck'] = 'Check IP Address?';
14
+$txt['sfs_ipcheck_autoban'] = 'Automatically ban IPs that are blacklisted?';
14 15
 $txt['sfs_usernamecheck'] = 'Check Username?';
15 16
 $txt['sfs_emailcheck'] = 'Check Email? (Recommended)';
16 17
 
... ...
@@ -77,3 +78,10 @@ $txt['sfs_log_types_0'] = 'Debug';
77 78
 $txt['sfs_log_types_1'] = 'Username';
78 79
 $txt['sfs_log_types_2'] = 'Email';
79 80
 $txt['sfs_log_types_3'] = 'IP Address';
81
+$txt['sfs_log_matched_on'] = 'Matched on %1$s [%2$s]';
82
+$txt['sfs_log_auto_banned'] = 'Banned';
83
+
84
+// The ban group info.
85
+$txt['sfs_ban_group_name'] = 'SFS Automatic IP Bans';
86
+$txt['sfs_ban_group_reason'] = 'Your IP has triggered the automatic ban for poor reputation and has been blacklisted';
87
+$txt['sfs_ban_group_notes'] = 'This Group is automatically created by the Stop Forum Spam Customization and automatically will add IPs that are blacklisted to this group';
80 88
\ No newline at end of file
81 89