diff --git a/ddos-mitigator.sh b/ddos-mitigator.sh index cffead1..d0eb2ce 100755 --- a/ddos-mitigator.sh +++ b/ddos-mitigator.sh @@ -72,46 +72,40 @@ reset="\033[0m" trap 'sudo -k; popd >/dev/null; rm -r ${tmpdir}' EXIT function check_installed() { - local command="$1" - local package="$2" - which "${command}" 2>/dev/null >&2 - local result=$? + local command="$1" + local package="$2" + which "${command}" 2>/dev/null >&2 + local result=$? - if [[ "${result}" -ne 0 ]] ; then - echo -e "${red}Command ${bold}${command}${reset}${red} not found.${reset} Please install package ${blue}${package}${reset}." - exit 1 - fi + if [[ "${result}" -ne 0 ]] ; then + echo -e "${red}Command ${bold}${command}${reset}${red} not found.${reset} Please install package ${blue}${package}${reset}." + exit 1 + fi } -# Create a temp directory, chdir into it and create the (initially empty) -# banlist file. -tmpdir=$(mktemp -d) -# Suppress output of dir stack. -pushd "${tmpdir}" > /dev/null -touch "${banlist}" - function print_help() { cat <> "${filtered}" + else + echo "IGNORING ${address}, already banned." + fi + done 3< "${file}" + + mv "${filtered}" "${file}" +} + function parse_command_line_args() { - TEMP=$(getopt -o 'a::,j:,n:,h' -l 'auto::,jail:,netmask:,help' -- "$@") + TEMP=$(getopt -o 'a::,c:,j:,n:,h' -l 'auto::,country:,jail:,netmask:,help' -- "$@") if [ $? -ne 0 ] ; then echo 'Error parsing command line options. Terminating. Invoke with --help for help.' >&2 @@ -157,6 +171,10 @@ function parse_command_line_args() { esac shift ;; + '-c'|'--country') + bancountry="$2" + shift + ;; '-j'|'--jail') jail="$2" shift @@ -199,10 +217,164 @@ function parse_command_line_args() { done } +################################################################################ +# Set the highlighting color for the address count. The color goes from green +# (low count) over yellow (intermediate count) to red (high count). Depending +# on the size of the subnet (/8, /16, /24 or /32), the transitions from one +# color to the next happen at different values. +################################################################################ +function set_highlight_color() { + local count=$1 + case "${choice}" in + "1" ) + # /32: 0 <= green < 3 <= yellow < 5 <= red + if [ $count -ge 5 ] ; then + hilite="${red}" + elif [ $count -ge 3 ] ; then + hilite="${yellow}" + else + hilite="${green}" + fi + ;; + "2" ) + # /24: 0 <= green < 7 <= yellow < 13 <= red + if [ $count -ge 13 ] ; then + hilite="${red}" + elif [ $count -ge 7 ] ; then + hilite="${yellow}" + else + hilite="${green}" + fi + ;; + "3" ) + # /16: 0 <= green < 13 <= yellow < 25 <= red + if [ $count -ge 25 ] ; then + hilite="${red}" + elif [ $count -ge 13 ] ; then + hilite="${yellow}" + else + hilite="${green}" + fi + ;; + "4" ) + # /8: 0 <= green < 21 <= yellow < 49 <= red + if [ $count -ge 49 ] ; then + hilite="${red}" + elif [ $count -ge 21 ] ; then + hilite="${yellow}" + else + hilite="${green}" + fi + ;; + * ) + # ???: We should never get here. As a fall-back, just use no + # highlighting. + hilite="" + ;; + esac +} + +################################################################################ +# Process the file denoted by $1. For each line in the file, the count and the +# address are displayed and a whois request is made. The user can then choose to +# ban or ignore the address. Addresses chosen to be banned are appended to the +# $banlist. +################################################################################ +function process_file () { + local file="${1}" + local line='' + local count=0 + local addr='' + local banaction='' + local nline=1 + local country_cn=1 + # Read the contents from filedescriptor 3 (important: Don's use the + # standard filedescriptor because we need to handle user input from + # within the loop). + while IFS= read -r -u3 line ; do + line="$(echo "${line}" | tr -s '[:blank:]')" + count="$(echo "${line}" | cut -d' ' -f2)" + addr="$(echo "${line}" | cut -d' ' -f3-)${ext}" + set_highlight_color "${count}" + if [[ autopilot -eq 0 ]] ; then + whois "${addr}" | tee "${whoisoutput}" + else + whois "${addr}" > "${whoisoutput}" + fi + grep -iq "^country: *${bancountry}$" "${whoisoutput}" + country_cn=$? + echo -en "Address ${bold}$((nline++)) of ${nlines}${reset}: \ +Found '${blue}${addr}${reset}' ${hilite}${count}${reset} times." + + if [[ ${autopilot} -eq 0 ]] ; then + echo -en "Ban [y/N/s=No, and skip remaining]? " + read banaction + else + if [[ ${country_cn} -eq 0 ]] ; then + if [[ $count -ge $autopilot ]] ; then + echo -en "\n${red}Autopilot active. ${reset}" + banaction=y + else + echo -e "\n${yellow}Autopilot active. Ignoring remaining addresses due to limit of ${autopilot}.${reset}" + return + fi + else + if [[ $count -ge $autopilot ]] ; then + echo -en "\n${green}Autopilot active. ${reset}" + banaction=n + else + echo -e "\n${green}Autopilot active.${reset} ${yellow}Ignoring remaining addresses due to limit of ${autopilot}.${reset}" + return + fi + fi + fi + + case "${banaction}" in + "s" | "S" ) + echo -e "Not banning '${blue}${addr}${reset}', \ +skipping remaining addresses." + return + ;; + "y" | "Y" ) + echo -e "Adding '${blue}${addr}${reset}' to \ +banlist." + echo "${addr}" >> "${banlist}" + ;; + "n" | "N" | * ) + echo -e "Not banning '${blue}${addr}${reset}'." + ;; + esac + # Here goes: Pipe the file contents via filedescriptor 3. + done 3< "${file}" + echo "Processed all entries in ${file}." +} + +# Create a temp directory, chdir into it and create the (initially empty) +# banlist file. +tmpdir=$(mktemp -d) +# Suppress output of dir stack. +pushd "${tmpdir}" > /dev/null +touch "${banlist}" + +check_installed "sudo" "app-admin/sudo" +check_installed "fail2ban-client" "net-analyzer/fail2ban" +check_installed "whois" "net-misc/whois" +check_installed "cut" "sys-apps/coreutils" +check_installed "id" "sys-apps/coreutils" +check_installed "sort" "sys-apps/coreutils" +check_installed "touch" "sys-apps/coreutils" +check_installed "tr" "sys-apps/coreutils" +check_installed "uniq" "sys-apps/coreutils" +check_installed "grep" "sys-apps/grep" +check_installed "sponge" "sys-apps/moreutils" +check_installed "netstat" "sys-apps/net_tools" +check_installed "getopt" "sys-apps/util-linux" + # Parse the command line options autopilot=0 netmask=0 jail="apache-auth" +bancountry="CN" parse_command_line_args "$@" @@ -220,26 +392,6 @@ cut -d. -f1-3 "${fileraw}" | sort > "${file24}" cut -d. -f1-2 "${fileraw}" | sort > "${file16}" cut -d. -f1 "${fileraw}" | sort > "${file8}" -function filter() { - # list of current connections - file="$1" - # subnet extension, e.g. ".0.0/16" - ext="$2" - rm -f "${filtered}" - - # Reject already banned addresses - while read -r -u3 address ; do - if [[ "${banned}" != *"${address}${ext}"* ]] ; then - echo "Considering ${address}." - echo "${address}" >> "${filtered}" - else - echo "IGNORING ${address}, already banned." - fi - done 3< "${file}" - - mv "${filtered}" "${file}" -} - # Filter already banned addresses filter "${file32}" "${ext32}" filter "${file24}" "${ext24}" @@ -305,143 +457,8 @@ unset TEMP echo "Processing ${file}." -################################################################################ -# Set the highlighting color for the address count. The color goes from green -# (low count) over yellow (intermediate count) to red (high count). Depending -# on the size of the subnet (/8, /16, /24 or /32), the transitions from one -# color to the next happen at different values. -################################################################################ -function setHilite() { - local count=$1 - case "${choice}" in - "1" ) - # /32: 0 <= green < 3 <= yellow < 5 <= red - if [ $count -ge 5 ] ; then - hilite="${red}" - elif [ $count -ge 3 ] ; then - hilite="${yellow}" - else - hilite="${green}" - fi - ;; - "2" ) - # /24: 0 <= green < 7 <= yellow < 13 <= red - if [ $count -ge 13 ] ; then - hilite="${red}" - elif [ $count -ge 7 ] ; then - hilite="${yellow}" - else - hilite="${green}" - fi - ;; - "3" ) - # /16: 0 <= green < 13 <= yellow < 25 <= red - if [ $count -ge 25 ] ; then - hilite="${red}" - elif [ $count -ge 13 ] ; then - hilite="${yellow}" - else - hilite="${green}" - fi - ;; - "4" ) - # /8: 0 <= green < 21 <= yellow < 49 <= red - if [ $count -ge 49 ] ; then - hilite="${red}" - elif [ $count -ge 21 ] ; then - hilite="${yellow}" - else - hilite="${green}" - fi - ;; - * ) - # ???: We should never get here. As a fall-back, just use no - # highlighting. - hilite="" - ;; - esac -} - -################################################################################ -# Process the file denoted by $1. For each line in the file, the count and the -# address are displayed and a whois request is made. The user can then choose to -# ban or ignore the address. Addresses chosen to be banned are appended to the -# $banlist. -################################################################################ -function processFile () { - local file="${1}" - local line='' - local count=0 - local addr='' - local banaction='' - local nline=1 - local country_cn=1 - local source_apnic=1 - # Read the contents from filedescriptor 3 (important: Don's use the - # standard filedescriptor because we need to handle user input from - # within the loop). - while IFS= read -r -u3 line ; do - line="$(echo "${line}" | tr -s '[:blank:]')" - count="$(echo "${line}" | cut -d' ' -f2)" - addr="$(echo "${line}" | cut -d' ' -f3-)${ext}" - setHilite "${count}" - if [[ autopilot -eq 0 ]] ; then - whois "${addr}" | tee "${whoisoutput}" - else - whois "${addr}" > "${whoisoutput}" - fi - grep -iq "^country: *cn$" "${whoisoutput}" - country_cn=$? - grep -iq "^source: *apnic$" "${whoisoutput}" - source_apnic=$? - echo -en "Address ${bold}$((nline++)) of ${nlines}${reset}: \ -Found '${blue}${addr}${reset}' ${hilite}${count}${reset} times." - - if [[ ${autopilot} -eq 0 ]] ; then - echo -en "Ban [y/N/s=No, and skip remaining]? " - read banaction - else - if [[ ${country_cn} -eq 0 && ${source_apnic} -eq 0 ]] ; then - if [[ $count -ge $autopilot ]] ; then - echo -en "\n${red}Autopilot active. ${reset}" - banaction=y - else - echo -e "\n${yellow}Autopilot active. Ignoring remaining addresses due to limit of ${autopilot}.${reset}" - return - fi - else - if [[ $count -ge $autopilot ]] ; then - echo -en "\n${green}Autopilot active. ${reset}" - banaction=n - else - echo -e "\n${green}Autopilot active.${reset} ${yellow}Ignoring remaining addresses due to limit of ${autopilot}.${reset}" - return - fi - fi - fi - - case "${banaction}" in - "s" | "S" ) - echo -e "Not banning '${blue}${addr}${reset}', \ -skipping remaining addresses." - return - ;; - "y" | "Y" ) - echo -e "Adding '${blue}${addr}${reset}' to \ -banlist." - echo "${addr}" >> "${banlist}" - ;; - "n" | "N" | * ) - echo -e "Not banning '${blue}${addr}${reset}'." - ;; - esac - # Here goes: Pipe the file contents via filedescriptor 3. - done 3< "${file}" - echo "Processed all entries in ${file}." -} - # Invoke the processing function on the chosen file. -processFile "${file}" +process_file "${file}" echo "These are the addresses to be banned:" cat "${banlist}"