Compare commits
No commits in common. "master" and "v2.1.0" have entirely different histories.
4 changed files with 229 additions and 422 deletions
|
@ -1,5 +0,0 @@
|
||||||
root = true
|
|
||||||
|
|
||||||
[*.sh]
|
|
||||||
indent_size = 4
|
|
||||||
indent_style = tab
|
|
|
@ -1,30 +0,0 @@
|
||||||
# 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"
|
|
|
@ -1,17 +1,21 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
################################################################################
|
||||||
|
################################################################################
|
||||||
|
########### FIXME: This text is outdated and needs to be rewritten. ###########
|
||||||
|
################################################################################
|
||||||
|
################################################################################
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# #
|
# #
|
||||||
# Try and prevent apache overloads by banning IP addresses that have (too) #
|
# Try and prevent apache overloads by banning IP addresses that have (too) #
|
||||||
# many open connections. #
|
# many open connections. #
|
||||||
# This script uses ss to determine the connections to a configurable port #
|
# This script uses netstat to determine the connections to the HTTPS port of #
|
||||||
# on the host machine and provides automated GeoIP information retrieval based #
|
# the host machine and provides automated whois information retrieval based on #
|
||||||
# the address or the /24-, /16- or /8-subnet thereof. A GeoIP city- or country #
|
# the address or the /24-, /16- or /8-subnet thereof. Addresses (or subnets) #
|
||||||
# database must be installed separately and is provided to the script via a #
|
# are presented to the user in order of descending connection count. For each #
|
||||||
# command line parameter. #
|
# address (or subnet), the user can choose to ban or ignore it. Addresses (or #
|
||||||
# Addresses (or subnets) are presented to the user in order of descending #
|
# subnets) chosen to be banned will be blocked by the apache-badbots jail of #
|
||||||
# connection count. For each address (or subnet), the user can choose to ban #
|
# fail2ban. #
|
||||||
# or ignore it. Addresses (or subnets) chosen to be banned will be blocked by #
|
|
||||||
# a configurable jail of fail2ban. #
|
|
||||||
# Author: Manuel Friedli, <manuel@fritteli.ch> #
|
# Author: Manuel Friedli, <manuel@fritteli.ch> #
|
||||||
# This script is licenced under the GNU General Public Licence, version 3 or #
|
# This script is licenced under the GNU General Public Licence, version 3 or #
|
||||||
# later. #
|
# later. #
|
||||||
|
@ -26,32 +30,17 @@
|
||||||
# - net-analyzer/fail2ban (`fail2ban-client`) #
|
# - net-analyzer/fail2ban (`fail2ban-client`) #
|
||||||
# - sys-apps/coreutils (`cut`, `id`, `sort`, `touch`, `tr`, `uniq`) #
|
# - sys-apps/coreutils (`cut`, `id`, `sort`, `touch`, `tr`, `uniq`) #
|
||||||
# - sys-apps/grep (`grep`) #
|
# - sys-apps/grep (`grep`) #
|
||||||
# - sys-apps/iproute2 (`ss`) #
|
|
||||||
# - sys-apps/moreutils (`sponge`) #
|
# - sys-apps/moreutils (`sponge`) #
|
||||||
|
# - sys-apps/net_tools (`netstat`) #
|
||||||
# - sys-apps/util-linux (`getopt`) #
|
# - sys-apps/util-linux (`getopt`) #
|
||||||
# #
|
# #
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
# Store the start time; this enables us to output the total runtime at the end.
|
# Set the host's own IP address. So far, only an IPv4 address is supported.
|
||||||
start="$(date +%s)"
|
MY_IP="94.199.214.20"
|
||||||
|
|
||||||
# Dependencies of this script. Simple array with the following structure:
|
# After this point, no editing is required.
|
||||||
# (command package [...])
|
start=$(date +%s)
|
||||||
dependencies=(
|
|
||||||
"sudo" "app-admin/sudo"
|
|
||||||
"python" "dev-lang/python:3.8"
|
|
||||||
"fail2ban-client" "net-analyzer/fail2ban"
|
|
||||||
"cut" "sys-apps/coreutils"
|
|
||||||
"id" "sys-apps/coreutils"
|
|
||||||
"sort" "sys-apps/coreutils"
|
|
||||||
"touch" "sys-apps/coreutils"
|
|
||||||
"tr" "sys-apps/coreutils"
|
|
||||||
"uniq" "sys-apps/coreutils"
|
|
||||||
"grep" "sys-apps/grep"
|
|
||||||
"ss" "sys-apps/iproute2"
|
|
||||||
"sponge" "sys-apps/moreutils"
|
|
||||||
"getopt" "sys-apps/util-linux"
|
|
||||||
)
|
|
||||||
|
|
||||||
# These suffixes must be appended to the respective addresses and subnets.
|
# These suffixes must be appended to the respective addresses and subnets.
|
||||||
suffix8="/8"
|
suffix8="/8"
|
||||||
|
@ -72,40 +61,18 @@ bold="$(printf '\033[1m')"
|
||||||
reset="$(printf '\033[0m')"
|
reset="$(printf '\033[0m')"
|
||||||
|
|
||||||
# Clean up when the script exits.
|
# Clean up when the script exits.
|
||||||
trap 'sudo -k 2>/dev/null >&2; rm -r ${tmpdir}' EXIT
|
trap 'sudo -k; rm -r ${tmpdir}' EXIT
|
||||||
|
|
||||||
function is_installed() {
|
function check_installed() {
|
||||||
which "${1}" 2>/dev/null >&2
|
local command="$1"
|
||||||
return $?
|
local package="$2"
|
||||||
}
|
which "${command}" 2>/dev/null >&2
|
||||||
|
local result=$?
|
||||||
|
|
||||||
function print_missing_dependency() {
|
if [[ "${result}" -ne 0 ]] ; then
|
||||||
local command="${1}"
|
echo "${red}Command ${bold}${command}${reset}${red} not found.${reset} Please install package ${blue}${package}${reset}."
|
||||||
local package="${2}"
|
exit 1
|
||||||
|
fi
|
||||||
echo "${red}Command ${bold}${command}${reset}${red} not found.${reset} Please install package ${blue}${package}${reset}." >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
function check_dependencies() {
|
|
||||||
local arraylength=${#dependencies[@]}
|
|
||||||
local res=
|
|
||||||
local command=
|
|
||||||
local package=
|
|
||||||
# 0: true, all installed; 1: false, at least one command/package missing
|
|
||||||
local all_installed=0
|
|
||||||
|
|
||||||
for ((i = 0; i < ${arraylength}; i += 2)); do
|
|
||||||
command="${dependencies[$i]}"
|
|
||||||
package="${dependencies[$i + 1]}"
|
|
||||||
is_installed "${command}" "${package}"
|
|
||||||
res=$?
|
|
||||||
if [[ ${res} -ne 0 ]]; then
|
|
||||||
print_missing_dependency "${command}" "${package}"
|
|
||||||
all_installed=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
return ${all_installed}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function print_help() {
|
function print_help() {
|
||||||
|
@ -129,20 +96,6 @@ Usage: $(basename $0) -d FILE [OPTION...]
|
||||||
comma-separated values; defaults to 'CN'
|
comma-separated values; defaults to 'CN'
|
||||||
(China).
|
(China).
|
||||||
|
|
||||||
-e, --dependencies Check if all required dependencies are
|
|
||||||
installed. If all dependencies are found,
|
|
||||||
the program terminates with exit code 0.
|
|
||||||
Otherwise, missing dependencies are
|
|
||||||
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
|
-j, --jail=JAIL Specify the JAIL to use for banning the IP
|
||||||
addresses.
|
addresses.
|
||||||
Defaults to 'apache-auth'.
|
Defaults to 'apache-auth'.
|
||||||
|
@ -169,7 +122,7 @@ ENDOFHELP
|
||||||
}
|
}
|
||||||
|
|
||||||
function exec_as_root() {
|
function exec_as_root() {
|
||||||
if [[ $(id -un) == "root" ]]; then
|
if [[ $(id -un) == "root" ]] ; then
|
||||||
"$@"
|
"$@"
|
||||||
else
|
else
|
||||||
sudo "$@"
|
sudo "$@"
|
||||||
|
@ -178,70 +131,30 @@ function exec_as_root() {
|
||||||
|
|
||||||
function filter() {
|
function filter() {
|
||||||
# list of current connections
|
# list of current connections
|
||||||
file="${1}"
|
file="$1"
|
||||||
# subnet extension, e.g. ".0.0"
|
# subnet extension, e.g. ".0.0"
|
||||||
ext="${2}"
|
ext="$2"
|
||||||
# subnet suffix, e.g. "/16"
|
# subnet suffix, e.g. "/16"
|
||||||
suffix="${3}"
|
suffix="$3"
|
||||||
rm -f "${filtered}"
|
rm -f "${filtered}"
|
||||||
touch "${filtered}"
|
touch "${filtered}"
|
||||||
|
|
||||||
# Reject already banned addresses
|
# Reject already banned addresses
|
||||||
while read -r -u3 address; do
|
while read -r -u3 address ; do
|
||||||
if [[ "${banned}" != *"${address}${ext}${suffix}"* ]]; then
|
if [[ "${banned}" != *"${address}${ext}${suffix}"* ]] ; then
|
||||||
echo "${address}" >>"${filtered}"
|
echo "${address}" >> "${filtered}"
|
||||||
else
|
else
|
||||||
echo "IGNORING ${address}${ext}${suffix}, already banned."
|
echo "IGNORING ${address}${ext}${suffix}, already banned."
|
||||||
fi
|
fi
|
||||||
done 3<"${file}"
|
done 3< "${file}"
|
||||||
|
|
||||||
mv "${filtered}" "${file}"
|
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() {
|
function parse_command_line_args() {
|
||||||
TEMP=$(getopt -o 'a::,c:,d:,e,f:,j:,n:,p:,h' -l 'auto::,country:,database:,dependencies,config-file:,jail:,netmask:,port:,help' -- "$@")
|
TEMP=$(getopt -o 'a::,c:,d:,j:,n:,p:,h' -l 'auto::,country:,database:,jail:,netmask:,port:,help' -- "$@")
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ] ; then
|
||||||
echo 'Error parsing command line options. Terminating. Invoke with --help for help.' >&2
|
echo 'Error parsing command line options. Terminating. Invoke with --help for help.' >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
@ -249,152 +162,87 @@ function parse_command_line_args() {
|
||||||
eval set -- "${TEMP}"
|
eval set -- "${TEMP}"
|
||||||
unset TEMP
|
unset TEMP
|
||||||
|
|
||||||
while true; do
|
while true ; do
|
||||||
case "${1}" in
|
case "$1" in
|
||||||
'-a' | '--auto')
|
'-a'|'--auto')
|
||||||
case "${2}" in
|
case "$2" in
|
||||||
'')
|
'')
|
||||||
autopilot=1
|
autopilot=1
|
||||||
;;
|
;;
|
||||||
|
*[!0-9]*)
|
||||||
|
echo "Invalid argument for parameter 'auto': '$2'. Invoke with --help for help." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
autopilot="$2"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
'-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')
|
||||||
|
database="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
'-j'|'--jail')
|
||||||
|
jail="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
'-n'|'--netmask')
|
||||||
|
case "$2" in
|
||||||
|
'1'|'8')
|
||||||
|
netmask=8
|
||||||
|
;;
|
||||||
|
'2'|'16')
|
||||||
|
netmask=16
|
||||||
|
;;
|
||||||
|
'3'|'24')
|
||||||
|
netmask=24
|
||||||
|
;;
|
||||||
|
'4'|'32')
|
||||||
|
netmask=32
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid argument for parameter 'netmask': '$2'. Invoke with --help for help." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
'-p'|'--port')
|
||||||
|
port="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
'-h'|'--help')
|
||||||
|
print_help
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
'--')
|
||||||
|
shift
|
||||||
|
break
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
autopilot="$2"
|
echo "Unknown error on command line argument '$1'. Terminating." >&2
|
||||||
;;
|
exit 1
|
||||||
esac
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
'-c' | '--country')
|
|
||||||
IFS=',' read -ra bancountries <<<"${2}"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
'-d' | '--database')
|
|
||||||
database="${2}"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
'-e' | '--dependencies')
|
|
||||||
check_dependencies
|
|
||||||
exit $?
|
|
||||||
;;
|
|
||||||
'-f' | '--config-file')
|
|
||||||
configfile="${2}"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
'-j' | '--jail')
|
|
||||||
jail="${2}"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
'-n' | '--netmask')
|
|
||||||
case "${2}" in
|
|
||||||
'1')
|
|
||||||
netmask=8
|
|
||||||
;;
|
|
||||||
'2')
|
|
||||||
netmask=16
|
|
||||||
;;
|
|
||||||
'3')
|
|
||||||
netmask=24
|
|
||||||
;;
|
|
||||||
'4')
|
|
||||||
netmask=32
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
netmask="${2}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
'-p' | '--port')
|
|
||||||
port="${2}"
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
'-h' | '--help')
|
|
||||||
print_help
|
|
||||||
exit
|
|
||||||
;;
|
|
||||||
'--')
|
|
||||||
shift
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown error on command line argument '${1}'. Terminating." >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
# If the config file option is set, parse the config file.
|
if [[ -z "${database}" ]] ; then
|
||||||
if [[ ! -z ${configfile+x} ]]; then
|
echo "No GeoIP database specified. Invoke with --help for more information." >&2
|
||||||
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f "${database}" || ! -r "${database}" ]]; then
|
if [[ ! -r "${database}" ]] ; then
|
||||||
echo "Database '${database}' is not accessible." >&2
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -406,52 +254,52 @@ function validate_parameter_values() {
|
||||||
# color to the next happen at different values.
|
# color to the next happen at different values.
|
||||||
################################################################################
|
################################################################################
|
||||||
function set_highlight_color() {
|
function set_highlight_color() {
|
||||||
local count=${1}
|
local count=$1
|
||||||
case "${choice}" in
|
case "${choice}" in
|
||||||
"1")
|
"1" )
|
||||||
# /32: 0 <= green < 3 <= yellow < 5 <= red
|
# /32: 0 <= green < 3 <= yellow < 5 <= red
|
||||||
if [ ${count} -ge 5 ]; then
|
if [ $count -ge 5 ] ; then
|
||||||
hilite="${red}"
|
hilite="${red}"
|
||||||
elif [ ${count} -ge 3 ]; then
|
elif [ $count -ge 3 ] ; then
|
||||||
hilite="${yellow}"
|
hilite="${yellow}"
|
||||||
else
|
else
|
||||||
hilite="${green}"
|
hilite="${green}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"2")
|
"2" )
|
||||||
# /24: 0 <= green < 7 <= yellow < 13 <= red
|
# /24: 0 <= green < 7 <= yellow < 13 <= red
|
||||||
if [ ${count} -ge 13 ]; then
|
if [ $count -ge 13 ] ; then
|
||||||
hilite="${red}"
|
hilite="${red}"
|
||||||
elif [ ${count} -ge 7 ]; then
|
elif [ $count -ge 7 ] ; then
|
||||||
hilite="${yellow}"
|
hilite="${yellow}"
|
||||||
else
|
else
|
||||||
hilite="${green}"
|
hilite="${green}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"3")
|
"3" )
|
||||||
# /16: 0 <= green < 13 <= yellow < 25 <= red
|
# /16: 0 <= green < 13 <= yellow < 25 <= red
|
||||||
if [ ${count} -ge 25 ]; then
|
if [ $count -ge 25 ] ; then
|
||||||
hilite="${red}"
|
hilite="${red}"
|
||||||
elif [ ${count} -ge 13 ]; then
|
elif [ $count -ge 13 ] ; then
|
||||||
hilite="${yellow}"
|
hilite="${yellow}"
|
||||||
else
|
else
|
||||||
hilite="${green}"
|
hilite="${green}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"4")
|
"4" )
|
||||||
# /8: 0 <= green < 21 <= yellow < 49 <= red
|
# /8: 0 <= green < 21 <= yellow < 49 <= red
|
||||||
if [ ${count} -ge 49 ]; then
|
if [ $count -ge 49 ] ; then
|
||||||
hilite="${red}"
|
hilite="${red}"
|
||||||
elif [ ${count} -ge 21 ]; then
|
elif [ $count -ge 21 ] ; then
|
||||||
hilite="${yellow}"
|
hilite="${yellow}"
|
||||||
else
|
else
|
||||||
hilite="${green}"
|
hilite="${green}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
* )
|
||||||
# ???: We should never get here. As a fall-back, just use no
|
# ???: We should never get here. As a fall-back, just use no
|
||||||
# highlighting.
|
# highlighting.
|
||||||
hilite=""
|
hilite=""
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
@ -462,7 +310,7 @@ function set_highlight_color() {
|
||||||
# GeoIP database. The user can then choose to ban or ignore the address.
|
# GeoIP database. The user can then choose to ban or ignore the address.
|
||||||
# Addresses chosen to be banned are appended to the $banlist.
|
# Addresses chosen to be banned are appended to the $banlist.
|
||||||
################################################################################
|
################################################################################
|
||||||
function process_file() {
|
function process_file () {
|
||||||
local file="${1}"
|
local file="${1}"
|
||||||
local line=''
|
local line=''
|
||||||
local count=0
|
local count=0
|
||||||
|
@ -474,25 +322,25 @@ function process_file() {
|
||||||
# Read the contents from filedescriptor 3 (important: Don's use the
|
# Read the contents from filedescriptor 3 (important: Don's use the
|
||||||
# standard filedescriptor because we need to handle user input from
|
# standard filedescriptor because we need to handle user input from
|
||||||
# within the loop).
|
# within the loop).
|
||||||
while IFS= read -r -u3 line; do
|
while IFS= read -r -u3 line ; do
|
||||||
line="$(echo "${line}" | tr -s '[:blank:]')"
|
line="$(echo "${line}" | tr -s '[:blank:]')"
|
||||||
count="$(echo "${line}" | cut -d' ' -f2)"
|
count="$(echo "${line}" | cut -d' ' -f2)"
|
||||||
addronly="$(echo "${line}" | cut -d' ' -f3-)${ext}"
|
addronly="$(echo "${line}" | cut -d' ' -f3-)${ext}"
|
||||||
addrwithsuffix="${addronly}${suffix}"
|
addrwithsuffix="${addronly}${suffix}"
|
||||||
set_highlight_color "${count}"
|
set_highlight_color "${count}"
|
||||||
country="$("${curdir}/geoip-lookup.py" -f "${database}" "${addronly}")"
|
country="$("${curdir}/geoip-lookup.py" -f "${database}" "${addronly}")"
|
||||||
if [[ ${autopilot} -eq 0 ]]; then
|
if [[ autopilot -eq 0 ]] ; then
|
||||||
echo "Country: '${yellow}${country}${reset}'"
|
echo "Country: '${yellow}${country}${reset}'"
|
||||||
fi
|
fi
|
||||||
echo -n "Address ${bold}$((nline++)) of ${nlines}${reset}: \
|
echo -n "Address ${bold}$((nline++)) of ${nlines}${reset}: \
|
||||||
Found '${blue}${addrwithsuffix}${reset}' ${hilite}${count}${reset} times."
|
Found '${blue}${addrwithsuffix}${reset}' ${hilite}${count}${reset} times."
|
||||||
|
|
||||||
if [[ ${autopilot} -eq 0 ]]; then
|
if [[ ${autopilot} -eq 0 ]] ; then
|
||||||
echo -n " Ban [y/N/s=No, and skip remaining]? "
|
echo -n " Ban [y/N/s=No, and skip remaining]? "
|
||||||
read banaction
|
read banaction
|
||||||
else
|
else
|
||||||
if [[ " ${bancountries[@]} " =~ " ${country} " ]]; then
|
if [[ " ${bancountries[@]} " =~ " ${country} " ]] ; then
|
||||||
if [[ ${count} -ge ${autopilot} ]]; then
|
if [[ $count -ge $autopilot ]] ; then
|
||||||
echo -en "\n${red}Autopilot active. ${reset}"
|
echo -en "\n${red}Autopilot active. ${reset}"
|
||||||
banaction=y
|
banaction=y
|
||||||
else
|
else
|
||||||
|
@ -500,7 +348,7 @@ Found '${blue}${addrwithsuffix}${reset}' ${hilite}${count}${reset} times."
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ ${count} -ge ${autopilot} ]]; then
|
if [[ $count -ge $autopilot ]] ; then
|
||||||
echo -en "\n${green}Autopilot active. ${reset}"
|
echo -en "\n${green}Autopilot active. ${reset}"
|
||||||
banaction=n
|
banaction=n
|
||||||
else
|
else
|
||||||
|
@ -511,28 +359,28 @@ Found '${blue}${addrwithsuffix}${reset}' ${hilite}${count}${reset} times."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case "${banaction}" in
|
case "${banaction}" in
|
||||||
"s" | "S")
|
"s" | "S" )
|
||||||
echo "Not banning '${blue}${addrwithsuffix}${reset}', \
|
echo "Not banning '${blue}${addrwithsuffix}${reset}', \
|
||||||
skipping remaining addresses."
|
skipping remaining addresses."
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
"y" | "Y")
|
"y" | "Y" )
|
||||||
echo "Adding '${blue}${addrwithsuffix}${reset}' to \
|
echo "Adding '${blue}${addrwithsuffix}${reset}' to \
|
||||||
banlist (country=${yellow}${country}${reset})."
|
banlist (country=${yellow}${country}${reset})."
|
||||||
echo "${addrwithsuffix}" >>"${banlist}"
|
echo "${addrwithsuffix}" >> "${banlist}"
|
||||||
;;
|
;;
|
||||||
"n" | "N" | *)
|
"n" | "N" | * )
|
||||||
echo "Not banning '${blue}${addrwithsuffix}${reset}' (country=${yellow}${country}${reset})."
|
echo "Not banning '${blue}${addrwithsuffix}${reset}' (country=${yellow}${country}${reset})."
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
# Here goes: Pipe the file contents via filedescriptor 3.
|
# Here goes: Pipe the file contents via filedescriptor 3.
|
||||||
done 3<"${file}"
|
done 3< "${file}"
|
||||||
echo "Processed all entries in ${file}."
|
echo "Processed all entries in ${file}."
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a temp directory, chdir into it and create the (initially empty)
|
# Create a temp directory, chdir into it and create the (initially empty)
|
||||||
# banlist file.
|
# banlist file.
|
||||||
tmpdir="$(mktemp -d)"
|
tmpdir=$(mktemp -d)
|
||||||
|
|
||||||
# Set up all file paths
|
# Set up all file paths
|
||||||
curdir="$(dirname "$0")"
|
curdir="$(dirname "$0")"
|
||||||
|
@ -545,43 +393,48 @@ file24="${tmpdir}/sorted-http-24.txt"
|
||||||
file32="${tmpdir}/sorted-http-32.txt"
|
file32="${tmpdir}/sorted-http-32.txt"
|
||||||
# This file will contain the addresses to be banned.
|
# This file will contain the addresses to be banned.
|
||||||
banlist="${tmpdir}/banlist.txt"
|
banlist="${tmpdir}/banlist.txt"
|
||||||
|
# This file contains the output of the last invocation of whois
|
||||||
|
whoisoutput="${tmpdir}/whois.txt"
|
||||||
|
|
||||||
touch "${banlist}"
|
touch "${banlist}"
|
||||||
|
|
||||||
# Parse the command line options
|
# Parse the command line options
|
||||||
autopilot=
|
autopilot=0
|
||||||
netmask=
|
netmask=0
|
||||||
jail=
|
jail="apache-auth"
|
||||||
bancountries=
|
bancountries=("CN")
|
||||||
database=
|
database=
|
||||||
port=
|
port=443
|
||||||
|
|
||||||
parse_command_line_args "$@"
|
parse_command_line_args "$@"
|
||||||
validate_parameter_values
|
|
||||||
|
|
||||||
check_dependencies
|
check_installed "sudo" "app-admin/sudo"
|
||||||
dependencies_ok=$?
|
check_installed "python" "dev-lang/python:3.8"
|
||||||
if [[ ${dependencies_ok} -ne 0 ]]; then
|
check_installed "fail2ban-client" "net-analyzer/fail2ban"
|
||||||
exit ${dependencies_ok}
|
check_installed "cut" "sys-apps/coreutils"
|
||||||
fi
|
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"
|
||||||
|
|
||||||
# List already banned addresses in the chosen jail
|
# List already banned addresses in the chosen jail
|
||||||
banned="$(exec_as_root fail2ban-client get "${jail}" banip)"
|
banned="$(exec_as_root fail2ban-client get "${jail}" banip)"
|
||||||
|
|
||||||
# Determine the current connections to the desired port; store the raw data in
|
# Determine the current connections to the desired port; store the raw data in
|
||||||
# $fileraw.
|
# $fileraw.
|
||||||
connections=$(ss -HOn state established "( sport = :${port} )" | tr -s '[:blank:]' | cut -d' ' -f5)
|
netstat -nt | grep "${MY_IP}:${port}" | tr -s '[:blank:]' | cut -d' ' -f5 \
|
||||||
|
| cut -d: -f1 | sort > "${fileraw}"
|
||||||
# IPv6-mapped-IPv4: [::ffff:192.168.0.1]:443
|
|
||||||
echo "${connections}" | grep '^\[::ffff:' - | cut -d: -f4 | cut -d] -f1 | grep -v '^$' >"${fileraw}"
|
|
||||||
# Pure IPv4: 192.168.0.1:443
|
|
||||||
echo "${connections}" | grep -v '^\[' - | cut -d: -f1 | grep -v '^$' >>"${fileraw}"
|
|
||||||
|
|
||||||
# Group and sort the data into the subnet-specific files.
|
# Group and sort the data into the subnet-specific files.
|
||||||
sort "${fileraw}" >"${file32}"
|
cp "${fileraw}" "${file32}"
|
||||||
cut -d. -f1-3 "${fileraw}" | sort >"${file24}"
|
cut -d. -f1-3 "${fileraw}" | sort > "${file24}"
|
||||||
cut -d. -f1-2 "${fileraw}" | sort >"${file16}"
|
cut -d. -f1-2 "${fileraw}" | sort > "${file16}"
|
||||||
cut -d. -f1 "${fileraw}" | sort >"${file8}"
|
cut -d. -f1 "${fileraw}" | sort > "${file8}"
|
||||||
|
|
||||||
# Filter already banned addresses
|
# Filter already banned addresses
|
||||||
filter "${file32}" "${ext32}" "${suffix32}"
|
filter "${file32}" "${ext32}" "${suffix32}"
|
||||||
|
@ -601,7 +454,7 @@ nlines24=$(cat "${file24}" | wc -l)
|
||||||
nlines16=$(cat "${file16}" | wc -l)
|
nlines16=$(cat "${file16}" | wc -l)
|
||||||
nlines8=$(cat "${file8}" | wc -l)
|
nlines8=$(cat "${file8}" | wc -l)
|
||||||
|
|
||||||
if [ ${netmask} -eq 0 ]; then
|
if [ ${netmask} -eq 0 ] ; then
|
||||||
# Now let the user choose which file to process.
|
# Now let the user choose which file to process.
|
||||||
echo "We've got:"
|
echo "We've got:"
|
||||||
echo "[1] 8bit: ${nlines8} entries"
|
echo "[1] 8bit: ${nlines8} entries"
|
||||||
|
@ -614,25 +467,25 @@ if [ ${netmask} -eq 0 ]; then
|
||||||
# $nlines, which will be used after this point. Also, $choice will be
|
# $nlines, which will be used after this point. Also, $choice will be
|
||||||
# used to color the output based on subnet-type.
|
# used to color the output based on subnet-type.
|
||||||
case "${choice}" in
|
case "${choice}" in
|
||||||
"1")
|
"1" )
|
||||||
netmask=8
|
netmask=8
|
||||||
;;
|
;;
|
||||||
"2")
|
"2" )
|
||||||
netmask=16
|
netmask=16
|
||||||
;;
|
;;
|
||||||
"3")
|
"3" )
|
||||||
netmask=24
|
netmask=24
|
||||||
;;
|
;;
|
||||||
"4")
|
"4" )
|
||||||
netmask=32
|
netmask=32
|
||||||
;;
|
;;
|
||||||
"Q" | "q")
|
"Q" | "q" )
|
||||||
echo "You chose to abort. That's fine! Have a nice day!"
|
echo "You chose to abort. That's fine! Have a nice day!"
|
||||||
exit
|
exit
|
||||||
;;
|
;;
|
||||||
*)
|
* )
|
||||||
echo "Invalid input: ${choice}. I'm out of here."
|
echo "Invalid input: ${choice}. I'm out of here."
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
@ -662,11 +515,11 @@ sudo -k
|
||||||
|
|
||||||
# Iterate over all addresses in $banlist and invoke fail2ban-client on each
|
# Iterate over all addresses in $banlist and invoke fail2ban-client on each
|
||||||
# one of them.
|
# one of them.
|
||||||
while read -r addrwithsuffix; do
|
while read -r addrwithsuffix ; do
|
||||||
echo "Banning ${addrwithsuffix} ..."
|
echo "Banning ${addrwithsuffix} ..."
|
||||||
exec_as_root fail2ban-client set "${jail}" banip "${addrwithsuffix}"
|
exec_as_root fail2ban-client set "${jail}" banip "${addrwithsuffix}"
|
||||||
done <"${banlist}"
|
done < "${banlist}"
|
||||||
|
|
||||||
end="$(date +%s)"
|
end=$(date +%s)
|
||||||
|
|
||||||
echo "${green}All done in $((end - start)) seconds!${reset}"
|
echo "${green}All done in $((end - start)) seconds!${reset}"
|
||||||
|
|
|
@ -1,24 +1,16 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python3.8
|
||||||
|
|
||||||
import argparse
|
import getopt
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import geoip2.database
|
import geoip2.database
|
||||||
import geoip2.errors
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print(
|
print(
|
||||||
"Required modules geoip2.database and geoip2.errors not found. On Gentoo Linux, please install dev-python/geoip2 from the 'fritteli' overlay.",
|
"Required module geoip2.database not found. On Gentoo Linux, please install dev-python/geoip2 from the 'fritteli' overlay.",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
try:
|
|
||||||
import maxminddb.errors
|
|
||||||
except ImportError:
|
|
||||||
print(
|
|
||||||
"Required module maxminddb.errors not found. On Gentoo Linux, please install dev-python/maxminddb from the 'fritteli' overlay.",
|
|
||||||
file=sys.stderr)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
class LookupException(Exception):
|
class LookupException(Exception):
|
||||||
"""
|
"""
|
||||||
|
@ -55,14 +47,6 @@ def get_county_code(ipaddress, dbfile):
|
||||||
raise LookupException("Unsupported DB type: " + dbtype)
|
raise LookupException("Unsupported DB type: " + dbtype)
|
||||||
|
|
||||||
return country.iso_code
|
return country.iso_code
|
||||||
except FileNotFoundError as e:
|
|
||||||
raise LookupException(e.args)
|
|
||||||
except maxminddb.errors.InvalidDatabaseError as e:
|
|
||||||
raise LookupException(e.args)
|
|
||||||
except geoip2.errors.AddressNotFoundError as e:
|
|
||||||
raise LookupException(e.args)
|
|
||||||
except ValueError as e:
|
|
||||||
raise LookupException(e.args)
|
|
||||||
finally:
|
finally:
|
||||||
if reader:
|
if reader:
|
||||||
reader.close()
|
reader.close()
|
||||||
|
@ -76,12 +60,21 @@ def parse_command_line(argv):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
dbfile = None
|
dbfile = None
|
||||||
parser = argparse.ArgumentParser(description='Get the country code from an IP address')
|
try:
|
||||||
parser.add_argument('-f', dest='dbfile', required=True, help="Path to the GeoIP2 database file")
|
opts, args = getopt.getopt(argv, "f:")
|
||||||
parser.add_argument('address', help="The IP address to check")
|
except getopt.GetoptError as e:
|
||||||
args = parser.parse_args()
|
raise LookupException("Error parsing command line") from e
|
||||||
|
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt == "-f":
|
||||||
|
dbfile = arg
|
||||||
|
else:
|
||||||
|
raise LookupException("Unknown command line argument")
|
||||||
|
|
||||||
|
if len(args) == 0:
|
||||||
|
raise LookupException("No address given on command line")
|
||||||
|
return dbfile, args[0]
|
||||||
|
|
||||||
return args.dbfile, args.address
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
"""
|
"""
|
||||||
|
@ -90,18 +83,14 @@ def main(argv):
|
||||||
:param argv: Format: "-f /path/to/database.mmdb ip.v4.add.ress"
|
:param argv: Format: "-f /path/to/database.mmdb ip.v4.add.ress"
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
try:
|
(dbfile, ipaddress) = parse_command_line(argv)
|
||||||
(dbfile, ipaddress) = parse_command_line(argv)
|
code = get_county_code(ipaddress, dbfile)
|
||||||
code = get_county_code(ipaddress, dbfile)
|
print(code)
|
||||||
print(code)
|
|
||||||
except LookupException as e:
|
|
||||||
print(e.args, file=sys.stderr)
|
|
||||||
print("Unknown")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
main(sys.argv[1:])
|
main(sys.argv[1:])
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
print("Unknown")
|
print("Usage: geoip-lookup.py -f /path/to/geoip2-database.mmdb 192.168.1.1", file=sys.stderr)
|
||||||
raise e
|
raise e
|
||||||
|
|
Loading…
Reference in a new issue