#!/usr/bin/env bash
# shellcheck disable=SC2181
#
# Description: Script for adding/updating GeoIP blocking via ipsets
#              for the default firewalld zone. 
#              Pulls data from ipdeny.com. Fork of a fork.
#
# Homepage: https://codeberg.org/krathalan/miscellaneous-scripts
#
# Originally written by Simon Bouchard <sbouchard@layer7.email>
# https://github.com/simonbouchard/geoip-blocking-w-firewalld (GPLv3)
# Originally refactored and inspired from https://gist.github.com/Pandry/21fc0e30abbfd0579ec69c491b99a446
#
# Copyright (C) 2026 Hunter Peavey
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

# -----------------------------------------
# -------------- Guidelines ---------------
# -----------------------------------------

# This script follows the Google Shell Style Guide:
# https://google.github.io/styleguide/shell.xml

# This script uses shellcheck: https://www.shellcheck.net/

# See https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -Eeuo pipefail

# -----------------------------------------
# ------------- User variables ------------
# -----------------------------------------

# Define your list of zones to block in /etc/krathalan/geoip-blocklist.conf
# See included example conf file in git repo root

# Names for the ipv4/ipv6 ipset lists that are inserted into firewalld.
readonly BLACKLIST_IP4="geoip-blacklist-ip4"
readonly BLACKLIST_IP6="geoip-blacklist-ip6"

# -----------------------------------------
# ----------- Program variables -----------
# -----------------------------------------


FIREWALLD_DEFAULT_ZONE="$(firewall-cmd --get-default-zone)"
readonly FIREWALLD_DEFAULT_ZONE

readonly SCRIPT_NAME="$0"
TMP_DIR="$(mktemp -d -t "${SCRIPT_NAME##*/}_XXXXXXXX")"
readonly TMP_DIR

trap "clean_up" EXIT SIGINT

clean_up()
{
  if [[ -d "${TMP_DIR:-}" ]]; then
    rm -rf "${TMP_DIR}"
  fi
}

# -----------------------------------------
# --------------- Functions ---------------
# -----------------------------------------

print_info()
{
  printf "[i] %s\n" "$1"
}

print_success()
{
    printf "[✓] %s\n" "$1"
}

print_failure()
{
    printf "[X] %s\n" "$1"
}

# -----------------------------------------
# ---------------- Script -----------------
# -----------------------------------------

# Load config file
if ! [[ -f "/etc/krathalan/geoip-blocklist.conf" ]]; then
    print_failure "Configuration file /etc/krathalan/geoip-blocklist.conf not found"
    exit 1
fi

# shellcheck disable=SC1091
source /etc/krathalan/geoip-blocklist.conf

print_info "Checking for existing ipset for ${BLACKLIST_IP4}"

if firewall-cmd --permanent --get-ipsets | grep -q "${BLACKLIST_IP4}"; then
    print_info "Deleting ipset ${BLACKLIST_IP4}"
    firewall-cmd --permanent --zone="${FIREWALLD_DEFAULT_ZONE}" --remove-source=ipset:"${BLACKLIST_IP4}" 
    firewall-cmd --reload 
    firewall-cmd --permanent --delete-ipset="${BLACKLIST_IP4}" 
fi

print_info "Creating new ipset for ${BLACKLIST_IP4}"
firewall-cmd --permanent --new-ipset="${BLACKLIST_IP4}" --type=hash:net --option=family=inet --option=hashsize=4096 --option=maxelem=200000 --zone="${FIREWALLD_DEFAULT_ZONE}" 

if [[ $? -eq 0 ]]; then
    print_success "ipset for ${BLACKLIST_IP4} successfully created"
else
    print_failure "Couldn't create the blacklist ${BLACKLIST_IP4}"
    exit 1
fi

printf "\n"

print_info "Checking for existing ipset for ${BLACKLIST_IP6}"
if firewall-cmd --permanent --get-ipsets | grep -q "${BLACKLIST_IP6}"; then
    print_info "Deleting ipset ${BLACKLIST_IP6}"
    firewall-cmd --permanent --zone="${FIREWALLD_DEFAULT_ZONE}" --remove-source=ipset:"${BLACKLIST_IP6}" 
    firewall-cmd --reload 
    firewall-cmd --permanent --delete-ipset="${BLACKLIST_IP6}" 
fi

print_info "Creating new ipset for ${BLACKLIST_IP6}"
firewall-cmd --permanent --new-ipset="${BLACKLIST_IP6}" --type=hash:net --option=family=inet6 --option=hashsize=4096 --option=maxelem=200000 --zone="${FIREWALLD_DEFAULT_ZONE}" 

if [[ $? -eq 0 ]]; then
    print_success "ipset for ${BLACKLIST_IP6} successfully created"
else
    print_failure "Couldn't create the blacklist ${BLACKLIST_IP6}"
    exit 1
fi

printf "\n"
print_info "Retrieving the requested zones to be blacklisted"

for zone in $ZONES; do
    print_info "Downloading zone ${zone}..."
    curl -s -L -o "${TMP_DIR}/${zone}-ip4.zone" "https://www.ipdeny.com/ipblocks/data/aggregated/${zone,,}-aggregated.zone" 
    curl -s -L -o "${TMP_DIR}/${zone}-ip6.zone" "https://www.ipdeny.com/ipv6/ipaddresses/aggregated/${zone,,}-aggregated.zone" 
    if [[ $? -eq 0 ]]; then
        print_success "OK"
    else
        print_failure "failed"
    fi
done

printf "\n"
print_info "Adding IP ranges into firewalld ipsets"
print_info "Some countries do not have ip6 ranges. Some may error when adding, but that's okay."

# Load the zone(s) into the blacklist
for zoneFile in "${TMP_DIR}"/*-ip4.zone; do
    print_info "Adding ipv4 target ranges from ${zoneFile}..."
    firewall-cmd --permanent --ipset="${BLACKLIST_IP4}" --add-entries-from-file="${zoneFile}" > /dev/null
    if [[ $? -eq 0 ]]; then
        print_success "OK"
    else
        print_failure "failed"
    fi
done

# Some countries don't have an ip6 range -- this can fail
set +Ee

for zoneFile in "${TMP_DIR}"/*-ip6.zone; do
    print_info "Adding ipv6 target ranges from ${zoneFile}..."
    firewall-cmd --permanent --ipset="${BLACKLIST_IP6}" --add-entries-from-file="${zoneFile}" > /dev/null
    if [[ $? -eq 0 ]]; then
        print_success "OK"
    else
        print_failure "failed"
    fi
done

set -Ee

printf "\n"

# Initialize the firewall
print_info "Initializing firewalld"
firewall-cmd --permanent --zone="${FIREWALLD_DEFAULT_ZONE}" --add-source="ipset:${BLACKLIST_IP4}"
firewall-cmd --permanent --zone="${FIREWALLD_DEFAULT_ZONE}" --add-source="ipset:${BLACKLIST_IP6}"

# Reload the firewall
print_info "Reloading firewalld"
firewall-cmd --reload

IPV4_COUNT=$(firewall-cmd --permanent --ipset=${BLACKLIST_IP4} --get-entries | wc -l)
IPV6_COUNT=$(firewall-cmd --permanent --ipset=${BLACKLIST_IP6} --get-entries | wc -l)
readonly IPV4_COUNT IPV6_COUNT

print_info "Blocking approx. ${YELLOW}${IPV4_COUNT}${NC} ipv4 target ranges and approx. ${YELLOW}${IPV6_COUNT}${NC} ipv6 target ranges."

print_success "Firewall successfully configured!"
