Script for Importing a Geo-Location IP-List into the "banned_ips" Table

Everything related to Hesk - helpdesk software

Moderator: mkoch227

Post Reply
PHSC
Posts: 2
Joined: Fri Jun 27, 2025 11:04 am

Script for Importing a Geo-Location IP-List into the "banned_ips" Table

Post by PHSC »

Hello everyone,

To restrict access to HESK to specific countries or individual IP addresses, manually recording "banned IPs" is not effective. Therefore, I have written a PHP script that imports an IP country list from GitHub weekly via CRON e.g.:

Code: Select all

https://myhesk.domain.com/inc/mail/import_Geo-Block-List.php?suppressOutput=true&key=XXXXXXXXXXX&allowedCountries=CH,DE,AT,FR,IT&excludedIPRanges=1.1.1.1-2.2.2.2,8.8.8.8
The script can be provided with entire country codes, specific IP ranges, or individual IPs that should NOT be imported into the list.

Code: Select all

<?php
/*
!!!!!-------- IMPORTANT ---------!!!!!
This script potentially imports a large number of entries. Displaying the entries under "Banned IPs" may therefore cause a browser crash!

This script downloads a CSV file from a URL containing IP address ranges and associated country codes.
It clears a database table and fills it with the IP address ranges that are not included in the list of allowed countries
or are within the excluded IP ranges.

Before importing, it checks if the "id" column in the "hesk_banned_ips" table is of type BIGINT(6) with AUTO_INCREMENT.
If not, the column is adjusted accordingly.
This is necessary because the original HESK table only allows SMALLINT(5), which cannot accommodate more than 65,535 rows.

The CSV file must be structured as follows:
Column 1: Start IP Address: The starting address of the IP range (e.g., 1.1.1.1).
Column 2: End IP Address: The ending address of the IP range (e.g., 1.1.1.10).
Column 3: Country Code: The 2-letter country code (e.g., CH, DE, AT).

Example of a line: 1.1.1.1,1.1.1.10,CH

Possible parameters:
- allowedCountries: A comma-separated list of 2-letter country codes that should not be inserted into the database.
At least the country code of your own country must be specified, otherwise everything will be blocked!
Example: ?allowedCountries=CH,DE,AT

- excludedIPRanges: A comma-separated list of IP ranges or individual IP addresses that should not be inserted into the database.
Example: ?excludedIPRanges=1.1.1.1-1.1.1.10,2.2.2.2

- suppressOutput: If this parameter is set to 'true', no screen outputs will be displayed.
Example: ?suppressOutput=true
*/

define('IN_SCRIPT', 1);
define('HESK_PATH', dirname(dirname(dirname(__FILE__))) . '/');
define('NO_HTTP_HEADER', 1);

// Parameter
$url = "https://github.com/sapics/ip-location-db/raw/refs/heads/main/geo-whois-asn-country/geo-whois-asn-country-ipv4.csv";
$allowedCountries = isset($_GET['allowedCountries']) ? array_map('trim', explode(',', $_GET['allowedCountries'])) : ['CH'];
$excludedIPRanges = isset($_GET['excludedIPRanges']) ? array_map('trim', explode(',', $_GET['excludedIPRanges'])) : [];
$suppressOutput = isset($_GET['suppressOutput']) && $_GET['suppressOutput'] == 'true';

require(HESK_PATH . 'hesk_settings.inc.php');
require(HESK_PATH . 'inc/common.inc.php');

// To execute the script, an API key must be provided using the parameter `?key=XXXXXXXXX`.
hesk_authorizeNonCLI();

function ipToCIDR($ipAddress) {
    $octets = explode('.', $ipAddress);
    if (count($octets) != 4) {
        return -1;
    }
    return $octets[0] * 256 * 256 * 256 + $octets[1] * 256 * 256 + $octets[2] * 256 + $octets[3];
}

function isIPInExcludedRange($ip, $excludedIPRanges) {
    $ipLong = ipToCIDR($ip);

    foreach ($excludedIPRanges as $range) {
        if (strpos($range, '-') !== false) {
            list($startIP, $endIP) = explode('-', $range);
            $start = ipToCIDR(trim($startIP));
            $end = ipToCIDR(trim($endIP));

            if ($ipLong >= $start && $ipLong <= $end) {
                return true;
            }
        } else {
            $excludedIP = ipToCIDR(trim($range));
            if ($ipLong == $excludedIP) {
                return true;
            }
        }
    }
    return false;
}

function getDBConnection($db_name, $db_user, $db_pass) {
    try {
        $db = new PDO('mysql:host=localhost;dbname=' . $db_name, $db_user, $db_pass);
        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        return $db;
    } catch (PDOException $e) {
        die("Datenbankverbindungsfehler: " . $e->getMessage());
    }
}

function checkAndFixIdColumn($db) {
    try {
        $result = $db->query("SHOW COLUMNS FROM hesk_banned_ips LIKE 'id'");
        $column = $result->fetch(PDO::FETCH_ASSOC);

        if ($column && (strtoupper($column['Type']) != 'BIGINT(6)' || strtoupper($column['Extra']) != 'AUTO_INCREMENT')) {
            $db->exec("ALTER TABLE hesk_banned_ips MODIFY COLUMN id BIGINT(6) NOT NULL AUTO_INCREMENT");
            if (!$suppressOutput) {
                echo "Die Spalte 'id' wurde auf BIGINT(6) AUTO_INCREMENT geändert.<br />";
            }
        }
    } catch (PDOException $e) {
        echo "Fehler beim Überprüfen oder Ändern der Spalte 'id': " . $e->getMessage() . "<br />";
    }
}

