Fail2ban action with external script


To help somebody out on the #fail2ban irc channel, I did create a special filter and actions, which will ban the IP address or disable the mail account when too many e-mails are sent from an foreign IP address.
The examples below are for qmail, but they should be adaptable to other services.

If you see any error or typo on this page, or have some other addition, please send an e-mail.

I also create other Fail2ban filters.

Table of Contents



Sample log lines

The filter and action mention in this article use log lines in the format below. If your log lines differ, then you will maybe need to adjust the
Filter qmail-ratelimit and for sure the shell script from Action qmail-disableaccount (see the line which starts with ACCOUNTS=).
@40000000527d6f2c3a1b7724 CHKUSER accepted sender: from <fabian@wenks.ch:fabian@wenks.ch:> remote <9b1a1ab9080c4d6:unknown:::ffff:192.0.2.42> rcpt <> : sender accepted
@40000000527d6f2d00660b0c CHKUSER relaying rcpt: from <fabian@wenks.ch:fabian@wenks.ch:> remote <9b1a1ab9080c4d6:unknown:::ffff:192.0.2.42> rcpt <recipient@example.com> : client allowed to relay


Filter qmail-ratelimit

This filter is for
qmail, a mail transfer agent (MTA). It is different from other Fail2ban filters, as it does not check for any failed logins, but it is used to count the amount of sent e-mails (depending on the configuration of findtime and maxretry in the Jail Configuration). Copy & paste from below and save it as /etc/fail2ban/filter.d/qmail-ratelimit.local or take it from the Complete Archive.

# Fail2Ban configuration file for qmail-ratelimit
#
# Author: Fabian Wenk <fabian@wenks.ch>
# 08-Nov-2013	initial publication
#
# $Revision$
#

[Definition]

# Option: failregex
# Notes.: regex to match the password failures messages in the logfile.
# Values: TEXT
#
failregex = CHKUSER (accepted sender|relaying rcpt): from \<.*@.*:.*@.*:\> remote \<.*:unknown:::ffff:<HOST>\> rcpt .* : (sender accepted|client allowed to relay)$

# Option:  ignoreregex
# Notes.:  regex to ignore. If this regex matches, the line is ignored.
# Values:  TEXT
#
ignoreregex = 


Action ban-foreign-iptables-multiport

This action will ban IP addresses only from foreign countries and not from one configured local country. The IP address from the local country will be added to the ignoreip list of the configured jail so the script does not need to be run to often. This action does also send a notification e-mail with useful information to the admin. Copy & paste from below and save it as /etc/fail2ban/action.d/ban-foreign-iptables-multiport.local or take it from the
Complete Archive.

# Fail2Ban configuration file
#
# Author: Cyril Jaquier
# Modified by Yaroslav Halchenko for multiport banning
# Modified by Fabian Wenk for ban-foreign
#

[INCLUDES]

before = iptables-blocktype.conf

[Definition]

# Option:  actionstart
# Notes.:  command executed once at the start of Fail2Ban.
# Values:  CMD
#
actionstart = iptables -N fail2ban-<name>
              iptables -A fail2ban-<name> -j RETURN
              iptables -I <chain> -p <protocol> -m multiport --dports <port> j fail2ban-<name>

# Option:  actionstop
# Notes.:  command executed once at the end of Fail2Ban
# Values:  CMD
#
actionstop = iptables -D <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
             iptables -F fail2ban-<name>
             iptables -X fail2ban-<name>

# Option:  actioncheck
# Notes.:  command executed once before each actionban command
# Values:  CMD
#
actioncheck = iptables -n -L <chain> | grep -q 'fail2ban-<name>[ \t]'

# Option:  actionban
# Notes.:  command executed when banning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    See jail.conf(5) man page
# Values:  CMD
#
actionban = printf %%b "Subject: [Fail2Ban] <name>: <ip>
            Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
            From: Fail2Ban <<sender>>
            To: <dest>\n
            `/etc/fail2ban/scripts/ban-foreign-iptables-multiport.sh <name> "<blocktype>" <ip> <logpath> <country> ban`\n
            " | /usr/sbin/sendmail -f <sender> <dest>

# Option:  actionunban
# Notes.:  command executed when unbanning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    See jail.conf(5) man page
# Values:  CMD
#
actionunban = /etc/fail2ban/scripts/ban-foreign-iptables-multiport.sh <name> "<blocktype>" <ip> <logpath> <country> unban

[Init]

# Default name of the chain
#
name = default

# Option:  port
# Notes.:  specifies port to monitor
# Values:  [ NUM | STRING ]  Default:
#
port = ssh

