nginx-ultimate-bad-bot-blocker/setup-ngxblocker
2017-04-27 19:28:10 +00:00

329 lines
9.2 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
mkdir -p $BOTS_DIR
if [ -n "$(which dig)" ]; then
ip=$(dig +short myip.opendns.com @resolver1.opendns.com)
if ! grep "$ip" $conf &>/dev/null; then
printf "%-17s %-15s %-s\n" "Whitelisting ip:" "$ip" "=> $conf"
if [ "$DRY_RUN" = "N" ]; then
printf "%-23s %-s\n" "$ip" "0;" >> $conf
fi
fi
else
echo "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; then
printf "%-s %-$(( $domain_len +2))s %s\n" "Whitelisting domain:" "$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
}
add_includes() {
local ph='<<!!>>' line=$1 file=$2 conf_dir=$3 text= update=
local include_list="$(echo $@ | awk '{$1=$2=$3=""}sub("^"OFS"+","")')"
local col_size=$(( $(longest_str $include_list) + $(echo $conf_dir | wc -m) ))
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 +10 ))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)
sed -n ${start},${end}p ${file} > $tmp
line=$(find_line $tmp $search $search_first_last)
rm -f $tmp
# 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 -q "\--spider"; 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 2>&1 /dev/null; then
echo "true"
fi
}
main() {
local include_url= file= line= file_list=
local CONF_FILES= BOT_FILES=
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 "$BOT_FILES" ]; then
printf "Error sourcing variables from: $include_url\n"
exit 1
fi
# configure ddos include
case "$INC_DDOS" in
n*|N*) BOT_FILES=$(echo $BOT_FILES | sed 's|ddos.conf||');;
esac
# gather vhosts
file_list=$(find_vhosts)
check_config $file_list
# by default do not change any files
if [ -z "$DRY_RUN" ]; then
printf "\n** Dry Run ** | not updating files | -x or --exec to change files\n\n"
fi
# update vhosts
for file in $file_list; do
line=$(find_includes $file include last server_ last location first )
add_includes $line $file $BOTS_DIR $BOT_FILES
done
# update main config
line=$(find_includes $MAIN_CONF include last http first '\}' last )
add_includes $line $MAIN_CONF $CONF_DIR $CONF_FILES
whitelist_ips
if [ -d $WWW ]; then
whitelist_domains
else
echo "Web directory not found ('$WWW'): not whitelisting domains"
fi
}
## START ##
main $@
exit $?