Could we help you? Please click the banners. We are young and desperately need the money
In today's digital landscape, securing your email server is more crucial than ever. Dovecot, a popular IMAP and POP3 server, is a prime target for malicious actors. To combat this, we've developed a powerful bash script that automates IP banning across multiple domains, significantly enhancing your Dovecot security. Let's dive into how this script works and how you can implement it to protect your email infrastructure.
Our Dovecot multi-domain IP banning script is a robust solution designed to:
By automating these processes, the script provides a powerful first line of defense against brute force attacks and unauthorized access attempts.
Before we dive into the script itself, let's cover the dependencies and setup required:
To install fail2ban on a Debian-compatible distribution, use:
sudo apt-get update
sudo apt-get install fail2ban
Create the fail2ban filter in:/etc/fail2ban/filter.d/empty.conf
[Definition]
failregex =
ignoreregex =
Set up the fail2ban jail in:/etc/fail2ban/jail.d/dovecot-multidomain.conf
[dovecot-multidomain]
enabled = true
port = 110,143,993,995
filter = empty
logpath = /dev/null
maxretry = 0
findtime = 86400
bantime = 86400
After configuring, restart fail2ban:
sudo systemctl restart fail2ban
In this configuration, 86400
represents 24 hours in seconds. Adjust findtime
and bantime
as needed for your security requirements.
Our Dovecot multi-domain IP banning script relies on GeoIP data to apply country-specific thresholds. Here's how to set up the necessary GeoIP dependencies:
First, install the mmdb-bin and geoipupdate packages:
sudo apt-get update
sudo apt-get install mmdb-bin geoipupdate
Edit the GeoIP configuration file:
sudo nano /etc/GeoIP.conf
Add your MaxMind account ID and license key (you'll need to create a free account at maxmind.com):
AccountID YOUR_ACCOUNT_ID
LicenseKey YOUR_LICENSE_KEY
EditionIDs GeoLite2-Country GeoLite2-City
Run the following command to download the latest GeoIP databases:
sudo geoipupdate
To keep your GeoIP data current, set up a weekly cron job. Open your crontab:
sudo crontab -e
Add the following line to run geoipupdate every Monday at 1 AM:
0 1 * * 1 /usr/bin/geoipupdate
With these steps completed, your system will have up-to-date GeoIP data, allowing our script to accurately apply country-specific thresholds for IP banning.
To ensure GeoIP is working correctly, you can test it with an IP address:
mmdblookup --file /usr/share/GeoIP/GeoLite2-Country.mmdb --ip 8.8.8.8 country names en
This should return the country name for the given IP address (in this case, "United States").
By properly setting up and maintaining your GeoIP database, you enhance the effectiveness of the Dovecot multi-domain IP banning script, allowing for more nuanced and accurate security measures based on geographical data.
Please check out our GitHub repository for the latest version of the script:
Now, let's break down our Dovecot multi-domain IP banning script and explain its key components.
#!/bin/bash
# Configuration
DOVECOT_LOG="/var/log/dovecot-info.log" # The path to your dovecot-info.log file
DOVECOT_LOG_1="/var/log/dovecot-info.log.1" # Previous log file for rotation handling
BAN_LOG="/var/log/dovecot-multidomain-ip-ban.log"
AMOUNTOFHOURSTOCHECK=24 # Number of hours to look back for failed logins
JAIL_NAME="dovecot-multidomain" # Fail2Ban jail name
GEOIP_BIN=$(which mmdblookup) # Path to GeoIP lookup binary
COUNTRY_DB="/var/lib/GeoIP/GeoLite2-Country.mmdb" # GeoIP database file
HIGH_THRESHOLD_COUNTRIES="CH LI CA" # Countries with higher threshold for banning
LOW_THRESHOLD=2 # Minimum number of domains for most countries
HIGH_THRESHOLD=4 # Minimum number of domains for high-threshold countries
MAX_AMOUNT_LINES=600000 # Maximum lines to fetch from log files. Lower values improve speed but may miss entries with large logs or longer check periods. Adjust based on log volume and AMOUNTOFHOURSTOCHECK.
WHITELIST="127.0.0.1 178.22.109.64" # IPs that should never be banned
# Console output control:
# Set CONSOLE_DEBUG_OUTPUT to 1 to enable any console output, 0 to disable all console output
CONSOLE_DEBUG_OUTPUT=1
# Set CONSOLE_DEBUG_OUTPUT_VERBOSE to 1 for full console output (all skipped, banned, and already banned messages),
# or 0 to show only newly banned IPs. This setting has no effect if CONSOLE_DEBUG_OUTPUT is set to 0.
CONSOLE_DEBUG_OUTPUT_VERBOSE=0
# Function to log messages with timestamp and log level as well as taking care of CLI debug output
log_message() {
local level=$1
shift
local message="$@"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> "$BAN_LOG"
# Control console output based on debug settings
if [ "$CONSOLE_DEBUG_OUTPUT" -eq 1 ]; then
if [ "$CONSOLE_DEBUG_OUTPUT_VERBOSE" -eq 1 ] || [[ "$message" == BANNED* ]] || [[ "$level" == "ERROR" ]]; then
echo "[$timestamp] [$level] $message"
fi
fi
}
# Function to get country code from IP using GeoIP database
get_country_code() {
local ip=$1
${GEOIP_BIN} -f ${COUNTRY_DB} -i "$ip" country iso_code | awk -F'"' '{print $2}' | xargs
}
# Function to determine minimum domains required for banning based on country
get_min_domains() {
local country=$1
if [[ " $HIGH_THRESHOLD_COUNTRIES " =~ " $country " ]]; then
echo $HIGH_THRESHOLD
else
echo $LOW_THRESHOLD
fi
}
# Function to check if an IP is already banned
is_ip_banned() {
local ip=$1
fail2ban-client status $JAIL_NAME | grep -q "$ip"
return $?
}
log_message "INFO" "Script execution start"
# Build the regex pattern for the last X hours
FAILED_LOGIN_PATTERN=""
for (( i = ${AMOUNTOFHOURSTOCHECK}; i > 0; i-- )); do
hour=$(date -d "-${i} hour" '+%Y-%m-%d %H')
FAILED_LOGIN_PATTERN+="(${hour}.*auth failed)|"
done
FAILED_LOGIN_PATTERN=${FAILED_LOGIN_PATTERN%|} # Remove trailing '|'
# Fetch all failed login attempts from the log
current_day=$(date +%d)
current_hour=$(date +%H)
if [ "$current_day" -eq 1 ] && [ "$current_hour" -lt "$AMOUNTOFHOURSTOCHECK" ]; then
# Include both current and previous log files
FAILED_LOGINS=$(tail -q -n ${MAX_AMOUNT_LINES} "$DOVECOT_LOG_1" "$DOVECOT_LOG" | grep -h -E "$FAILED_LOGIN_PATTERN")
else
# Only use current log file
FAILED_LOGINS=$(tail -q -n ${MAX_AMOUNT_LINES} "$DOVECOT_LOG" | grep -h -E "$FAILED_LOGIN_PATTERN")
fi
# Count total failed logins and unique IPs
FAILED_LOGIN_COUNT=$(echo "$FAILED_LOGINS" | wc -l)
UNIQUE_IP_COUNT=$(echo "$FAILED_LOGINS" | grep -oP 'rip=\K[0-9.]+' | sort -u | wc -l)
log_message "INFO" "Amount of failed logins found: $FAILED_LOGIN_COUNT"
log_message "INFO" "Number of unique IPs: $UNIQUE_IP_COUNT"
# Process failed logins
echo "$FAILED_LOGINS" | while read -r line; do
ip=$(echo "$line" | grep -oP 'rip=\K[0-9.]+')
user=$(echo "$line" | grep -oP 'user=<\K[^>]+')
timestamp=$(echo "$line" | awk '{print $1, $2}')
# Check if the user field contains a domain
if [[ $user == *@* ]]; then
domain=$(echo "$user" | cut -d'@' -f2)
else
domain="no_domain.tld"
fi
if [ -n "$ip" ] && [ -n "$domain" ] && [ -n "$timestamp" ]; then
echo "$ip $domain $timestamp"
fi
done | sort | uniq | awk '
{
ip[$1] = $1
domains[$1][$2] = 1
if (!timestamp[$1] || $3 " " $4 < timestamp[$1]) {
timestamp[$1] = $3 " " $4
}
}
END {
for (i in ip) {
printf "%s: ", i
domain_list = ""
for (d in domains[i]) {
domain_list = domain_list d " "
}
printf "%s|%s\n", domain_list, timestamp[i]
}
}' | while read -r line; do
# Extract information for each IP
ip=$(echo "$line" | cut -d':' -f1)
domain_info=$(echo "$line" | cut -d':' -f2-)
domain_list=$(echo "$domain_info" | cut -d'|' -f1 | sed 's/^ *//' | sed 's/ *$//')
earliest_timestamp=$(echo "$domain_info" | cut -d'|' -f2)
domains=$(echo "$domain_list" | wc -w)
country=$(get_country_code "$ip")
min_domains=$(get_min_domains "$country")
# Create a single-line log entry for each IP
log_entry="IP: $ip | Country: $country | Domains: $domains/$min_domains | Earliest: $earliest_timestamp | List: [$domain_list]"
# Decide whether to ban the IP based on the number of unique domains
if [ "$domains" -ge "$min_domains" ]; then
if [[ ! " $WHITELIST " =~ " $ip " ]]; then
if is_ip_banned "$ip"; then
log_message "INFO" "ALREADY BANNED | $log_entry"
else
RETVAL=$(fail2ban-client set $JAIL_NAME banip $ip 2>&1)
if [ "$RETVAL" = "1" ]; then
log_message "WARN" "BANNED | $log_entry"
else
log_message "ERROR" "FAILED TO BAN | $log_entry"
log_message "DEBUG" "fail2ban-client output: $RETVAL"
fi
fi
else
log_message "INFO" "SKIPPED (whitelisted) | $log_entry"
fi
else
log_message "INFO" "SKIPPED (insufficient domains) | $log_entry"
fi
done
log_message "INFO" "Script execution completed"
Proper log rotation is crucial for maintaining the effectiveness of our multi-domain IP banning script and managing disk space efficiently. We'll use logrotate to configure this for Dovecot logs. Here's how to set it up:
Create or edit the following file (ensure that there's only 1 configuration for this!):/etc/logrotate.d/dovecot.conf
sample content:
/var/log/dovecot*.log {
rotate 6
monthly
missingok
notifempty
compress
sharedscripts
delaycompress
postrotate
doveadm log reopen
endscript
}
Let's break down the key parameters:
Important Note: The delaycompress
parameter is essential for the proper functioning of our multi-domain IP banning script. It ensures that the script can access the previous month's log file when needed, especially important during the first few days of each month.
Other parameters like missingok
notifempty
compress
are standard log rotation settings that help manage logs efficiently.
The postrotate
script uses doveadm log reopen
to ensure Dovecot properly handles the log rotation without requiring a restart.
While we recommend keeping at least 6 months of logs (rotate 6), you may need to adjust this based on your server's storage capacity and compliance requirements. Remember, increasing the number of rotations will increase the historical data available to the script but will also consume more disk space.
After making changes to the logrotate configuration, you can test it without waiting for the scheduled run:
sudo logrotate -d /etc/logrotate.d/dovecot.conf
This command will show you what logrotate would do, without actually rotating the logs.
By properly configuring log rotation, you ensure that our multi-domain IP banning script has access to the necessary historical data while maintaining efficient use of your server's storage resources. This setup complements the script's functionality, particularly its ability to handle log rotation scenarios at the beginning of each month.
The script has been optimized to run efficiently:
To ensure regular execution, set up a CRON job. We recommend running the script every 5 minutes:
*/5 * * * * /home/srvdata/dovecot-multidomain-ip-ban
Adjust the path to match your script's location.
After implementation, monitor the script's effectiveness by reviewing the log file. Here's an example of what you might see:
[2024-08-13 12:59:03] [INFO] BANNED | IP: 92.52.146.18 | Country: UA | Domains: 2/2 | Earliest: 2024-08-12 15:04:50 | List: [no_domain.tld domain1.ch]
[2024-08-13 12:59:03] [INFO] SKIPPED (insufficient domains) | IP: 24.38.214.194 | Country: US | Domains: 1/2 | Earliest: 2024-08-12 21:02:59 | List: [domain2.ch]
[2024-08-13 13:20:04] [INFO] ALREADY BANNED | IP: 88.235.188.89 | Country: TR | Domains: 2/2 | Earliest: 2024-08-12 19:02:18 | List: [no_domain.tld domain3.ch]
This log excerpt shows the script in action, identifying potentially malicious IPs and taking appropriate action based on the configured thresholds.
Important: This script is provided as-is, without any warranty or guarantee. Users should understand that they are using this script at their own risk. LEXO does not take any responsibilities or liabilities for any data loss, system damage, or any other issues that may arise from the use of this script. It is strongly recommended to thoroughly test the script in a non-production environment before using it on critical systems. Always ensure you have multiple backups of your important data using various methods.
While the script is already optimized for performance, here are some potential areas for further improvement:
A: Unlike standard fail2ban setups, this script analyzes login attempts across multiple domains, applies country-specific thresholds, and provides more granular control over the banning process.
A: While designed for Dovecot, the script can be adapted for other servers that generate similar log formats. You'll need to adjust the log parsing logic accordingly.
A: It's recommended to update the GeoIP database at least monthly to ensure accurate country identification. Some providers offer weekly updates for more current data.
A: You can unban an IP using the fail2ban-client command: sudo fail2ban-client unban ip [IP_ADDRESS]
Consider adding frequently mis-banned IPs to the whitelist.
Implementing this Dovecot multi-domain IP banning script is a significant step towards enhancing your email server's security. By automating the process of identifying and banning potentially malicious IPs across multiple domains, you create a robust defense against brute force attacks and unauthorized access attempts.
Remember, security is an ongoing process. Regularly review and adjust the script's parameters based on your specific needs and the evolving threat landscape. Stay informed about new security threats and best practices in email server administration.
With proper implementation, monitoring, and integration with other security measures, this script can be a powerful tool in your server security arsenal, helping to ensure the integrity and availability of your email services.
We encourage you to share your experiences, suggestions, or questions about this script in the comments section below. Your feedback helps us improve and provides valuable insights for other users implementing similar security measures.