• The ImunifyAV extension is now deprecated and no longer available for installation.
    Existing ImunifyAV installations will continue operating for three months, and after that will automatically be replaced with the new Imunify extension. We recommend that you manually replace any existing ImunifyAV installations with Imunify at your earliest convenience.
  • The Horde webmail has been deprecated. Its complete removal is scheduled for April 2025. For details and recommended actions, see the Feature and Deprecation Plan.

Input Spamhaus (e)DROP Script for iptables

AbramS

Basic Pleskian
While migrating my Plesk server due to an OS upgrade last year, I pulled together the scripts that I wrote/modified and added some better documentation. I figured I would share these here, as there are probably some among you that can benefit from them. Hope it's helpful.
Note: I've added links to inspiration/authors wherever possible. But I couldn't quite trace the origins of some of these scripts. Happy to add a name if another author is found.

In this post I'm sharing the two scripts that I use for adding Spamhaus DROP and eDROP lists into iptables. They're basically the same scripts, just calling a different source file.

Spamhaus DROP script for iptables
Bash:
#!/bin/bash

# based off the following two scripts
# http://www.theunsupported.com/2012/07/block-malicious-ip-addresses/
# http://www.cyberciti.biz/tips/block-spamming-scanning-with-iptables.html
# Inspyr Media 2020.

# path to iptables
IPTABLES="/sbin/iptables";

# list of known spammers
URL="www.spamhaus.org/drop/drop.lasso";

# save local copy here
FILE="/tmp/drop.lasso";

# iptables custom chain
CHAIN="SpamhausDROP";

# check to see if the chain already exists
$IPTABLES -L $CHAIN -n

# check to see if the chain already exists
if [ $? -eq 0 ]; then

    # flush the old rules
    $IPTABLES -F $CHAIN

    echo "Flushed old rules. Applying updated Spamhaus list...."

else

    # create a new chain set
    $IPTABLES -N $CHAIN

    # tie chain to input rules so it runs
    $IPTABLES -A INPUT -j $CHAIN

    # don't allow this traffic through
    $IPTABLES -A FORWARD -j $CHAIN

    echo "Chain not detected. Creating new chain and adding Spamhaus list...."

fi;

# get a copy of the spam list
wget -qc $URL -O $FILE

# iterate through all known spamming hosts
for IP in $( cat $FILE | egrep -v '^;' | awk '{ print $1}' ); do

    # add the ip address log rule to the chain
    $IPTABLES -A $CHAIN -p 0 -s $IP -j LOG --log-prefix "[SPAMHAUS BLOCK]" -m limit --limit 3/min --limit-burst 10

    # add the ip address to the chain
    $IPTABLES -A $CHAIN -p 0 -s $IP -j DROP

    echo $IP

done

echo "Done!"

# remove the spam list
unlink $FILE
exit 0

Spamhaus eDROP script for iptables
Bash:
#!/bin/bash

# based off the following two scripts
# http://www.theunsupported.com/2012/07/block-malicious-ip-addresses/
# http://www.cyberciti.biz/tips/block-spamming-scanning-with-iptables.html
# Inspyr Media 2020.

# path to iptables
IPTABLES="/sbin/iptables";

# list of known spammers
URL="www.spamhaus.org/drop/edrop.lasso";

# save local copy here
FILE="/tmp/edrop.lasso";

# iptables custom chain
CHAIN="SpamhausEDROP";

# check to see if the chain already exists
$IPTABLES -L $CHAIN -n

# check to see if the chain already exists
if [ $? -eq 0 ]; then

    # flush the old rules
    $IPTABLES -F $CHAIN

    echo "Flushed old rules. Applying updated Spamhaus list...."

else

    # create a new chain set
    $IPTABLES -N $CHAIN

    # tie chain to input rules so it runs
    $IPTABLES -A INPUT -j $CHAIN

    # don't allow this traffic through
    $IPTABLES -A FORWARD -j $CHAIN

    echo "Chain not detected. Creating new chain and adding Spamhaus list...."

fi;

# get a copy of the spam list
wget -qc $URL -O $FILE