# Option:  protocol
# Notes.:  internally used by config reader for interpolations.
# Values:  [ tcp | udp | icmp | all ] Default: tcp
#
protocol = tcp

# Option:  chain
# Notes    specifies the iptables chain to which the fail2ban rules should
# be
#          added
# Values:  STRING  Default: INPUT
chain = INPUT

# Destination/Addressee of the mail
#
dest = root

# Sender of the mail
#
sender = fail2ban

For the above action to work also copy & paste from below and save it as /etc/fail2ban/scripts/ban-foreign-iptables-multiport.sh or take it from the Complete Archive.

#!/usr/bin/env bash

# ban only foreign IP addresses and
# add local (choosed country) to the ignore list of the jail
# notify admin of banned IP with log lines and whois output
#
# Fabian Wenk <fabian@wenks.ch>
# 21-Nov-2013	Initial publication
# 02-Dec-2013	reworked

# check if needed options are given
if [ "${4}" = "" ]; then
	echo " Error: use ${0} <jail> <blocktype> <ip> <logpath> <country> <action>"
	exit 1
else
	if [ ! -f ${4} ]; then
		echo " Error: file ${4} does not exist"
		exit 1
	else
		JAIL=${1}
		BLOCKTYPE=${2}
		IP=${3}
		LOGPATH=${4}
		if [ "${5}" = "" ]; then
			echo " Error: <country> not set, use ISO country code for source IP to ignore."
			echo "        If you do not want to filter based on country use: DISABLED"
			exit 1
		else
			COUNTRY="`echo ${5} | tr '[:lower:]' '[:upper:]'`"
		fi
		if [ "${6}" = "" ]; then
			echo " Error: <action> not set, use 'ban' or 'unban'."
			exit 1
		else
			ACTION="${6}"
		fi
	fi
fi

# Define variables
# Commands to ban / unban foreign IP addresses
ACTIONBAN="iptables -I fail2ban-${JAIL} 1 -s ${IP} -j ${BLOCKTYPE}"
ACTIONUNBAN="iptables -D fail2ban-${JAIL} -s ${IP} -j ${BLOCKTYPE}"

# check if IP address is from local or foreign country and act on it
WHOIS="`whois ${IP}`"
CURRENTCOUNTRY="`echo "${WHOIS}" | grep ^country: | awk '{ print $2 }' | tr '[:lower:]' '[:upper:]' | uniq`"
if [ "${ACTION}" = "ban" ]; then
	if [ ! "${CURRENTCOUNTRY}" = "${COUNTRY}" ]; then
		${ACTIONBAN}
		echo "IP address banned:"
		echo "=================="
	else
		echo "IP address not banned, added to ignorelist:"
		echo "==========================================="
	fi
	echo
	echo "Affected IP address: ${IP}"
	echo "Affected Country:    ${CURRENTCOUNTRY}"
	if [ "${CURRENTCOUNTRY}" = "${COUNTRY}" ]; then
		echo
		fail2ban-client set ${JAIL} addignoreip ${IP}
		echo
		echo "To manually ban this IP address, until the fail2ban-server is restarted, use:"
		echo "  ${ACTIONBAN}"
	fi
	echo
	echo
	echo "Entries from log file:"
	echo "----------------------"
	grep ${IP} ${LOGPATH}
	echo
	echo
	echo "Whois output:"
	echo "-------------"
	echo "${WHOIS}"
else
	if [ ! "${CURRENTCOUNTRY}" = "${COUNTRY}" ]; then
		${ACTIONUNBAN}
	fi
fi
exit 0


Action ban-foreign-bsd-ipfw

This action will ban IP addresses only from foreign countries and not from one configured local country. The IP address from the local country will be added to the ignoreip list of the configured jail so the script does not need to be run to often. This action does also send a notification e-mail with useful information to the admin. Copy & paste from below and save it as /etc/fail2ban/action.d/ban-foreign-bsd-ipfw.local or take it from the
Complete Archive.

# Fail2Ban configuration file
#
# Author: Nick Munger
# Modified by: Ken Menzel
#              Daniel Black (start/stop)
#              Fabian Wenk (many ideas as per fail2ban users list)
#              Fabian Wenk (ban-foreign)
#
# Ensure firewall_enable="YES" in the top of /etc/rc.conf
#

[Definition]

