235 lines
8.1 KiB
Bash
235 lines
8.1 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
|
|
|
|
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
|
|
|
|
#### Min function call
|
|
main |