diff --git a/ddos-mitigator.conf b/ddos-mitigator.conf new file mode 100644 index 0000000..abcd59e --- /dev/null +++ b/ddos-mitigator.conf @@ -0,0 +1,30 @@ +# Example configuration file for ddos-mitigator.sh. +# PLEASE TAKE CARE not to put any whitespace around the '=' signs, as this file is directly sourced by the script +# file and this needs to conform to the BASH syntax. Also, make sure to declare the COUNTRIES variable with the +# correct array syntax: COUNTRIES=("AA" "BB" "CC"), or to comment it out altogether. + +# The path to the GeoIP2 database file (must be either country or city database). This parameter is mandatory. If it is +# not specified here, it must be given on the command line (through the -d option). +DATABASE_FILE="/path/to/geoip/country-or-city-database.mmdb" + +# Enable the autopilot for automatically banning IP addresses of the desired countries (see also COUNTRIES option). +# Only ban IP addresses with at least AUTOPILOT current connections. If the value is not specified or 0, don't +# automatically ban IP addresses, but run in interactive mode. +AUTOPILOT="0" + +# Defines the subnet size in bytes to be analyzed. Valid values are: +# - 8 for class A networks (X.0.0.0/8) +# - 16 for class B networks (X.X.0.0/16) +# - 24 for class C networks (X.X.X.0/24) +# - 32 for class D networks (X.X.X.X/32) +# If not specified, run in interactive mode and prompt for the netmask size. +NETMASK="8" + +# The country-codes to block as an array. Defaults to "CN" (China). +#COUNTRIES=("CN" "HK" "TW") + +# Specify the JAIL to use for banning the IP addresses. Defaults to 'apache-auth'. +#JAIL="apache-auth" + +# The desired port to monitor. Defaults to 443 (https). +#PORT="443" diff --git a/ddos-mitigator.sh b/ddos-mitigator.sh index 8270584..f90c0e1 100755 --- a/ddos-mitigator.sh +++ b/ddos-mitigator.sh @@ -32,10 +32,7 @@ # # ################################################################################ -# Set the host's own IP address. So far, only an IPv4 address is supported. -MY_IP="94.199.214.20" - -# After this point, no editing is required. +# Store the start time; this enables us to output the total runtime at the end. start="$(date +%s)" # Dependencies of this script. Simple array with the following structure: @@ -139,6 +136,13 @@ Usage: $(basename $0) -d FILE [OPTION...] printed to stderr and the program terminates with exit code 1. + -f, --config-file=FILENAME Specify the full path to the configuration + file. If omitted, all options are read + from the command line. + Any parameter specified on the command + line takes precedence over configuration + option read from the file. + -j, --jail=JAIL Specify the JAIL to use for banning the IP addresses. Defaults to 'apache-auth'. @@ -194,8 +198,48 @@ function filter() { mv "${filtered}" "${file}" } +function set_default_values() { + if [[ -z "${autopilot}" ]]; then + autopilot=0 + fi + if [[ -z "${bancountries}" ]]; then + bancountries=("CN") + fi + if [[ -z "${jail}" ]]; then + jail="apache-auth" + fi + if [[ -z "${netmask}" ]]; then + netmask=0 + fi + if [[ -z "${port}" ]]; then + port=443 + fi +} + +function parse_config_file() { + source "${configfile}" + if [[ -z "${autopilot}" ]]; then + autopilot="${AUTOPILOT}" + fi + if [[ -z "${bancountries}" ]]; then + bancountries=(${COUNTRIES[@]}) + fi + if [[ -z "${database}" ]]; then + database="${DATABASE_FILE}" + fi + if [[ -z "${jail}" ]]; then + jail="${JAIL}" + fi + if [[ -z "${netmask}" ]]; then + netmask="${NETMASK}" + fi + if [[ -z "${port}" ]]; then + port="${PORT}" + fi +} + function parse_command_line_args() { - TEMP=$(getopt -o 'a::,c:,d:,e,j:,n:,p:,h' -l 'auto::,country:,database:,dependencies,jail:,netmask:,port:,help' -- "$@") + TEMP=$(getopt -o 'a::,c:,d:,e,f:,j:,n:,p:,h' -l 'auto::,country:,database:,dependencies,config-file:,jail:,netmask:,port:,help' -- "$@") if [ $? -ne 0 ]; then echo 'Error parsing command line options. Terminating. Invoke with --help for help.' >&2 @@ -212,10 +256,6 @@ function parse_command_line_args() { '') autopilot=1 ;; - *[!0-9]*) - echo "Invalid argument for parameter 'auto': '${2}'. Invoke with --help for help." >&2 - exit 1 - ;; *) autopilot="$2" ;; @@ -224,10 +264,6 @@ function parse_command_line_args() { ;; '-c' | '--country') IFS=',' read -ra bancountries <<<"${2}" - if [[ -z ${bancountries[@]// /} ]]; then - echo "Invalid argument for parameter 'country': '${2}'. Invoke with --help for help." >&2 - exit 1 - fi shift ;; '-d' | '--database') @@ -238,27 +274,30 @@ function parse_command_line_args() { check_dependencies exit $? ;; + '-f' | '--config-file') + configfile="${2}" + shift + ;; '-j' | '--jail') jail="${2}" shift ;; '-n' | '--netmask') case "${2}" in - '1' | '8') + '1') netmask=8 ;; - '2' | '16') + '2') netmask=16 ;; - '3' | '24') + '3') netmask=24 ;; - '4' | '32') + '4') netmask=32 ;; *) - echo "Invalid argument for parameter 'netmask': '${2}'. Invoke with --help for help." >&2 - exit 1 + netmask="${2}" ;; esac shift @@ -283,15 +322,82 @@ function parse_command_line_args() { shift done + # If the config file option is set, parse the config file. + if [[ ! -z ${configfile+x} ]]; then + if [[ ! -f "${configfile}" || ! -r "${configfile}" ]]; then + echo "Can not read configuration file '${2}'. Invoke with --help for help." >&2 + exit 1 + fi + + parse_config_file + fi + + # Here, we set the default values for all options that have not been set yet. + set_default_values +} + +function validate_parameter_values() { + # GeoIP-Database if [[ -z "${database}" ]]; then echo "No GeoIP database specified. Invoke with --help for more information." >&2 exit 1 fi - if [[ ! -r "${database}" ]]; then + if [[ ! -f "${database}" || ! -r "${database}" ]]; then echo "Database '${database}' is not accessible." >&2 exit 1 fi + + # Autopilot + case "${autopilot}" in + *[!0-9]*) + echo "Invalid value for parameter 'auto' / 'AUTOPILOT': '${autopilot}'." >&2 + echo "Invoke with --help for help." >&2 + exit 1 + ;; + esac + + # Countries + if [[ -z ${bancountries[@]// /} ]]; then + echo "Invalid value for parameter 'country' / 'COUNTRIES': '${bancountries[*]}'." >&2 + echo "Invoke with --help for help." >&2 + exit 1 + fi + + # Jail + if [[ -z "${jail}" ]]; then + echo "Invalid value for parameter 'jail' / 'JAIL': '${jail}'." >&2 + echo "Invoke with --help for help." >&2 + exit 1 + fi + + # Netmask + case "${netmask}" in + '0' | '8' | '16' | '24' | '32') + # Everything OK. + ;; + *) + echo "Invalid value for parameter 'netmask': '${2}'." >&2 + echo "Invoke with --help for help." >&2 + exit 1 + ;; + esac + + # Port + case "${port}" in + *[!0-9]*) + echo "Invalid value for parameter 'port' / 'PORT': '${autopilot}'." >&2 + echo "Invoke with --help for help." >&2 + exit 1 + ;; + esac + if [[ ${port} -lt 0 || ${port} -gt 65535 ]]; then + echo "Invalid value for parameter 'port' / 'PORT': '${autopilot}'." >&2 + echo "Value must be between 0 ... 65535 (inclusive)." >&2 + echo "Invoke with --help for help." >&2 + exit 1 + ;; + fi } ################################################################################ @@ -444,14 +550,15 @@ banlist="${tmpdir}/banlist.txt" touch "${banlist}" # Parse the command line options -autopilot=0 -netmask=0 -jail="apache-auth" -bancountries=("CN") +autopilot= +netmask= +jail= +bancountries= database= -port=443 +port= parse_command_line_args "$@" +validate_parameter_values check_dependencies dependencies_ok=$?