mirror of
https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker.git
synced 2025-09-01 18:19:55 +00:00
removes --exec from DRY RUN messages as the scripts now use short command line switches only with getopts.
350 lines
10 KiB
Bash
Executable file
350 lines
10 KiB
Bash
Executable file
#!/bin/sh
|
|
|
|
### NGINX Bad Bot Blocker: setup script #################
|
|
### Copyright (C) 2017 Stuart Cardall ###
|
|
### https://github.com/itoffshore ###
|
|
### Licensed under the terms of the GPL2 ###
|
|
##########################################################
|
|
|
|
WWW=/var/www
|
|
VHOST_EXT="vhost"
|
|
VHOST_DIR=/etc/nginx/sites-available
|
|
BOTS_DIR=/etc/nginx/bots.d
|
|
CONF_DIR=/etc/nginx/conf.d
|
|
MAIN_CONF=/etc/nginx/nginx.conf
|
|
# setting Y / yes will whitelist only directories in $www
|
|
# that look like domain.names
|
|
DOT_NAMES="Y"
|
|
# if you already set 'limit_conn addr' you may want to set
|
|
# this to N / no.
|
|
INC_DDOS="Y"
|
|
|
|
####### end user configuration ###########################
|
|
|
|
usage() {
|
|
local script=$(basename $0)
|
|
cat <<EOF
|
|
$script: SETUP Nginx Bad Bot Blocker configuration in [ $MAIN_CONF ] [ $VHOST_DIR/* ]
|
|
|
|
Usage: $script [OPTIONS]
|
|
[ -w ] : WWW path (default: $WWW)
|
|
[ -e ] : Vhost file extension (default: .$VHOST_EXT)
|
|
[ -v ] : Vhost directory (default: $VHOST_DIR)
|
|
[ -b ] : Bot rules directory (default: $BOTS_DIR)
|
|
[ -c ] : NGINX conf directory (default: $CONF_DIR)
|
|
[ -m ] : NGINX main configuration (default: $MAIN_CONF)
|
|
[ -n ] : NO whitelist of .names only (default: $DOT_NAMES)
|
|
[ -d ] : NO insert of DDOS rule (default: $INC_DDOS)
|
|
[ -x ] : Actually change the files (default: don't change anything)
|
|
[ -h ] : this help message
|
|
|
|
Examples:
|
|
$script -n (Whitelist all directory names in $WWW as domains: not just dot.name directories)
|
|
$script -d (Do not insert DDOS rule: these may clash with existing 'limit_conn addr' rules)
|
|
$script (Don't change anything: display results on stdout)
|
|
$script -x (Change / update config files)
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
check_config() {
|
|
local files="$*"
|
|
|
|
if [ -z "$files" ]; then
|
|
echo "no vhost files in: $VHOST_DIR/*.$VHOST_EXT => exiting."
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$MAIN_CONF" ]; then
|
|
echo "NGINX main configuration ('$MAIN_CONF') not found => exiting."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
find_vhosts() {
|
|
find $VHOST_DIR -type f -name "*.$VHOST_EXT"
|
|
}
|
|
|
|
whitelist_ips() {
|
|
local ip= conf=$BOTS_DIR/whitelist-ips.conf col_size=$1
|
|
|
|
mkdir -p $BOTS_DIR
|
|
|
|
if [ -n "$(which dig)" ]; then
|
|
ip=$(dig +short myip.opendns.com @resolver1.opendns.com)
|
|
if ! grep "$ip" $conf >/dev/null 2>&1; then
|
|
printf "\n%-17s %-15s %-s\n" "Whitelisting ip:" "$ip" "=> $conf"
|
|
if [ "$DRY_RUN" = "N" ]; then
|
|
printf "%-23s %-s\n" "$ip" "0;" >> $conf
|
|
fi
|
|
fi
|
|
else
|
|
printf "%-10s %-${col_size}s %-s\n" \
|
|
"WARN:" "dig binary missing" "=> install bind-tools to whitelist external ip address"
|
|
fi
|
|
}
|
|
|
|
whitelist_domains() {
|
|
local domain_list= domain= domain_len=
|
|
local conf=$BOTS_DIR/whitelist-domains.conf
|
|
|
|
case "$DOT_NAMES" in
|
|
y*|Y*) domain_list=$(find $WWW -mindepth 1 -maxdepth 1 -type d -name '*\.*' -exec basename {} \;);;
|
|
*) domain_list=$(find $WWW -mindepth 1 -maxdepth 1 -type d -exec basename {} \;);;
|
|
esac
|
|
|
|
domain_len=$(find $WWW -mindepth 1 -maxdepth 1 -type d -exec basename {} \; \
|
|
| awk '{ print length ($0) }' | sort -nr | head -1)
|
|
|
|
for domain in $domain_list; do
|
|
if ! grep "$domain" $conf >/dev/null 2>&1; then
|
|
printf "%-s %-$(( $domain_len +2))s %s\n" "Whitelist:" "$domain" "=> $conf"
|
|
if [ "$DRY_RUN" = "N" ]; then
|
|
printf "%-$(( $domain_len +8))s %s\n" "\"~*$domain\"" "0;" >> $conf
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
longest_str() {
|
|
echo $@ | tr " " "\n" | awk '{print length ($0)}' | sort -nr | head -n1
|
|
}
|
|
|
|
check_wildcard() {
|
|
local file=$1 dir=$(basename $2)
|
|
local check="$(grep -E "include[[:alnum:] /]+$dir/\*" $file)"
|
|
echo $check
|
|
}
|
|
|
|
add_includes() {
|
|
local ph='<<!!>>' line=$1 file=$2 conf_dir=$3 col_size=$4 text= update=
|
|
local include_list="$(echo $@ | awk '{$1=$2=$3=$4=""}sub("^"OFS"+","")')"
|
|
|
|
for text in $include_list; do
|
|
if ! grep "$text" $file 1>/dev/null; then
|
|
update='true'
|
|
text="include $conf_dir/$text;"
|
|
printf "%-10s %-${col_size}s %s\n" "inserting:" "$text" "=> $file"
|
|
if [ "$DRY_RUN" = "N" ]; then
|
|
# $ph is just a placeholder so sed inserts a \t (tab)
|
|
sed -i "$line i $ph \t$text $ph" $file
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [ "$DRY_RUN" = "N" ]; then
|
|
if [ -n "$update" ]; then
|
|
#add blank line below inserts
|
|
line=$(( $line + $(echo $include_list | wc -w) ))
|
|
if ! sed -n "${line}p" $file | grep ^'}' 1>/dev/null; then
|
|
text="include $conf_dir/$(echo $include_list | awk '{print $1}');"
|
|
sed -i "s|$text|$text\n|" $file
|
|
fi
|
|
|
|
#add comment above inserts
|
|
text="include $conf_dir/$(echo $include_list | awk '{print $NF}');"
|
|
sed -i "s|$text|# Bad Bot Blocker\n\t$text|" $file
|
|
|
|
# remove placeholders
|
|
sed -i "s|$ph||g" $file
|
|
fi
|
|
fi
|
|
}
|
|
|
|
find_line() {
|
|
local file=$1 find_str=$2 first_last=$3
|
|
|
|
case "$first_last" in
|
|
first) awk "/$find_str/{ print NR; exit }" $file;;
|
|
last) awk "/$find_str/{ print NR }" $file | tail -n1;;
|
|
esac
|
|
}
|
|
|
|
find_includes() {
|
|
local file=$1 search=$2 search_first_last=$3 line= tmp=$(mktemp)
|
|
local start_range=$4 start_first_last=$5
|
|
local end_range=$6 end_first_last=$7
|
|
local start=$(find_line $file $start_range $start_first_last)
|
|
local end=$(find_line $file $end_range $end_first_last)
|
|
|
|
if [ -n "$start" ] && [ -n "$end" ]; then
|
|
sed -n "$start,$end"p $file > $tmp
|
|
line=$(find_line $tmp $search $search_first_last)
|
|
rm -f $tmp
|
|
fi
|
|
|
|
# search string not found
|
|
if [ -z "$line" ]; then
|
|
line=1
|
|
fi
|
|
|
|
case "$search_first_last" in
|
|
first) line=$(( $line + $start -1 ));;
|
|
last) line=$(( $line + $start +1 ));;
|
|
esac
|
|
|
|
# if inserting beyond the end of the stanza
|
|
if [ "$(sed -n $(( $line - 1))p $file | grep ^})" = "}" ]; then
|
|
# insert blank line
|
|
sed -i "$(( line - 1)) i \ " $file
|
|
fi
|
|
|
|
echo $line
|
|
}
|
|
|
|
sanitize_path() {
|
|
echo $1 |tr -cd '[:alnum:] [=@=] [=.=] [=-=] [=/=] [=_=]' \
|
|
|tr -s '@.-/_' |awk '{print tolower($0)}'
|
|
}
|
|
|
|
sanitize_ext() {
|
|
echo $1 |tr -cd '[:alnum:]' |awk '{print tolower($0)}'
|
|
}
|
|
|
|
check_args() {
|
|
local option=$1 type=$2 arg=$3
|
|
local msg="ERROR: option '-$option' argument '$arg' requires:"
|
|
|
|
case "$type" in
|
|
path) if ! echo $arg | grep ^/ 1>/dev/null; then
|
|
printf "$msg absolute path.\n"
|
|
exit 1
|
|
fi
|
|
;;
|
|
none) printf "$msg argument.\n"; exit 1;;
|
|
esac
|
|
}
|
|
|
|
get_options() {
|
|
local arg= opts=
|
|
|
|
while getopts :w:e:v:b:c:m:ndxh opts "$@"
|
|
do
|
|
if [ -n "${OPTARG}" ]; then
|
|
case "$opts" in
|
|
e) arg=$(sanitize_ext ${OPTARG});;
|
|
*) arg=$(sanitize_path ${OPTARG});;
|
|
esac
|
|
fi
|
|
|
|
case "$opts" in
|
|
w) WWW=$arg; check_args $opts path $arg ;;
|
|
e) VHOST_EXT=$arg;;
|
|
v) VHOST_DIR=$arg; check_args $opts path $arg ;;
|
|
b) BOTS_DIR=$arg; check_args $opts path $arg ;;
|
|
c) CONF_DIR=$arg; check_args $opts path $arg ;;
|
|
m) MAIN_CONF=$arg; check_args $opts path $arg ;;
|
|
n) DOT_NAMES=N ;;
|
|
d) INC_DDOS=N ;;
|
|
x) DRY_RUN=N ;;
|
|
h) usage ;;
|
|
\?) usage ;;
|
|
:) check_args $OPTARG none none ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
wget_opts() {
|
|
local opts=
|
|
|
|
# GNU wget / Busybox 1.26.2
|
|
if wget --help 2>&1 | grep "\--spider" >/dev/null 2>&1; then
|
|
opts="--spider"
|
|
else # Busybox wget < 1.26.2
|
|
opts="-s"
|
|
fi
|
|
|
|
echo $opts
|
|
}
|
|
|
|
check_online() {
|
|
local url=$1 options=$(wget_opts)
|
|
|
|
if wget $options $url >/dev/null 2>&1; then
|
|
echo "true"
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
local include_url= file= line= file_list= col_size=
|
|
local CONF_FILES= VHOST_INCLUDES=
|
|
local REPO=https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master
|
|
|
|
# require root
|
|
if [ "$(id -u)" != "0" ]; then
|
|
echo "This script must be run as root" 1>&2
|
|
exit 1
|
|
fi
|
|
|
|
# parse command line
|
|
get_options $@
|
|
include_url=$REPO/include_filelist.txt
|
|
|
|
# check repo is online & source includes
|
|
printf "Checking url: $include_url\n"
|
|
if [ -n "$(check_online $include_url)" ]; then
|
|
local tmp=$(mktemp)
|
|
wget -q $include_url -O $tmp
|
|
# use period not source in POSIX shell
|
|
. $tmp 2>/dev/null
|
|
rm -f $tmp
|
|
else
|
|
printf "Repo down or missing: $include_url\n"
|
|
exit 1
|
|
fi
|
|
|
|
# double check we have some files sourced
|
|
if [ -z "$CONF_FILES" ] || [ -z "$VHOST_INCLUDES" ]; then
|
|
printf "Error sourcing variables from: $include_url\n"
|
|
exit 1
|
|
fi
|
|
|
|
# configure ddos include
|
|
case "$INC_DDOS" in
|
|
n*|N*) VHOST_INCLUDES=$(echo $VHOST_INCLUDES | sed 's|ddos.conf||');;
|
|
esac
|
|
|
|
# gather vhosts
|
|
file_list=$(find_vhosts)
|
|
check_config $file_list
|
|
col_size=$(( $(longest_str $CONF_FILES) + $(echo $CONF_DIR | wc -m) + 10 ))
|
|
|
|
# by default do not change any files
|
|
if [ -z "$DRY_RUN" ]; then
|
|
printf "\n** Dry Run ** | not updating files | run as '$(basename $0) -x' to setup files.\n\n"
|
|
else
|
|
printf "\n"
|
|
fi
|
|
|
|
# update main config
|
|
line=$(find_includes $MAIN_CONF include last http first '\}' last )
|
|
if [ -n "$(check_wildcard $MAIN_CONF $CONF_DIR)" ]; then # also recalculate column width
|
|
col_size=$(( $(longest_str $VHOST_INCLUDES) + $(echo $BOTS_DIR | wc -m) + 10 ))
|
|
printf "%-10s %-${col_size}s %s\n" "INFO:" "$CONF_DIR/* detected" "=> $MAIN_CONF"
|
|
else # wildcard conf.d ok in nginx.conf
|
|
add_includes $line $MAIN_CONF $CONF_DIR $col_size $CONF_FILES
|
|
fi
|
|
|
|
# update vhosts
|
|
for file in $file_list; do
|
|
line=$(find_includes $file include last server_ last location first )
|
|
if [ -n "$(check_wildcard $file $BOTS_DIR)" ]; then
|
|
printf "%-10s %-${col_size}s %s\n" "WARN:" "$BOTS_DIR/* detected" "=> $file"
|
|
else # don't use wildcards in vhost files
|
|
add_includes $line $file $BOTS_DIR $col_size $VHOST_INCLUDES
|
|
fi
|
|
done
|
|
|
|
whitelist_ips $col_size
|
|
|
|
if [ -d $WWW ]; then
|
|
whitelist_domains
|
|
else
|
|
printf "\nWeb directory not found ('$WWW'): not whitelisting domains.\n"
|
|
fi
|
|
}
|
|
|
|
## START ##
|
|
main $@
|
|
exit $?
|
|
|