function truncDB($db) {
    try {
        $trunc = $db->prepare("TRUNCATE TABLE hesk_banned_ips");
        $trunc->execute();
        if (!$suppressOutput) {
            echo "Tabelle wurde erfolgreich geleert.<br />";
        }
    } catch (PDOException $e) {
        echo "Fehler beim Leeren der Tabelle: " . $e->getMessage() . "<br />";
    }
}

function insertIPRange($db, $startIP, $endIP, $country, $suppressOutput) {
    $ipFromCIDR = ipToCIDR($startIP);
    $ipToCIDR = ipToCIDR($endIP);
    $ipDisplay = $startIP . ' - ' . $endIP;

    try {
        $stmt = $db->prepare("INSERT INTO hesk_banned_ips (ip_from, ip_to, ip_display, banned_by, dt) VALUES (?, ?, ?, 3, NOW())");
        $stmt->execute([$ipFromCIDR, $ipToCIDR, $ipDisplay]);
        if (!$suppressOutput) {
            echo $ipDisplay . ' | ' . $country . '<br />';
        }
    } catch (PDOException $e) {
        echo "Fehler beim Einfügen in die Datenbank: " . $e->getMessage() . "<br />";
    }
}

function downloadAndProcessCSVFromUrl($url, $db, $allowedCountries, $excludedIPRanges, $suppressOutput) {
    try {
        $file = fopen($url, 'r');
        if ($file === FALSE) {
            throw new Exception("Fehler beim Öffnen der URL.");
        }

        while (($line = fgetcsv($file)) !== FALSE) {
            if (count($line) < 3) continue;

            $startIP = trim($line[0]);
            $endIP = trim($line[1]);
            $country = trim($line[2]);

            if (!in_array($country, $allowedCountries)) {
                $startIPCIDR = ipToCIDR($startIP);
                $endIPCIDR = ipToCIDR($endIP);

                $excludedIPsInRange = [];
                foreach ($excludedIPRanges as $range) {
                    if (strpos($range, '-') === false) {
                        $excludedIP = ipToCIDR(trim($range));
                        if ($excludedIP >= $startIPCIDR && $excludedIP <= $endIPCIDR) {
                            $excludedIPsInRange[] = $excludedIP;
                        }
                    }
                }

                sort($excludedIPsInRange);

                $currentStartIP = $startIP;
                foreach ($excludedIPsInRange as $excludedIP) {
                    $excludedIPStr = long2ip($excludedIP);
                    if ($currentStartIP != $excludedIPStr) {
                        insertIPRange($db, $currentStartIP, long2ip($excludedIP - 1), $country, $suppressOutput);
                    }
                    $currentStartIP = long2ip($excludedIP + 1);
                }

                if ($currentStartIP != $endIP) {
                    insertIPRange($db, $currentStartIP, $endIP, $country, $suppressOutput);
                }
            } else {
                if (!$suppressOutput) {
                    echo '<strong>' . $startIP . ' - ' . $endIP . ' | ' . $country . ' (erlaubtes Land, nicht eingefügt)</strong><br />';
                }
            }
        }
        fclose($file);
    } catch (Exception $e) {
        echo $e->getMessage();
    }
}

try {
    $db = getDBConnection($hesk_settings['db_name'], $hesk_settings['db_user'], $hesk_settings['db_pass']);
    checkAndFixIdColumn($db);
    truncDB($db);
    downloadAndProcessCSVFromUrl($url, $db, $allowedCountries, $excludedIPRanges, $suppressOutput);
} catch (Exception $e) {
    echo "Ein Fehler ist aufgetreten: " . $e->getMessage();
}
?>

It's far from perfect, but perhaps someone might find it useful. I am open to suggestions for improvement :)

Best regards,
Philipp

P.S. Maybe a developer will read this; it would actually be cool to use such a list, which is always updated in the background into a table "ip-location-db", to blacklist or whitelist countries based on country codes, or IP addresses/ranges in the graphical interface. It probably wouldn't be that difficult to implement...
Klemen
Site Admin
Posts: 10157
Joined: Fri Feb 11, 2005 4:04 pm

Re: Script for Importing a Geo-Location IP-List into the "banned_ips" Table

Post by Klemen »

Thank you for sharing your script for IP 2 Location import.

To block such large IP sets, it is usually better to use server-side blocking (in a firewall) or CDN blocking (like Cloudflare) instead of doing it in PHP.

However, this could still be useful to some, so thank you.
Klemen, creator of HESK and PHPJunkyardWas this helpful? You can buy me a drink here Image

Image You should follow me on Twitter here

Help desk software | Cloud help desk | Guestbook | Link manager | Click counter | more PHP Scripts ...

Also browse for php hosting companies, read php books, find php resources and use webmaster tools
PHSC
Posts: 2
Joined: Fri Jun 27, 2025 11:04 am

Re: Script for Importing a Geo-Location IP-List into the "banned_ips" Table

Post by PHSC »

Yes, of course. I know...

However, this option assumes that HESK is operated in a private environment with an appropriate firewall. If HESK is operated on normal shared web hosting without a self-managed firewall, this is not possible. In that case, the script can help to somewhat improve the security of HESK without self-managed hosting/firewall.
Post Reply