#!/bin/sh

# Shell Script for Installing the Nginx Bad Bot Blocker
# Copyright - https://github.com/mitchellkrogza
# Project Url: https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker
# Version 2.2017.07
# Install script & Alpine Linux package by Stuart Cardall: https://github.com/itoffshore

# PLEASE READ CONFIGURATION INSTRUCTIONS BEFORE USING THIS - THIS IS ONLY A PARTIAL INSTALLER
# FOR COPYING THE FILES CORRECTLY TO THE NGINX FOLDERS:
# https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/blob/master/CONFIGURATION.md

# Use this script for a new install or to update to a new release (which has a new configuration
# file structure) and thereafter use the Auto Update Shell Script update-ngxblocker to update
# the blacklist in /etc/nginx/conf.d/globalblacklist.conf

# THIS INSTALL SCRIPT ONLY COPIES THE NECESSARY FILES FOR NGINX DIRECTLY FROM THE REPO

### The installer script does not carry out STEP 6 of the configuration instructions for you.
### You must manually edit any vhost files with the includes in STEP 6 or it will not actually be protecting any sites.
### READ: https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/blob/master/CONFIGURATION.md

### You can also now use a setup script contributed by Stuart Cardall to automatically insert the includes for you
### See - https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/setup-ngxblocker

# Save this file as /usr/local/sbin/install-ngxblocker
# 	sudo wget https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/install-ngxblocker -O /usr/local/sbin/install-ngxblocker
# Make it Executable:
# 	chmod 700 /usr/local/sbin/install-ngxblocker
# Run it from the command line:
#	sudo /usr/local/sbin/install-ngxblocker [ -h ]

######## LETS INSTALL NOW ###############################

CONF_DIR=/etc/nginx/conf.d
BOTS_DIR=/etc/nginx/bots.d
SCRIPT_DIR=/usr/local/sbin
REPO=https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master

####### end user configuration ##########################
OS=$(uname -s)

usage() {
        local script=$(basename $0)
        cat <<EOF
$script: INSTALL Nginx Bad Bot Blocker configuration to: [ $CONF_DIR ] [ $BOTS_DIR ]

Usage: $script [OPTIONS]
        [ -b ] : Bot rules directory           (default: $BOTS_DIR)
        [ -c ] : NGINX conf directory          (default: $CONF_DIR)
        [ -s ] : Script directory              (default: $SCRIPT_DIR)
        [ -r ] : Change repo url               (default: $REPO)
        [ -x ] : Actually change the files     (default: don't change anything)
        [ -q ] : Suppress non error messages
        [ -v ] : Print blacklist version
        [ -h ] : this help message

Examples:
 $script       (Don't change anything: display results on stdout)
 $script -x    (Download / update config files)
 $script -q    (Less verbose messages for cron)
EOF
        exit 0
}

check_version() {
        local file=$CONF_DIR/globalblacklist.conf

        if [ -f $file ]; then
                grep Version $file
                grep 'Updated:' $file
        else
                printf "Missing '$file' (pass -c \$path before -v)\n"
        fi

        exit 0
}

longest_str() {
	echo $@ | tr " " "\n" | awk '{print length ($0)}' | sort -nr | head -n1
}

check_if_updating() {
	local x= local_file= local_dir=$1
	local file_list="$(echo $@ | awk '{$1=""; print}' | sed -e 's/^[ \t]*//')"

	for x in $file_list; do
		local_file=$local_dir/$x

		if [ ! -f $local_file ]; then
			echo "true"
			break
		fi
	done
}

download_files() {
	local url= x= local_file= remote_path= remote_dir=$1 local_dir=$2 tmp= retval=
	local file_list="$(echo $@ | awk '{$1=$2=""; print $0}' | sed -e 's/^[ \t]*//')" # rm leading whitespace
	local col_size=$(( $(longest_str $file_list) + $(echo $remote_dir | wc -m) ))

	if [ -n "$(check_if_updating $local_dir $file_list)" ]; then
		printf "\nREPO = $REPO\n\n"

		for x in $file_list; do
			local_file=$local_dir/$x

			if [ ! -f $local_file ] || [ ! -s $local_file ]; then
				if [ "$remote_dir" = "/" ]; then
					remote_path=$x
				else
					remote_path="$remote_dir/$x"
				fi

				if [ "$DRY_RUN" = "N" ]; then
					printf "%-21s %-$(( $col_size +8 ))s %s" \
					"Downloading [FROM]=>" \
					"[REPO]/$remote_path" \
					"[TO]=>  $local_file"

					tmp=$(mktemp)
					url=$REPO/$remote_path
					curl --fail --connect-timeout 60 --retry 10 --retry-delay 5 -so $tmp $url
					retval=$?

					case "$retval" in
						 0) printf "...OK\n"
						    mv $tmp $local_file
						    ;;
						22) printf "...ERROR 404: $url\n";;
						28) printf "...ERROR TIMEOUT: $url\n";;
						 *) printf "...ERROR CURL: ($retval)\n";;
					esac
				else
					printf "%-21s %-$(( $col_size +8 ))s %s\n" \
					"Downloading [FROM]=>" \
					"[REPO]/$remote_path" \
					"[TO]=>  $local_file"
				fi
			fi
		done
	else
		print_message "Nothing to update for directory: $local_dir\n"
	fi
}