# Option:  actionstart
# Notes.:  command executed once at the start of Fail2Ban.
# Values:  CMD
#
actionstart = ipfw show | fgrep -q 'table(<table>)' || ( ipfw show | awk 'BEGIN { b = 1 } { if ($1 <= b) { b = $1 + 1 } else { e = b } } END { if (e) exit e <br> else exit b }'; num=$?; ipfw -q add $num <blocktype> <block> from table\(<table>\) to me <port>; echo $num > "<startstatefile>" )


# Option:  actionstop
# Notes.:  command executed once at the end of Fail2Ban
# Values:  CMD
#
actionstop =  [ ! -f <startstatefile> ] || ( read num < "<startstatefile>" <br> ipfw -q delete $num <br> rm "<startstatefile>" )


# Option:  actioncheck
# Notes.:  command executed once before each actionban command
# Values:  CMD
#
actioncheck = 


# Option:  actionban
# Notes.:  command executed when banning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    See jail.conf(5) man page
# Values:  CMD
#
# requires an ipfw rule like "deny ip from table(1) to me"
actionban = printf %%b "Subject: [Fail2Ban] <name>: <ip>
            Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
            From: Fail2Ban <<sender>>
            To: <dest>\n
            `/etc/fail2ban/scripts/ban-foreign-bsd-ipfw.sh <name> <table> <ip> <logpath> <country> ban`\n
            " | /usr/sbin/sendmail -f <sender> <dest>


# Option:  actionunban
# Notes.:  command executed when unbanning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    See jail.conf(5) man page
# Values:  CMD
#
actionunban = /etc/fail2ban/scripts/ban-foreign-bsd-ipfw.sh <name> <table> <ip> <logpath> <country> unban

[Init]
# Option:  table
# Notes:   The ipfw table to use. If a ipfw rule using this table already exists,
#          this action will not create a ipfw rule to block it and the following
#          options will have no effect.
# Values:  NUM
table = 1

# Option:  port
# Notes.:  Specifies port to monitor. Blank indicate block all ports.
# Values:  [ NUM | STRING ]
#
port = 

# Option:  startstatefile
# Notes:   A file to indicate that the table rule that was added. Ensure it is unique per table.
# Values:  STRING
startstatefile = /var/run/fail2ban/ipfw-started-table_<table>

# Option: block
# Notes:  This is how much to block.
#         Can be "ip", "tcp", "udp" or various other options.
# Values: STRING
block = ip

# Option:  blocktype
# Notes.:  How to block the traffic. Use a action from man 5 ipfw
#          Common values: deny, unreach port, reset
#          ACTION defination at the top of man ipfw for allowed values.
# Values:  STRING
#
blocktype = unreach port

# Destination/Addressee of the mail
#
dest = root

# Sender of the mail
#
sender = fail2ban

For the above action to work also copy & paste from below and save it as /etc/fail2ban/scripts/ban-foreign-bsd-ipfw.sh or take it from the Complete Archive.

#!/usr/bin/env bash

# ban only foreign IP addresses and
# add local (choosed country) to the ignore list of the jail
# notify admin of banned IP with log lines and whois output
#
# Fabian Wenk <fabian@wenks.ch>
# 21-Nov-2013	Initial publication
# 28-Nov-2013	Adapted for bsd-ipfw
# 02-Dec-2013	reworked

# check if needed options are given
if [ "${4}" = "" ]; then
	echo " Error: use ${0} <jail> <table> <ip> <logpath> <country> <action>"
	exit 1
else
	if [ ! -f ${4} ]; then
		echo " Error: file ${4} does not exist"
		exit 1
	else
		JAIL=${1}
		TABLE=${2}
		IP=${3}
		LOGPATH=${4}
		if [ "${5}" = "" ]; then
			echo " Error: <country> not set, use ISO country code for source IP to ignore"
			exit 1
		else
			COUNTRY="`echo ${5} | tr '[:lower:]' '[:upper:]'`"
		fi
		if [ "${6}" = "" ]; then
			echo " Error: <action> not set, use 'ban' or 'unban'."
			exit 1
		else
			ACTION="${6}"
		fi
	fi
fi

# Define variables
# Commands to ban / unban foreign IP addresses
ACTIONBAN="ipfw table ${TABLE} add ${IP}"
ACTIONUNBAN="ipfw table ${TABLE} delete ${IP}"