# iterate through all known spamming hosts
for IP in $( cat $FILE | egrep -v '^;' | awk '{ print $1}' ); do

    # add the ip address log rule to the chain
    $IPTABLES -A $CHAIN -p 0 -s $IP -j LOG --log-prefix "[SPAMHAUS BLOCK]" -m limit --limit 3/min --limit-burst 10

    # add the ip address to the chain
    $IPTABLES -A $CHAIN -p 0 -s $IP -j DROP

    echo $IP

done

echo "Done!"

# remove the spam list
unlink $FILE
exit 0

Disclaimer: I'm not a full-time coder and created/modified these to help solve problems that I encountered. There's probably cleaner/more efficient ways of doing this, so feel free to share any improvements.
 
Updated script for 2024: eDROP is no longer a thing as it has been merged with DROP, and the DROP file has a new URL. Also rewrote the script with more validation and logging. It also uses fail2ban instead of directly writing to IPTables, making the results visible in Plesk. Note: You will have to create a jail called spamhaus-drop. I have it setup to expire once a week, but it really doesn't matter as I run this script daily via crontab.

Bash:
#!/bin/bash

# Constants
DROP_URL="https://www.spamhaus.org/drop/drop.txt"
DROP_FILE="/tmp/drop.txt"
JAIL_NAME="spamhaus-drop"
LOG_FILE="/var/log/spamhaus_update.log"

# Reset the log file
> "$LOG_FILE" || echo "Failed to reset log file: $LOG_FILE"

# Function to log messages
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

# Function to exit on failure
exit_on_failure() {
    log_message "ERROR: $1"
    exit 1
}

# Function to validate IP addresses (IPv4 and CIDR)
validate_ip() {
    local ip=$1
    local ip_regex="^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)$"
    [[ $ip =~ $ip_regex ]]
}

# Download the Spamhaus DROP list
log_message "Downloading Spamhaus DROP list..."
curl -s -o "$DROP_FILE" "$DROP_URL"

# Check if the file was downloaded successfully and is not empty
if [[ -s "$DROP_FILE" ]]; then
    log_message "Download successful. File is not empty."
else
    exit_on_failure "Download failed or file is empty."
fi

# Extract "Last-Modified" and "Expires" values
last_modified=$(grep "^; Last-Modified:" "$DROP_FILE" | cut -d':' -f2- | xargs)
expires=$(grep "^; Expires:" "$DROP_FILE" | cut -d':' -f2- | xargs)

# Log the extracted values
log_message "Last-Modified: $last_modified"
log_message "Expires: $expires"

# Unban all IPs and reload the spamhaus-drop jail
log_message "Unbanning all IPs and reloading jail: $JAIL_NAME..."
fail2ban-client reload --unban "$JAIL_NAME" || exit_on_failure "Failed to unban IPs in $JAIL_NAME."

# Add all IPs from drop.txt to the spamhaus-drop jail
log_message "Adding IPs from $DROP_FILE to jail: $JAIL_NAME..."
last_ip=""
while IFS= read -r line; do
    # Skip comments and empty lines
    if [[ "$line" =~ ^\; ]] || [[ -z "$line" ]]; then
        continue
    fi

    # Extract the IP or CIDR range (remove the space and everything after the ;)
    ip=$(echo "$line" | awk -F' ;' '{print $1}')
    last_ip="$ip"  # Keep track of the last IP/CIDR

    # Validate the IP or CIDR range
    if validate_ip "$ip"; then
        # Add the IP or CIDR to the jail
        fail2ban-client set "$JAIL_NAME" banip "$ip" || log_message "Failed to ban IP: $ip"
        log_message "Successfully banned IP: $ip"
    else
        log_message "Invalid IP skipped: $ip"
    fi
done < "$DROP_FILE"

# Final check: Ensure the last IP/CIDR was added
if fail2ban-client status "$JAIL_NAME" | grep -q "$last_ip"; then
    log_message "The last IP/CIDR ($last_ip) was successfully added to the jail."
    log_message "Update completed successfully."
else
    exit_on_failure "Failed to add the last IP/CIDR ($last_ip) to the jail. Update completed with errors."
fi

