Website/IPtables Blacklist/blocklists.sh
2024-11-14 16:49:03 -06:00

234 lines
8.1 KiB
Bash

#! /bin/bash
#### System Variables ####
IPSET_PREFIX="bl" # Prefix for ipset names
IPSET_TYPE="hash:net" # Type of created ipsets
IPV4=1 # Enable IPv4 by default
IPV6=1 # Enable IPv6 by default
QUIET=0 # Default quiet mode setting
VERBOSE=0 # Default verbosity level
declare -A BLOCKLISTS # Array for blocklists to use. Populated by CLI args,
IPV4_REGEX="(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/[1-3]?[0-9])?" # Regex for a valid IPv4 address with optional subnet part
IPV6_REGEX="(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(/[1-6]?[0-9])?" # Regef for a valid IPv6 address with optional subnet part
if [ ! "$(command -v ipset)" ]; then
apt -y install ipset
else
IPSET_BIN=$(command -v ipset)
fi
if [ ! -d "/var/lib/ipset" ]; then
mkdir -p /var/lib/ipset
else
IPSET_DIR="/var/lib/ipset" # Folder to write ipset save files to
fi
##
# Prints the help/usage message
##
function print_usage() {
{
cat << EOF
Usage: $0 [-h]
Blocking lists of IPs from public blocklists / blacklists (e.g. blocklist.de, spamhaus.org)
Options:
-l : Blocklist to use. Can be specified multiple times.
Format: "\$name \$url" (space-separated). See examples below.
-4 : Run in IPv4 only mode. Ignore IPv6 addresses.
-6 : Run in IPv6 only mode. Ignore IPv4 addresses.
-q : Quiet mode. Outputs are suppressed if flag is present.
-v : Verbose mode. Prints additional information during execution.
-h : Print this help message.
Example usage:
$0 -l "spamhaus https://www.spamhaus.org/drop/drop.txt"
$0 -l "blocklist https://lists.blocklist.de/lists/all.txt" -l "spamhaus https://www.spamhaus.org/drop/drop.txt"
$0 -l "spamhaus https://www.spamhaus.org/drop/drop.txt" -l "spamhaus6 https://www.spamhaus.org/drop/dropv6.txt"
EOF
}
}
#### Writes argument $1 to stdout if $QUIET is not set
function log() {
{
if [[ $QUIET -eq 0 ]]; then
echo "$1"
fi
}
}
#### Writes argument $1 to stdout if $VERBOSE is set and $QUIET is not set
function log_verbose() {
{
if [[ $VERBOSE -eq 1 ]]; then
if [[ $QUIET -eq 0 ]]; then
echo "$1"
fi
fi
}
}
#### Writes argument $1 to stderr. Ignores $QUIET.
function log_error() {
{
>&2 echo "[ERROR]: $1"
}
}
#### Validates the BLOCKLISTS array. Exits upon error.
function validate_blocklists() {
{
if [ ${#BLOCKLISTS[@]} -eq 0 ]; then
log_error "No blocklists given. Exiting..."
print_usage
exit 1
fi
for list in "${BLOCKLISTS[@]}"
do
list_name=$(echo "$list" | cut -d ' ' -f 1)
list_url=$(echo "$list" | cut -d ' ' -f 2)
if [ -z "$list_name" ]; then
log_error "Invalid name for list: $list"
exit 1
fi
if [ -z "$list_url" ]; then
log_error "Invalid url for list: $list"
exit 1
fi
log_verbose "Found valid blocklist: name=${list_name}, url=${list_url}"
done
}
}
#### Updates an ipset based on a list of IP addresses
function update_ipset() {
{
# Setup local vars
setname=$1 # $1 Name of the ipset to update
ipfile=$2 # $2 File containing all IP addresses to store in ipset
family=$3 # $3 Procotol family (e.g. inet OR inet6)
# Create temporary ipset to build and ensure existence of live ipset
livelist="$setname-$family"
templist="$setname-$family-T"
$IPSET_BIN create -q "$livelist" "$IPSET_TYPE" family "$family"
$IPSET_BIN create -q "$templist" "$IPSET_TYPE" family "$family"
log_verbose "Prepared ipset lists: livelist='$livelist', templist='$templist'"
while read -r ip; do
if $IPSET_BIN add "$templist" "$ip"; then
log_verbose "Added '$ip' to '$templist'"
else
log "Failed to add '$ip' to '$templist'"
fi
done < "$ipfile"
$IPSET_BIN swap "$templist" "$livelist"
log_verbose "Swapped ipset: $livelist"
$IPSET_BIN destroy "$templist"
log_verbose "Destroyed ipset: $templist"
# Write ipset savefile
$IPSET_BIN save "$livelist" > "$IPSET_DIR/$livelist.save"
log_verbose "Wrote savefile for '$livelist' to: $IPSET_DIR/$livelist.save"
log "Added $(wc -l < "$ipfile") to ipset '$livelist'"
}
}
#### Updates the given blocklist from an URL
function update_blocklist() {
{
# Download blocklist
log "Updating blacklist '$1' ..." # $1 Name of the blocklist
log_verbose "Downloading blocklist '$1' from: $2 ..." # $2 URL of the blocklist
tempfile=$(mktemp "/tmp/blocklist.$1.XXXXXXXX")
wget -q -O "$tempfile" "$2"
# Check downloaded list
linecount=$(wc -l < "$tempfile")
if [ "$linecount" -lt 10 ]; then
log_error "Blacklist '$1' containes only $linecount lines. This seems to short. Exiting..."
exit 1
fi
# Extract ips from raw list data
if [[ $IPV4 -eq 1 ]]; then
grep -v '^[#;]' "$tempfile" | grep -E -o "$IPV4_REGEX" | cut -d ' ' -f 1 > "$tempfile.filtered"
numips=$(wc -l < "$tempfile.filtered")
log_verbose "Got $numips IPv4 entries from blocklist '$1'"
if [[ $numips -gt 0 ]]; then
update_ipset "${IPSET_PREFIX}-$1" "$tempfile.filtered" "inet"
else
log_verbose "No IPv4 addresses found in blocklist '$1'. Skipping"
fi
fi
if [[ $IPV6 -eq 1 ]]; then
grep -v '^[#;]' "$tempfile" | grep -E -o "$IPV6_REGEX" | cut -d ' ' -f 1 > "$tempfile.filtered6"
numips=$(wc -l < "$tempfile.filtered6")
log_verbose "Got $numips IPv6 entries from blocklist '$1'"
if [[ $numips -gt 0 ]]; then
update_ipset "${IPSET_PREFIX}-$1" "$tempfile.filtered6" "inet6"
else
log_verbose "No IPv6 addresses found in blocklist '$1'. Skipping"
fi
fi
# Cleanup
rm "$tempfile"*
}
}
#### Main loop
function main() {
{
# Check arguments
validate_blocklists
# Update blocklists
for list in "${BLOCKLISTS[@]}"; do
list_name=$(echo "$list" | cut -d ' ' -f 1)
list_url=$(echo "$list" | cut -d ' ' -f 2)
update_blocklist "$list_name" "$list_url"
done
}
}
# Parse arguments
while getopts ":hqv46l:" opt
do
case ${opt} in
l) BLOCKLISTS[${#BLOCKLISTS[@]}]=${OPTARG}
;;
4) IPV4=1
IPV6=0
log "Using IPv4 only mode. Skipping IPv6 addresses."
;;
6) IPV4=0
IPV6=1
log "Using IPv6 only mode. Skipping IPv4 addresses."
;;
q) QUIET=1
;;
v) VERBOSE=1
;;
h) print_usage; exit
;;
:) print_usage; exit
;;
\?) print_usage; exit
;;
esac
done
#### Main function call
main