# function for reporting to admin
WHOIS="`whois ${IP}`"
CURRENTCOUNTRY="`echo "${WHOIS}" | grep ^country: | awk '{ print $2 }' | tr '[:lower:]' '[:upper:]' | uniq`"
if [ "${ACTION}" = "ban" ]; then
	if [ ! "${CURRENTCOUNTRY}" = "${COUNTRY}" ]; then
		${ACTIONBAN}
		echo "IP address banned:"
		echo "=================="
	else
		echo "IP address not banned, added to ignorelist:"
		echo "==========================================="
	fi
	echo
	echo "Affected IP address: ${IP}"
	echo "Affected Country:    ${CURRENTCOUNTRY}"
	if [ "${CURRENTCOUNTRY}" = "${COUNTRY}" ]; then
		echo
		fail2ban-client set ${JAIL} addignoreip ${IP}
		echo
		echo "To manually ban this IP address, until the fail2ban-server is restarted, use:"
		echo "  ${ACTIONBAN}"
	fi
	echo
	echo
	echo "Entries from log file:"
	echo "----------------------"
	grep ${IP} ${LOGPATH}
	echo
	echo
	echo "whois output:"
	echo "-------------"
	echo "${WHOIS}"
else
	if [ ! "${CURRENTCOUNTRY}" = "${COUNTRY}" ]; then
		${ACTIONUNBAN}
	fi
fi
exit 0


Action qmail-disableaccount

This action will disable SMTP accounts for
qmail, but only when e-mails are sent from IP addresses of foreign countries and not from one configured local country. The IP address from the local country will be added to the ignoreip list of the configured jail so the script does not need to be run to often. This action does also send a notification e-mail to the user with an URL where you can give detailed instructions. It also does send a notification e-mail with useful information to the admin. Copy & paste from below and save it as /etc/fail2ban/action.d/qmail-disableaccount.local or take it from the Complete Archive.

# Fail2Ban configuration file for qmail-ratelimit
#
# Author: Fabian Wenk <fabian@wenks.ch>
# 12-Nov-2013	initial publication
# 21-Nov-2013	small adjustments
# 02-Dec-2013	small adjustments
#
# $Revision$
#

[Definition]

# Option:  actionstart
# Notes.:  command executed once at the start of Fail2Ban.
# Values:  CMD
#
actionstart = 

# Option:  actionstop
# Notes.:  command executed once at the end of Fail2Ban
# Values:  CMD
#
actionstop = 

# Option:  actioncheck
# Notes.:  command executed once before each actionban command
# Values:  CMD
#
actioncheck = 

# Option:  actionban
# Notes.:  command executed when banning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    See jail.conf(5) man page
# Values:  CMD
#
actionban = printf %%b "Subject: [Fail2Ban] <name>: <ip>
            Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
            From: Fail2Ban <<sender>>
            To: <dest>\n
            `/etc/fail2ban/scripts/qmail-disableaccount.sh <name> <ip> <logpath> <country> <url>`\n
            " | /usr/sbin/sendmail -f <sender> <dest>

# Option:  actionunban
# Notes.:  command executed when unbanning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    See jail.conf(5) man page
# Values:  CMD
#
actionunban = 

[Init]

# Default name of the chain
#
name = default

# Destination/Addressee of the mail
#
dest = root

# Sender of the mail
#
sender = fail2ban

For the above action to work also copy & paste from below and save it as /etc/fail2ban/scripts/qmail-disableaccount.sh or take it from the Complete Archive.

#!/usr/bin/env bash

# parse log messages from qmail, disable smtp account and notfiy user
# add local (choosed country) to the ignore list of the jail
# notify admin of "banned" IP with log lines and whois output
#
# Fabian Wenk <fabian@wenks.ch>
# 12-Nov-2013	Initial publication
# 21-Nov-2013	small corrections
# 02-Dec-2013	reworked

# check if needed options are given
if [ "${3}" = "" ]; then
	echo " Error: use ${0} <jail> <ip> <logpath> <country> <url>"
	exit 1
else
	if [ ! -f ${3} ]; then
		echo " Error: file ${3} does not exist"
		exit 1
	else
		JAIL=${1}
		IP=${2}
		LOGPATH=${3}
		if [ "${4}" = "" ]; then
			echo " Error: <country> not set, use ISO country code for source IP to ignore."
			echo "        If you do not want to filter based on country use: DISABLED"
			exit 1
		else
			COUNTRY="`echo ${4} | tr '[:lower:]' '[:upper:]'`"
		fi
		if [ "${5}" = "" ]; then
			echo " Error: <url> not set."
			exit 1
		else
			URL="${5}"
		fi
	fi
fi

# Define variables
# Command to disable account (e-mail address will be appended):
COMMANDTODISABLEACCOUNT="vmoduser -d -p -w -i"
# Subject of the e-mail to the disabled account (e-mail address will be appended):
SUBJECT="SMTP access disabled for"
# Mail body text sent to the disabled account:
MAILTEXT="Dear User