set_mode() {
	local mode=$1 dir=$2 file=
	local file_list="$(echo $@ | awk '{$1=$2=""; print}' | sed -e 's/^[ \t]*//')"

	for file in $file_list; do
		print_message "Setting mode: $mode => $dir/$file\n"
		chmod $mode $dir/$file
	done
}

check_config() {
	local x= dirs="$*"

	for x in $dirs; do
		if [ ! -d $x ]; then
			printf "Creating directory: $x\n"
			if [ "$DRY_RUN" = "N" ]; then
				mkdir -p $x
			fi
		fi
	done
}

sanitize_path() {
        echo $1 |tr -cd '[:alnum:] [=@=] [=.=] [=-=] [=/=] [=_=]' \
                |tr -s '@.-/_' |awk '{print tolower($0)}'
}

sanitize_url() {
        echo $1 |tr -cd '[:alnum:] [=:=] [=.=] [=-=] [=/=]' \
                |tr -s ':.-' |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
                        ;;
                url)    if ! echo $arg | grep -E ^http[s]?://[0-9a-zA-Z-]+[.]+[/0-9a-zA-Z.]+ 1>/dev/null; then
                                printf "$msg url => http[s]://the.url\n"
                                exit 1
                        fi
                        ;;
                none)   printf "$msg argument.\n"; exit 1;;
        esac
}

print_message() {
        local msg="$@"

        if [ "$VERBOSE" != "N" ]; then
                printf "$msg"
        fi
}

get_options() {
        local arg= opts=

        while getopts :b:c:s:r:xvqh opts "$@"
        do
                if [ -n "${OPTARG}" ]; then
                        case "$opts" in
                                r) arg=$(sanitize_url ${OPTARG});;
                                *) arg=$(sanitize_path ${OPTARG});;
                        esac
                fi

                case "$opts" in
                        b) BOTS_DIR=$arg; check_args $opts path $arg ;;
                        c) CONF_DIR=$arg; check_args $opts path $arg ;;
                        s) SCRIPT_DIR=$arg; check_args $opts path $arg ;;
                        r) REPO=$arg; check_args $opts url $arg ;;
                        x) DRY_RUN=N ;;
                        v) check_version ;;
                        q) VERBOSE=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
}

find_binary() {
        local x= path= binary=$1 bin_paths='/bin /usr/bin /usr/local/bin /sbin /usr/sbin /usr/local/sbin /root/bin /root/.bin'

        for x in $bin_paths; do
                path="$x/$binary"

                if [ -x $path ]; then
                        echo $path
                        return
                fi
        done
}

check_depends() {
	# some distros do not have these tools installed by default
	local x= depends_list="wget curl"

	for x in $depends_list; do
		if [ -z $(find_binary $x) ]; then
			printf "$0 requires: '$x' \n"
			exit 1
		fi
	done

	case $OS in
		Linux)
			# give a helpful message for missing pidof
			if [ -z $(find_binary pidof) ]; then
				printf "$0 requires 'pidof' \n\n"
				printf "In Debian: apt install sysvinit-utils\n"
				printf "In Centos: yum install sysvinit-tools\n"
				exit 1
			fi
			;;
		*BSD)
			# give a helpful message for missing gsed
			if [ -z $(find_binary gsed) ]; then
				printf "$0 requires 'gsed' \n\n"
				printf "In FreeBSD: 'pkg install textproc/gsed' or 'portmaster textproc/gsed'\n"
				exit 1
			fi
			;;
	esac
}

check_online() {
	local url=$1 options=$(wget_opts)

	if wget $options $url >/dev/null 2>&1; then
		echo "true"
	fi
}

main() {
	local include_url=

	# require root
	if [ "$(id -u)" != "0" ]; then
		echo "This script must be run as root" 1>&2
		exit 1
	fi

	check_depends

	# parse command line
	get_options $@
	include_url=$REPO/include_filelist.txt

	# check repo is online & source includes
	print_message "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" ] || [ -z "$SCRIPT_FILES" ]; then
		printf "Error sourcing variables from: $include_url\n"
		exit 1
	fi

	# 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 install files.\n\n"
	else
		printf "\n"
	fi

	check_config $CONF_DIR $BOTS_DIR $SCRIPT_DIR
	download_files conf.d $CONF_DIR $CONF_FILES
	download_files bots.d $BOTS_DIR $BOT_FILES
	download_files / $SCRIPT_DIR $SCRIPT_FILES

	# ensures scripts are executable
	if [ "$DRY_RUN" = "N" ]; then
		set_mode 700 $SCRIPT_DIR $SCRIPT_FILES
	fi
}

## START ##
main $@
exit $?

# PLEASE READ CONFIGURATION INSTRUCTIONS
# https://github.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/blob/master/CONFIGURATION.md

# PLEASE ALSO SEE THE SETUP SCRIPT TO INSERT THE NECESSARY INCLUDES FOR YOU
### You can now use a setup script contributed by Stuart Cardall to automatically add the includes for you
### See - https://raw.githubusercontent.com/mitchellkrogza/nginx-ultimate-bad-bot-blocker/master/setup-ngxblocker