# Cleanup: Remove the drop.txt file
log_message "Cleaning up temporary file..."
rm -f "$DROP_FILE" || log_message "Failed to remove temporary file $DROP_FILE."
log_message "Temporary file $DROP_FILE has been removed."
 
Updated script for 2024: eDROP is no longer a thing as it has been merged with DROP, and the DROP file has a new URL. Also rewrote the script with more validation and logging. It also uses fail2ban instead of directly writing to IPTables, making the results visible in Plesk. Note: You will have to create a jail called spamhaus-drop. I have it setup to expire once a week, but it really doesn't matter as I run this script daily via crontab.

Bash:
#!/bin/bash

# Constants
DROP_URL="https://www.spamhaus.org/drop/drop.txt"
DROP_FILE="/tmp/drop.txt"
JAIL_NAME="spamhaus-drop"
LOG_FILE="/var/log/spamhaus_update.log"

# Reset the log file
> "$LOG_FILE" || echo "Failed to reset log file: $LOG_FILE"

# Function to log messages
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

# Function to exit on failure
exit_on_failure() {
    log_message "ERROR: $1"
    exit 1
}

# Function to validate IP addresses (IPv4 and CIDR)
validate_ip() {
    local ip=$1
    local ip_regex="^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)$"
    [[ $ip =~ $ip_regex ]]
}

# Download the Spamhaus DROP list
log_message "Downloading Spamhaus DROP list..."
curl -s -o "$DROP_FILE" "$DROP_URL"

# Check if the file was downloaded successfully and is not empty
if [[ -s "$DROP_FILE" ]]; then
    log_message "Download successful. File is not empty."
else
    exit_on_failure "Download failed or file is empty."
fi

# Extract "Last-Modified" and "Expires" values
last_modified=$(grep "^; Last-Modified:" "$DROP_FILE" | cut -d':' -f2- | xargs)
expires=$(grep "^; Expires:" "$DROP_FILE" | cut -d':' -f2- | xargs)

# Log the extracted values
log_message "Last-Modified: $last_modified"
log_message "Expires: $expires"

# Unban all IPs and reload the spamhaus-drop jail
log_message "Unbanning all IPs and reloading jail: $JAIL_NAME..."
fail2ban-client reload --unban "$JAIL_NAME" || exit_on_failure "Failed to unban IPs in $JAIL_NAME."

# Add all IPs from drop.txt to the spamhaus-drop jail
log_message "Adding IPs from $DROP_FILE to jail: $JAIL_NAME..."
last_ip=""
while IFS= read -r line; do
    # Skip comments and empty lines
    if [[ "$line" =~ ^\; ]] || [[ -z "$line" ]]; then
        continue
    fi

    # Extract the IP or CIDR range (remove the space and everything after the ;)
    ip=$(echo "$line" | awk -F' ;' '{print $1}')
    last_ip="$ip"  # Keep track of the last IP/CIDR

    # Validate the IP or CIDR range
    if validate_ip "$ip"; then
        # Add the IP or CIDR to the jail
        fail2ban-client set "$JAIL_NAME" banip "$ip" || log_message "Failed to ban IP: $ip"
        log_message "Successfully banned IP: $ip"
    else
        log_message "Invalid IP skipped: $ip"
    fi
done < "$DROP_FILE"

# Final check: Ensure the last IP/CIDR was added
if fail2ban-client status "$JAIL_NAME" | grep -q "$last_ip"; then
    log_message "The last IP/CIDR ($last_ip) was successfully added to the jail."
    log_message "Update completed successfully."
else
    exit_on_failure "Failed to add the last IP/CIDR ($last_ip) to the jail. Update completed with errors."
fi

# Cleanup: Remove the drop.txt file
log_message "Cleaning up temporary file..."
rm -f "$DROP_FILE" || log_message "Failed to remove temporary file $DROP_FILE."
log_message "Temporary file $DROP_FILE has been removed."
Great job! Please give an example of how to configure spamhaus-drop Jail in /etc/fail2ban/jail.conf and /etc/fail2ban/filter.d/spamhaus-drop.conf (if needed)
 
Back
Top