diff --git a/web/css/styles.min.css b/web/css/styles.min.css
index 374b458c..d84a6a85 100644
--- a/web/css/styles.min.css
+++ b/web/css/styles.min.css
@@ -4236,3 +4236,29 @@ div.ui-dialog + div div{
padding: 5px 10px !important;
border-radius: 13px;
}
+
+.get-ip-info-btn {
+ cursor: pointer;
+ margin-left: 10px;
+}
+.get-ip-info-btn:hover {
+ color: #000000;
+}
+.get-ip-info-btn + .get-ip-info-result {
+ margin: 10px 0;
+}
+.get-ip-info-btn + .get-ip-info-result dl dt {
+ font-weight: bold;
+}
+.get-ip-info-btn + .get-ip-info-result dl dd {
+ margin: 0 0 10px 0;
+}
+.get-ip-info-btn + .get-ip-info-result dl .fa-exclamation-triangle {
+ color: red;
+}
+.get-ip-info-btn + .get-ip-info-result dl .fa-check-circle {
+ color: green;
+}
+.get-ip-info-btn + .get-ip-info-result dl .fa-exclamation-circle {
+ color: orange;
+}
diff --git a/web/inc/i18n/en.php b/web/inc/i18n/en.php
index 39c3bfff..09ae210f 100644
--- a/web/inc/i18n/en.php
+++ b/web/inc/i18n/en.php
@@ -377,6 +377,7 @@ $LANG['en'] = array(
'ErrorLog' => 'ErrorLog',
'Download AccessLog' => 'Download AccessLog',
'Download ErrorLog' => 'Download ErrorLog',
+ 'Continent' => 'Continent',
'Country' => 'Country',
'2 letter code' => '2 letter code',
'State / Province' => 'State / Province',
diff --git a/web/js/app.js b/web/js/app.js
index 9ba909a5..978b7d09 100644
--- a/web/js/app.js
+++ b/web/js/app.js
@@ -1072,3 +1072,42 @@ function elementHideShow(elementToHideOrShow){
el.style.display = el.style.display === 'none' ? 'block' : 'none';
}
+(function($) {
+ $(document).ready(function() {
+ $('.get-ip-info-btn').click(function() {
+ var token = $('#token').attr('token');
+ var index = $(this).attr('data-index');
+ var btn_el = $('.get-ip-info-btn[data-index="' + index + '"]');
+ var result_el = $('.get-ip-info-btn[data-index="' + index + '"] + .get-ip-info-result');
+ var ip = btn_el.attr('data-ip');
+
+ var url_params = new URLSearchParams(window.location.search);
+ var clear_cache = url_params.get('clear_cache');
+
+ if (!$.trim(result_el.html())) {
+ result_el.html('');
+
+ $.ajax({
+ method: "POST",
+ url: "/list/firewall/banlist/ip_info.php",
+ data: { ip: ip, clear_cache: clear_cache, token: token },
+ cache: false,
+ error: function(jqXHR, textStatus, errorThrown) {
+ result_el.html('GENERAL ERROR
' + errorThrown);
+ },
+ success: function(result_data) {
+ if (btn_el.find('i').hasClass('fa-times')) {
+ result_el.html(result_data);
+ }
+ }
+ });
+
+ btn_el.find('i').removeClass('fa-search').addClass('fa-times');
+ }
+ else {
+ result_el.html('');
+ btn_el.find('i').removeClass('fa-times').addClass('fa-search');
+ }
+ });
+ });
+})(jQuery);
diff --git a/web/list/firewall/banlist/ip_info.php b/web/list/firewall/banlist/ip_info.php
new file mode 100644
index 00000000..cf7607bf
--- /dev/null
+++ b/web/list/firewall/banlist/ip_info.php
@@ -0,0 +1,164 @@
+
+error_reporting(NULL);
+session_start();
+
+include($_SERVER['DOCUMENT_ROOT']."/inc/main.php");
+
+if (!function_exists('str_contains')) {
+ function str_contains($haystack, $needle)
+ {
+ return $needle !== '' && mb_strpos($haystack, $needle) !== false;
+ }
+}
+
+// cidrMatch() based on https://stackoverflow.com/a/14535823
+function cidrMatch($ip, $range)
+{
+ if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) return false;
+ list($subnet, $bits) = explode('/', $range);
+ $ip = substr(ipToBinary($ip), 0, $bits);
+ $subnet = substr(ipToBinary($subnet), 0, $bits);
+ return ($ip == $subnet);
+}
+
+// ipToBinary based on https://stackoverflow.com/a/14535823
+function ipToBinary($ip)
+{
+ $ipbin = '';
+ $ips = explode(".", $ip);
+ foreach ($ips as $iptmp) {
+ $ipbin .= sprintf("%08b", $iptmp);
+ }
+ return $ipbin;
+}
+
+function fetchURL($url, &$info = [])
+{
+ $curl_handle = curl_init();
+ curl_setopt($curl_handle, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($curl_handle, CURLOPT_ENCODING, 'gzip, deflate');
+ curl_setopt($curl_handle, CURLOPT_URL, $url);
+ curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, 10);
+ curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
+ $data = curl_exec($curl_handle);
+ $info = curl_getinfo($curl_handle);
+ curl_close($curl_handle);
+ return $data;
+}
+
+function parseCacheEntries($strEntries)
+{
+ $parsed = [];
+ $entries = explode("\n", $strEntries);
+ if ($entries) {
+ foreach ($entries as $entry) {
+ list($entry,) = explode("#", $entry);
+ list($entry,) = explode(";", $entry);
+ $entry = trim($entry);
+ if (!empty($entry)) $parsed[] = $entry;
+ }
+ }
+ return $parsed;
+}
+
+function checkIP($ip)
+{
+ $check_results = [];
+ $lists = [
+ 'BDEALL' => 'http://lists.blocklist.de/lists/all.txt',
+ 'BFB' => 'http://danger.rulez.sk/projects/bruteforceblocker/blist.php',
+ 'CIARMY' => 'http://www.ciarmy.com/list/ci-badguys.txt',
+ 'GREENSNOW' => 'https://blocklist.greensnow.co/greensnow.txt',
+ 'SPAMDROP' => 'https://www.spamhaus.org/drop/drop.txt',
+ 'SPAMEDROP' => 'https://www.spamhaus.org/drop/edrop.txt',
+ 'TOR' => 'https://check.torproject.org/cgi-bin/TorBulkExitList.py',
+ ];
+ $today = date('Y-m-d');
+
+ foreach ($lists as $code => $url) {
+ $cache_tag = 'ip-blacklist-' . $code . '-cache';
+
+ // init cache
+ if (!isset($_SESSION[$cache_tag])) $_SESSION[$cache_tag] = ['updated' => '', 'items' => [], 'http_code' => ''];
+
+ // invalidate cache if clear_cache parameter is 1
+ if (!empty($_REQUEST['clear_cache']) && $_REQUEST['clear_cache'] == 1) $_SESSION[$cache_tag]['updated'] = '2000-01-01';
+
+ // if cache is not updated, fetch new data and save to cache
+ if (strtotime($today) > strtotime($_SESSION[$cache_tag]['updated'])) {
+ $new_cache_data = fetchURL($url, $url_result);
+ if ($url_result['http_code'] == '200') $new_cache_items = parseCacheEntries($new_cache_data);
+ $_SESSION[$cache_tag] = ['updated' => $today, 'items' => $new_cache_items, 'http_code' => $url_result['http_code']];
+ }
+
+ // check ip
+ $matched_ips = array_filter($_SESSION[$cache_tag]['items'], function ($item) use ($ip) {
+ if (str_contains($item, '/')) return cidrMatch($ip, $item);
+ if ($ip == $item) return true;
+ return false;
+ });
+
+ $check_results[$code]['found'] = count($matched_ips) > 0 ? true : false;
+ $check_results[$code]['updated'] = $_SESSION[$cache_tag]['updated'];
+ $check_results[$code]['http_code'] = $_SESSION[$cache_tag]['http_code'];
+ }
+
+ return $check_results;
+}
+
+// Check token
+if ((!isset($_REQUEST['token'])) || ($_SESSION['token'] != $_REQUEST['token'])) {
+ die("Wrong token");
+}
+
+$ip = $_REQUEST['ip'];
+
+// Validate IP format
+if (filter_var($ip, FILTER_VALIDATE_IP) === false) {
+ die('GENERAL ERROR
BAD_IP_FORMAT');
+}
+
+// Query host
+$host = gethostbyaddr($ip);
+
+// Query blocklists
+$result_blocklists = '';
+$ip_check = checkIP($ip);
+if ($ip_check) {
+ foreach ($ip_check as $list_code => $list_results) {
+ $result_blocklists .= '