We had to disable your SMTP access for your account,
for more information please visit:
${URL}

Best Regards"

# check if IP address is from a foreign country and act on it
WHOIS="`whois ${IP}`"
CURRENTCOUNTRY="`echo "${WHOIS}" | grep ^country: | awk '{ print $2 }' | tr '[:lower:]' '[:upper:]' | uniq`"
LOGLINES="`grep ${IP} ${LOGPATH}`"
ACCOUNTS="`echo "${LOGLINES}" | grep 'CHKUSER accepted sender' | awk '{ print $6 }' | sed 's/^<.*:\(.*\):>$/\1/' | sort | uniq`"
if [ ! "${CURRENTCOUNTRY}" = "${COUNTRY}" ]; then
	# disabling accounts and send info to user (and admin)
	for i in ${ACCOUNTS} ; do
		${COMMANDTODISABLEACCOUNT} ${i}
		echo "${MAILTEXT}" | mailx -s "${SUBJECT} ${i}" ${i}
	done
	echo "E-mail accounts disabled:"
	echo "========================="
	echo
	echo "Because of abuse from ${IP} the following accounts had been disabled:"
	echo
	echo "${ACCOUNTS}"
else
	echo "No e-mail account disabled, IP address added to ignorelist:"
	echo "==========================================================="
fi
echo
echo "Affected IP address: ${IP}"
echo "Affected Country:    ${CURRENTCOUNTRY}"
if [ "${CURRENTCOUNTRY}" = "${COUNTRY}" ]; then
	echo
	fail2ban-client set ${JAIL} addignoreip ${IP}
	echo
	echo "To manually disable this e-mail accounts, use:"
	for i in ${ACCOUNTS} ; do
		echo "  ${COMMANDTODISABLEACCOUNT} ${i}"
	done
	echo
	echo "Do not forget to inform the user!"
fi
echo
echo
echo "Entries from log file:"
echo "----------------------"
echo "${LOGLINES}"
echo
echo
echo "Whois output:"
echo "-------------"
echo "${WHOIS}"
exit 0


Jail Configuration

To use the above actions (
ban-foreign-iptables-multiport, ban-foreign-bsd-ipfw or qmail-disableaccount) copy & paste the corresponding entry from below to your /etc/fail2ban/jail.local file or take it from the Complete Archive.
Be sure to change enabled = false to enabled = true and also adjust country=, url= and dest= (You do not want to send me e-mails with the details out of your log files!) parmeters. Eventually also adjust both logpath= occurrences for the jail you use.
For all three jails even if a local IP address is matched, fail2ban will add the IP address to the list of banned IP addresses, but the script which is started from the action takes care, that in this case no IP address is banned or the account is disabled. But still, after the bantime is reached fail2ban will unban it, but for local IP addresses with just doing nothing. For the [qmail-disable] jail the bantime is not relevant, but still used, so it is the best to leave it at 0. In the fail2ban.log ban and unban are logged anyway, depending on the loglevel you have set.

[DEFAULT]
ignoreip = 127.0.0.1

# qmail-foreign with iptables-multiport
[qmail-foreign]
enabled  = false
filter   = qmail-ratelimit
action   = ban-foreign-iptables-multiport[name=qmail-foreign, port="25,465,587", logpath=/var/log/qmail/smtpd/current, country=CH, dest=fabian@wenks.ch]
logpath  = /var/log/qmail/smtpd/current
findtime = 10
maxretry = 5
bantime  = 7200

# qmail-foreign with bsd-ipfw
[qmail-foreign]
enabled  = false
filter   = qmail-ratelimit
action   = ban-foreign-bsd-ipfw[name=qmail-foreign, port="25,465,587", table=25, logpath=/var/log/qmail/smtpd/current, country=CH, dest=fabian@wenks.ch]
logpath  = /var/log/qmail/smtpd/current
findtime = 10
maxretry = 5
bantime  = 7200

# qmail-disable accounts
[qmail-disable]
enabled  = false
filter   = qmail-ratelimit
action   = qmail-disableaccount[name="qmail-disable", logpath=/var/log/qmail/smtpd/current, country=CH, url="http://www.example.com/", dest=fabian@wenks.ch]
logpath  = /var/log/qmail/smtpd/current
findtime = 120
maxretry = 50
bantime	 = 0

Complete Archive

Download the complete fail2ban-action-with-script.tgz archive, which contains all the files from this article. Extract it and copy the needed files into the corresponding directories in your existing /etc/fail2ban/ directory.



Document History

02-Dec-2013Initial publication

Fabian Wenk last update 02-Dec-2013