#!/bin/sh
# pfBlockerNG IP Reputation Script - By BBcan177@gmail.com - 04-12-14
# Copyright (c) 2015-2016 BBcan177@gmail.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License Version 2 as
# published by the Free Software Foundation.  You may not use, modify or
# distribute this program under any other version of the GNU General
# Public License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

pfs_version=$(/bin/cat /etc/version | /usr/bin/cut -c 1-3)
if [ "${pfs_version}" = '2.2' ]; then
	mtype=$(/usr/bin/uname -m)
	prefix="/usr/pbi/pfblockerng-${mtype}"
else
	prefix='/usr/local'
fi

now=$(/bin/date +%m/%d/%y' '%T)

# Application Locations
pathgrepcidr="${prefix}/bin/grepcidr"
pathaggregate="${prefix}/bin/aggregate"
pathmwhois="${prefix}/bin/mwhois"
pathgeoip="${prefix}/bin/geoiplookup"
pathgunzip=/usr/bin/gunzip
pathhost=/usr/bin/host
pathtar=/usr/bin/tar
pathpfctl=/sbin/pfctl

# Script Arguments
alias="${2}"
max="${3}"
dedup="${4}"
cc="$(echo ${5} | sed 's/,/, /g')"
ccwhite="$(echo ${6} | tr '[A-Z]' '[a-z]')"
ccblack="$(echo ${7} | tr '[A-Z]' '[a-z]')"
etblock="$(echo ${8} | sed 's/,/, /g')"
etmatch="$(echo ${9} | sed 's/,/, /g')"

# File Locations
aliasarchive="${prefix}/etc/aliastables.tar.bz2"
pathgeoipdat="${prefix}/share/GeoIP/GeoIP.dat"
pfbsuppression=/var/db/pfblockerng/pfbsuppression.txt
pfbalexa=/var/db/pfblockerng/pfbalexawhitelist.txt
masterfile=/var/db/pfblockerng/masterfile
mastercat=/var/db/pfblockerng/mastercat
geoiplog=/var/log/pfblockerng/geoip.log
errorlog=/var/log/pfblockerng/error.log
domainmaster=/tmp/domainmaster

# Folder Locations
etdir=/var/db/pfblockerng/ET
tmpxlsx=/tmp/xlsx/

pfbdb=/var/db/pfblockerng/
pfbdeny=/var/db/pfblockerng/deny/
pfborig=/var/db/pfblockerng/original/
pfbmatch=/var/db/pfblockerng/match/
pfbpermit=/var/db/pfblockerng/permit/
pfbnative=/var/db/pfblockerng/native/
pfsensealias=/var/db/aliastables/
pfbdomain=/var/db/pfblockerng/dnsbl/

# Store 'Match' d-dedups in matchdedup.txt file
matchdedup=matchdedup.txt

# Randomize temporary variables
rvar="$(/usr/bin/jot -r 1 1000 100000)"

tempfile=/tmp/pfbtemp1_$rvar
tempfile2=/tmp/pfbtemp2_$rvar
dupfile=/tmp/pfbtemp3_$rvar
dedupfile=/tmp/pfbtemp4_$rvar
addfile=/tmp/pfbtemp5_$rvar
syncfile=/tmp/pfbtemp6_$rvar
matchfile=/tmp/pfbtemp7_$rvar
tempmatchfile=/tmp/pfbtemp8_$rvar

PLATFORM="$(cat /etc/platform)"
USE_MFS_TMPVAR="$(/usr/bin/grep -c use_mfs_tmpvar /cf/conf/config.xml)"
DISK_NAME="$(/bin/df /var/db/rrd | /usr/bin/tail -1 | /usr/bin/awk '{print $1;}')"
DISK_TYPE="$(/usr/bin/basename ${DISK_NAME} | /usr/bin/cut -c1-2)"

if [ "${PLATFORM}" != 'pfSense' ] || [ ${USE_MFS_TMPVAR} -gt 0 ] || [ "${DISK_TYPE}" = 'md' ]; then
	/etc/rc.conf_mount_rw > /dev/null 2>&1
fi

if [ ! -d "${pfbdb}" ]; then mkdir "${pfbdb}"; fi
if [ ! -d "${pfsensealias}" ]; then mkdir "${pfsensealias}"; fi
if [ ! -d "${pfbmatch}" ]; then mkdir "${pfbmatch}"; fi
if [ ! -d "${etdir}" ]; then mkdir "${etdir}"; fi
if [ ! -d "${tmpxlsx}" ]; then mkdir "${tmpxlsx}"; fi

if [ ! -f "${masterfile}" ]; then touch "${masterfile}"; fi
if [ ! -f "${mastercat}" ]; then touch "${mastercat}"; fi


# Exit function to set mount RO if required before exiting.
exitnow() {
	if [ "${PLATFORM}" != 'pfSense' ] || [ ${USE_MFS_TMPVAR} -gt 0 ] || [ "${DISK_TYPE}" = 'md' ]; then
		/etc/rc.conf_mount_ro > /dev/null 2>&1
	fi

	# Remove temp files
	rm -f /tmp/pfbtemp?_"${rvar}"
	exit
}


# Function to restore aliasables from archive on reboot. ( NanoBSD and Ramdisk installations only )
aliastables() {
	if [ "${PLATFORM}" != 'pfSense' ] || [ ${USE_MFS_TMPVAR} -gt 0 ] || [ "${DISK_TYPE}" = 'md' ]; then
		[ -f "${aliasarchive}" ] && cd "${pfsensealias}" && /usr/bin/tar -jxvf "${aliasarchive}"
	fi
}


# Function to write '1.1.1.1' to 'empty' final blocklist files.
emptyfiles() {
	emptyfiles="$(find ${pfbdeny}*.txt -size 0 2>/dev/null)"
	for i in ${emptyfiles}; do
		echo '1.1.1.1' > "${i}";
	done
}


# Function to remove lists from masterfiles and delete associated files.
remove() {
	echo
	for i in ${cc}; do
		header="$(echo ${i%*,})"
		if [ ! -z "${header}" ]; then
			# Make sure that alias exists in masterfile before removal.
			masterchk="$(grep -m1 '${header}[[:space:]]' ${masterfile})"

			if [ ! -z "${masterchk}" ]; then
				# Grep header with a trailing space character
				grep "${header}[[:space:]]" "${masterfile}" > "${tempfile}"
				awk 'FNR==NR{a[$0];next}!($0 in a)' "${tempfile}" "${masterfile}" > "${tempfile2}"; mv -f "${tempfile2}" "${masterfile}"
			fi

			rm -f "${pfborig}${header}"*; rm -f "${pfbdeny}${header}"*; rm -f "${pfbmatch}${header}"*;
			rm -f "${pfbpermit}${header}"*; rm -f "${pfbnative}${header}"*
			echo "The Following list has been REMOVED [ ${header} ]"
		fi
	done
	cut -d ' ' -f2 "${masterfile}" > "${mastercat}"

	# Delete masterfiles if they are empty
	if [ ! -s "${masterfile}" ]; then
		rm -f "${masterfile}"; rm -f "${mastercat}"
	fi
}


# Function to remove IPs if exists over 253 IPs in a range and replace with a single /24 block. (excl. '0' & '255')
process255() {
	> "${dedupfile}"
	data255="$(cut -d '.' -f 1-3 ${pfbdeny}${alias}.txt | awk '{a[$0]++}END{for(i in a){if(a[i] > 253){print i}}}')"

	if [ ! -z "${data255}" ]; then
		cp "${pfbdeny}${alias}.txt" "${tempfile}"

		for ip in ${data255}; do
			ii="$(echo ^${ip}. | sed 's/\./\\\./g')"
			grep "${ii}" "${tempfile}" >> "${dedupfile}"
		done

		awk 'FNR==NR{a[$0];next}!($0 in a)' "${dedupfile}" "${tempfile}" > "${pfbdeny}${alias}.txt"
		for ip in ${data255}; do echo "${ip}.0/24" >> "${pfbdeny}${alias}.txt"; done
	fi
}

# Process to remove suppressed entries.
suppress() {
	if [ ! -x "${pathgrepcidr}" ]; then
		log="Application [ grepcidr ] Not found. Cannot proceed."
		echo "${log}" | tee -a "${errorlog}"
		exitnow
	fi

	if [ -e "${pfbsuppression}" ] && [ -s "${pfbsuppression}" ]; then
		data="$(cat ${pfbsuppression} | sort | uniq)"

		if [ ! -z "${data}" ] && [ ! -z "${cc}" ]; then
			if [ "${cc}" == 'suppressheader' ]; then
				echo; echo '===[ Suppression Stats ]==================================='; echo
				printf "%-20s %-10s %-10s %-10s\n" 'List' 'Pre' 'Suppress' 'Master'
				echo '-----------------------------------------------------------'
				exitnow
			fi

			alias="$(echo ${cc%|*})"
			pfbfolder="$(echo ${cc#*|})"
			counter=0; > "${dupfile}"

			if [ ! -z "${alias}" ]; then
				countg="$(grep -c ^ ${pfbfolder}${alias}.txt)"
				cp "${pfbfolder}${alias}.txt" "${tempfile}"

				for ip in ${data}; do
					found=''; dcheck='';
					mask="$(echo ${ip##*/})"
					iptrim="$(echo "${ip%.*}")"
					ip="$(echo ${ip%%/*})"
					found="$(grep -m1 ${iptrim}.0/24 ${tempfile})"

					# If a suppression is '/32' and a blocklist has a full '/24' block, execute the following.
					if [ ! -z "${found}" ] && [ "${mask}" -eq 32 ]; then
						echo " Suppression ${alias}: ${iptrim}.0/24"
						octet4="$(echo ${ip##*.})"
						dcheck="$(grep ${iptrim}.0/24 ${dupfile})"

						if [ -z "${dcheck}" ]; then
							echo "${iptrim}.0/24" >> "${dupfile}"
							counter="$((counter + 1))"

							# Add individual IP addresses from range excluding suppressed IP
							for i in $(/usr/bin/jot 255); do
								if [ "${i}" != "${octet4}" ]; then
									echo "${iptrim}.${i}" >> "${tempfile}"
									counter="$((counter + 1))"
								fi
							done
						fi
					fi
				done

				if [ -s "${dupfile}" ]; then
					# Remove '/24' suppressed ranges
					awk 'FNR==NR{a[$0];next}!($0 in a)' "${dupfile}" "${tempfile}" > "${tempfile2}"; mv -f "${tempfile2}" "${tempfile}"
				fi

				# Remove all other suppressions from list
				"${pathgrepcidr}" -vf "${pfbsuppression}" "${tempfile}" > "${pfbfolder}${alias}.txt"

				# Update masterfiles. Don't execute if duplication process is disabled
				if [ "${dedup}" == 'x' ]; then
					# Don't execute if alias doesn't exist in masterfile
					lcheck="$(grep -m1 ${alias} ${masterfile})"

					if [ ! -z "${lcheck}" ]; then
						# Replace masterfile with changes to list.
						grep "${alias}[[:space:]]" "${masterfile}" > "${tempfile}"
						awk 'FNR==NR{a[$0];next}!($0 in a)' "${tempfile}" "${masterfile}" > "${tempfile2}"
						mv -f "${tempfile2}" "${masterfile}"
						sed -e 's/^/'$alias' /' "${pfbfolder}${alias}.txt" >> "${masterfile}"
						cut -d ' ' -f2 "${masterfile}" > "${mastercat}"
					fi
				fi

				countk="$(grep -c ^ ${masterfile})"
				countx="$(grep -c ^ ${pfbfolder}${alias}.txt)"
				counto="$((countx - counter))"
				printf "%-20s %-10s %-10s %-10s\n" "${alias}" "${countg}" "${counto}" "${countk}"
			fi
		fi
	fi
}


# Function to optimise CIDRs
cidr_aggregate() {
	if [ ! -x "${pathaggregate}" ]; then
		log="Application [ aggregate ] Not found. Cannot proceed."
		echo "${log}" | tee -a "${errorlog}"
		return
	fi

	if [ "${agg_folder}" = true ]; then
		# Use $3 folder path
		pfbfolder="${max}/"
	else
		pfbfolder="${pfbdeny}"
	fi

	counto="$(grep -c ^ ${pfbfolder}${alias}.txt)"
	retval="$(cat "${pfbfolder}${alias}.txt" | "${pathaggregate}" -t -p 32 -m 32 -o 32  2>&1 > ${tempfile})"
	sed 's/\/32//' "${tempfile}" > "${pfbfolder}${alias}.txt"
	countf="$(grep -c ^ ${pfbfolder}${alias}.txt)"

	# Report errors (First two lines are informational only)
	aggstring='aggregate: maximum prefix length permitted will be 32aggregate: prefix length of 32 bits will be used where none specified'
	retval2=$(echo "${retval}" | tr -d '\n\r' | sed "s/${aggstring}//g")
	if [ ! -z "${retval2}" ]; then
		echo "${retval2}"
	fi

	if [ "${counto}" != "${countf}" ]; then
		echo; echo '  Aggregation Stats:'
		echo '  ------------------'
		printf "%-10s %-10s \n" '  Original' 'Final'
		echo '  ------------------'
		printf "%-10s %-10s \n" "  ${counto}" "${countf}"
		echo '  ------------------'
	fi
}


# Function to remove duplicate entries in each list individually.
duplicate() {
	if [ ! -x "${pathgrepcidr}" ]; then
		log="Application [ grepcidr ] Not found. Cannot proceed."
		echo "${log}" | tee -a "${errorlog}"
		exitnow
	fi

	dupcheck=1
	# Check if masterfile is empty
	hcheck="$(grep -cv ^$ ${masterfile})"; if [ "${hcheck}" -eq 0 ]; then dupcheck=0; fi
	# Check if alias exists in masterfile
	lcheck="$(grep -m1 ${alias} ${masterfile})"; if [ -z "${lcheck}" ]; then dupcheck=0; fi
	# Check for single alias in masterfile
	aliaslist="$(cut -d ' ' -f1 ${masterfile} | sort | uniq)"; if [ "${alias}" == "${aliaslist}" ]; then hcheck=0; fi

	# Only execute if 'Alias' exists in masterfile
	if [ "${dupcheck}" -eq 1 ]; then
		# Grep alias with a trailing space character
		grep "${alias}[[:space:]]" "${masterfile}" > "${tempfile}"
		awk 'FNR==NR{a[$0];next}!($0 in a)' "${tempfile}" "${masterfile}" > "${tempfile2}"; mv -f "${tempfile2}" "${masterfile}"
		cut -d ' ' -f2 "${masterfile}" > "${mastercat}"
	fi

	# Don't execute when only a single 'Alias' exists in masterfile
	if [ ! "${hcheck}" -eq 0 ]; then
		sort "${pfbdeny}${alias}.txt" | uniq > "${tempfile}"; mv -f "${tempfile}" "${pfbdeny}${alias}.txt"
		"${pathgrepcidr}" -vf "${mastercat}" "${pfbdeny}${alias}.txt" > "${tempfile}"; mv -f "${tempfile}" "${pfbdeny}${alias}.txt"
	fi

	sed -e 's/^/'$alias' /' "${pfbdeny}${alias}.txt" >> "${masterfile}"
	cut -d ' ' -f2 "${masterfile}" > "${mastercat}"

	counto="$(grep -cv '^#\|^$' ${pfborig}${alias}.orig)"
	countm="$(grep -c ${alias} ${masterfile})"
	countf="$(grep -c ^ ${pfbdeny}${alias}.txt)"

	if [ "${countm}" -eq "${countf}" ]; then
		sanity='Pass'
	else
		sanity=' ==> FAILED <== '
	fi

	echo '  ------------------------------'
	printf "%-10s %-10s %-10s\n" '  Original' 'Master' 'Final'
	echo '  ------------------------------'
	printf "%-10s %-10s %-10s %-10s\n" "  ${counto}" "${countm}" "${countf}" " [ ${sanity} ]"
	echo '  -----------------------------------------------------------------'
}


# Function to remove duplicate DNSBL domain names from feeds.
domainduplicate() {
	# Alexa Whitelist variables
	alexa_enable="${max}"

	counto="$(grep -c ^ ${pfbdomain}${alias}.bk)"
	if [ -d "${pfbdomain}" ] && [ "$(ls -A ${pfbdomain}*.txt 2>/dev/null)" ]; then
		sort "${pfbdomain}${alias}.bk" | uniq > "${pfbdomain}${alias}.bk2"
		countu="$(grep -c ^ ${pfbdomain}${alias}.bk2)"
		find "${pfbdomain}"*.txt ! -name "${alias}.txt" | xargs cat > "${domainmaster}"

		# Only execute awk command, if master domain file contains data.
		counta="$(grep -c ^ ${domainmaster})"
		if [ "${counta}" -gt 0 ]; then
			awk 'FNR==NR{a[$0];next}!($0 in a)' "${domainmaster}" "${pfbdomain}${alias}.bk2" > "${pfbdomain}${alias}.bk"
		fi

		rm -f "${domainmaster}"; rm -f "${pfbdomain}${alias}.bk2"
		countf="$(grep -c ^ ${pfbdomain}${alias}.bk)"
		countd="$((countu - countf))"
	else
		sort "${pfbdomain}${alias}.bk" | uniq > "${pfbdomain}${alias}.bk2" && mv -f "${pfbdomain}${alias}.bk2" "${pfbdomain}${alias}.bk"
		countf="$(grep -c ^ ${pfbdomain}${alias}.bk)"
		countd=0; countu="${counto}"
	fi

	if [ "${alexa_enable}" == 'on' ]; then
		awk 'FNR==NR{a[$0];next}!($0 in a)' "${pfbalexa}" "${pfbdomain}${alias}.bk" > "${pfbdomain}${alias}.bk2"
		countw="$(grep -c ^ ${pfbdomain}${alias}.bk2)"
		counta="$((countf - countw))"

		if [ "${counta}" -gt 0 ]; then
			data="$(awk 'FNR==NR{a[$0];next}!($0 in a)' ${pfbdomain}${alias}.bk2 ${pfbdomain}${alias}.bk | \
				cut -d '"' -f2 | cut -d ' ' -f1 | sort | uniq | tr '\n' '|')"
			echo; echo; echo "  Alexa Whitelist: ${data}"
			mv -f "${pfbdomain}${alias}.bk2" "${pfbdomain}${alias}.bk"
			countf="$((countw))"
		else
			rm -f "${pfbdomain}${alias}.bk2"
		fi
	else
		counta='-'
	fi

	echo; echo '  ------------------------------------------------'
	printf "%-10s %-10s %-10s %-10s %-10s\n" '  Original' 'Unique' '# Dups' 'Alexa' 'Final'
	echo '  ------------------------------------------------'
	printf "%-10s %-10s %-10s %-10s %-10s\n" "  ${counto}" "${countu}" "${countd}" "${counta}" "${countf}"
	echo '  ------------------------------------------------'
}


# Function to convert Domains/ASs to its respective IP addresses
whoisconvert() {
	if [ ! -x "${pathmwhois}" ]; then
		log="Application [ mwhois ] Not found. Cannot proceed."
		echo "${log}" | tee -a "${errorlog}"
		exitnow
	fi

	vtype="${max}"
	custom_list="$(echo ${dedup} | tr ',' ' ')"
	rm -f "${pfborig}${alias}.orig"

	if [ "${vtype}" == '_v4' ]; then
		_type=A
		_route=route
		_opt=gAS
	else
		_type=AAAA
		_route=route6
		_opt=6AS
	fi

	for host in ${custom_list}; do
		# Determine if host is a Domain or an AS
		host_check="$(echo ${host} | grep '\.')"
		if [ ! -z "${host_check}" ]; then
			echo "### Domain: ${host} ###" >> "${pfborig}${alias}.orig"
			${pathhost} -t ${_type} ${host} | sed 's/^.* //' >> "${pfborig}${alias}.orig"
		else
			asn="$(echo ${host} | tr -d 'AaSs')"
			echo "### AS${asn}: ${host} ###" >> "${pfborig}${alias}.orig"
			"${pathmwhois}" -h whois.radb.net \!"${_opt}${asn}" | tail -n +2 | tr -d '\nC' | tr ' ' '\n' >> "${pfborig}${alias}.orig"
		fi

		echo >> "${pfborig}${alias}.orig"
	done
}


# Function to check for Reputation application dependencies.
reputation_depends() {
	if [ ! -x "${pathgeoip}" ]; then
		log="Application [ GeoIP ] Not found, cannot proceed. [ ${now} ]"
		echo "${log}" | tee -a "${errorlog}"
		return
	fi

	# Download MaxMind GeoIP.dat on first install.
	if [ ! -f "${pathgeoipdat}" ]; then
		echo "Downloading [ MaxMind GeoIP.dat ] [ ${now} ]" >> "${geoiplog}"
		/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php bu
	fi

	# Exit if GeoIP.dat is not found
	if [ ! -f "${pathgeoipdat}" ]; then
		log="Database GeoIP [ GeoIP.Dat ] not found. Reputation function terminated."
		echo "${log}" | tee -a "${errorlog}"
		return
	fi

	# Clear variables and tempfiles
	rm -f /tmp/pfbtemp?_"${rvar}"
	count=0; countb=0; countm=0; counts=0; countr=0
}


# Reputation function to condense an IP range if a 'Max' amount of IP addresses are found in a /24 range per individual list.
reputation_max() {
	sort "${pfbdeny}${alias}.txt" | uniq > "${tempfile}"
	data="$(cut -d '.' -f 1-3 ${tempfile} | awk -v max=${max} '{a[$0]++}END{for(i in a){if(a[i] > max){print i}}}')"

	# Classify repeat offenders by Country code
	if [ ! -z "${data}" ]; then
		for ip in ${data}; do
			ccheck="$(${pathgeoip} -f ${pathgeoipdat} ${ip}.1 | cut -c 24-25)"
			case "${cc}" in
				*$ccheck*)
					countr="$((countr + 1))"
					if [ "${ccwhite}" == 'match' ] || [ "${ccblack}" == 'match' ]; then
						echo "${ip}." >> "${matchfile}"
					fi
					;;
				*)
					count="$((count + 1))"
					echo "${ip}." >> "${dupfile}"
					;;
			esac
		done
	else
		countr=0; count=0
	fi

	# Collect match file details
	if [ -s "${matchfile}" ] && [ "${dedup}" != 'on' ] && [ "${ccwhite}" == 'match' ]; then
		mon="$(sed -e 's/^/^/' -e 's/\./\\\./g' ${matchfile})"
		for ip in ${mon}; do
			grep "${ip}" "${tempfile}" >> "${tempfile2}"
		done
		counts="$(grep -c ^ ${tempfile2})"
		if [ "${ccwhite}" == 'match' ]; then
			sed 's/$/0\/24/' "${matchfile}" >> "${tempmatchfile}"
			sed 's/^/\!/' "${tempfile2}" >> "${tempmatchfile}"
		fi
	fi

	# If no matches found remove previous matchoutfile if exists.
	matchoutfile="match${header}.txt"
	if [ ! -s "${tempmatchfile}" ] && [ -f "${matchoutfile}" ]; then rm -r "${matchoutfile}"; fi
	# Move match file to the match folder by individual blocklist name
	if [ -s "${tempmatchfile}" ]; then mv -f "${tempmatchfile}" "${pfbmatch}${matchoutfile}"; fi

	# Find repeat offenders in each individual blocklist outfile
	if [ -s "${dupfile}" ]; then
		> "${tempfile2}"
		dup="$(sed -e 's/^/^/' -e 's/\./\\\./g' ${dupfile})"
		for ip in ${dup}; do
			grep "${ip}" "${tempfile}" >> "${tempfile2}"
		done
		countb="$(grep -c ^ ${tempfile2})"

		if [ "${ccblack}" == 'block' ]; then
			awk 'FNR==NR{a[$0];next}!($0 in a)' "${tempfile2}" "${tempfile}" > "${pfbdeny}${alias}.txt"
			sed 's/$/0\/24/' "${dupfile}" >> "${pfbdeny}${alias}.txt"
		elif [ "${ccblack}" == 'match' ]; then
			sed 's/$/0\/24/' "${dupfile}" >> "${tempmatchfile}"
			sed 's/^/\!/' "${tempfile2}" >> "${tempmatchfile}"
		else
			:
		fi
	fi

	if [ "${count}" -gt 0 ]; then
		echo; echo "  Reputation (Max=${max}) - Range(s)"
		cat "${dupfile}" | tr '\n' '|'; echo
		sort "${pfbdeny}${alias}.txt" | uniq > "${tempfile}"; mv -f "${tempfile}" "${pfbdeny}${alias}.txt"
	fi

	if [ "${count}" -gt 0 ] || [ "${countr}" -gt 0 ]; then
		echo; echo '  Reputation -Max Stats'
		echo '  ------------------------------'
		printf "%-17s %-10s\n" '  Blacklisted' 'Match'
		printf "%-8s %-8s %-8s %-8s\n" '  Ranges' 'IPs' 'Ranges' 'IPs'
		echo '  ------------------------------'
		printf "%-8s %-8s %-8s %-8s\n" "  ${count}" "${countb}" "${countr}" "${counts}"
		echo
	fi
}


# Reputation function 'dMax' utilizing MaxMind GeoIP Country code.
reputation_dmax() {
	echo; echo '===[ Reputation - dMax ]======================================'
	echo; echo "  Querying for repeat offenders ( dMax=${max} ) [ ${now} ]"
	data="$(find ${pfbdeny}*.txt ! -name pfB*.txt ! -name *_v6.txt -type f | xargs cut -d '.' -f 1-3 | \
		awk -v max=${max} '{a[$0]++}END{for(i in a){if(a[i] > max){print i}}}' | grep -v '^1\.1\.1')"

	# Classify repeat offenders by Country code
	if [ ! -z "${data}" ]; then
		echo '  Classifying repeat offenders by GeoIP'
		for ip in ${data}; do
			ccheck="$(${pathgeoip} -f ${pathgeoipdat} ${ip}.1 | cut -c 24-25)"
			case "${cc}" in
				*$ccheck*)
					countr="$((countr + 1))"
					if [ "${ccwhite}" == 'match' ] || [ "${ccblack}" == 'match' ]; then
						echo "${ip}." >> "${matchfile}"
					fi
					;;
				*)
					count="$((count + 1))"
					echo "${ip}." >> "${dupfile}"
					;;
			esac
		done
	else
		countr=0; count=0
	fi

	if [ "${ccwhite}" == 'match' ] && [ -s "${matchfile}" ]; then
		echo '  Processing [ Match ] IPs'
		match="$(sed -e 's/^/^/' -e 's/\./\\\./g' ${matchfile})"

		for mfile in ${match}; do
			grep "${mfile}" "${pfbdeny}"*.txt >> "${tempfile}"
		done

		sed 's/$/0\/24/' "${matchfile}" >> "${tempmatchfile}"
		sed -e 's/.*://' -e 's/^/\!/' "${tempfile}" >> "${tempmatchfile}"
		mv -f "${tempmatchfile}" "${pfbmatch}${matchdedup}"
		countm="$(grep -c ^ ${tempfile})"
		counts="$((countm + counts))"
	fi

	# Find repeat offenders in each individual blocklist outfile
	if [ "${count}" -gt 0 ]; then
		echo '  Processing [ Block ] IPs'
		dup="$(cat ${dupfile})"

		for ip in ${dup}; do
			runonce=0; ii="$(echo ^${ip} | sed 's/\./\\\./g')"
			list="$(find ${pfbdeny}*.txt ! -name pfB*.txt ! -name *_v6.txt -type f | xargs grep -al ${ii})"

			for blfile in ${list}; do
				header="$(echo ${blfile##*/} | cut -d '.' -f1)"
				grep "${ii}" "${blfile}" > "${tempfile}"

				if [ "${ccblack}" == 'block' ]; then
					awk 'FNR==NR{a[$0];next}!($0 in a)' "${tempfile}" "${blfile}" > "${tempfile2}"; mv -f "${tempfile2}" "${blfile}"
					if [ "${runonce}" -eq 0 ]; then
						echo "${ip}0/24" >> "${blfile}"
						echo "${header}" "${ip}" >> "${dedupfile}"
						echo "${header}" "${ip}0/24" >> "${addfile}"
						runonce=1
					else
						echo "${header}" "${ip}" >> "${dedupfile}"
					fi
				else
					if [ "${runonce}" -eq 0 ]; then
						matchoutfile="match${header}.txt"
						echo "${ip}0/24" >> "${pfbmatch}${matchoutfile}"
						sed 's/^/\!/' "${tempfile}" >> "${pfbmatch}${matchoutfile}"
						countm="$(grep -c ^ ${pfbmatch}${matchoutfile})"
						counts="$((countm + counts))"
						runonce=1
					fi
				fi
			done
		done

		# Remove repeat offenders in masterfiles
		echo '  Removing   [ Block ] IPs'
		> "${tempfile}"; > "${tempfile2}"
		sed 's/\./\\\./g' "${dedupfile}" > "${tempfile2}"
		while IFS=' ' read -r ips; do grep "${ips}" "${masterfile}" >> "${tempfile}"; done < "${tempfile2}"
		countb="$(grep -c ^ ${tempfile})"
		awk 'FNR==NR{a[$0];next}!($0 in a)' "${tempfile}" "${masterfile}" > "${tempfile2}"; mv -f "${tempfile2}" "${masterfile}"
		cat "${addfile}" >> "${masterfile}"
		cut -d ' ' -f2 "${masterfile}" > "${mastercat}"

		echo; echo '  Removed the following IP ranges:'
		sed -e 's/^.* //' -e 's/0\/24//' "${addfile}" | tr '\n' '|'; echo
	fi

	if [ "${count}" -gt 0 ] || [ "${countr}" -gt 0 ]; then
		echo; echo '  Reputation - dMax Stats'
		echo '  ------------------------------'
		printf "%-17s %-10s\n" '  Blacklisted' 'Match'
		printf "%-8s %-8s %-8s %-8s\n" '  Ranges' 'IPs' 'Ranges' 'IPs'
		echo '  ------------------------------'
		printf "%-8s %-8s %-8s %-8s\n" "  ${count}" "${countb}" "${countr}" "${counts}"

		emptyfiles # Call emptyfiles function
	else
		echo '  Reputation -dMax ( None )'
	fi
}


# Reputation function 'pMax'. (No Country code exclusions)
reputation_pmax(){
	echo; echo; echo '===[ Reputation - pMax ]======================================'
	echo; echo "  Querying for repeat offenders ( pMax=${max} ) [ ${now} ]"
	data="$(find ${pfbdeny}*.txt ! -name pfB*.txt ! -name *_v6.txt -type f | xargs cut -d '.' -f 1-3 |
		awk -v max=${max} '{a[$0]++}END{for(i in a){if(a[i] > max){print i}}}' | grep -v '^1\.1\.1')"

	if [ ! -z "${data}" ]; then
		# Find repeat offenders in each individual blocklist outfile
		echo '  Processing [ Block ] IPs'
		count=0

		for ip in ${data}; do
			count="$((count + 1))"
			runonce=0; ii="$(echo ^${ip}. | sed 's/\./\\\./g')"
			list="$(find ${pfbdeny}*.txt ! -name pfB*.txt ! -name *_v6.txt -type f | xargs grep -al ${ii})"

			for blfile in ${list}; do
				header="$(echo ${blfile##*/} | cut -d '.' -f1)"
				grep "${ii}" "${blfile}" > "${tempfile}"
				awk 'FNR==NR{a[$0];next}!($0 in a)' "${tempfile}" "${blfile}" > "${tempfile2}"; mv -f "${tempfile2}" "${blfile}"

				if [ "${runonce}" -eq 0 ]; then
					echo "${ip}.0/24" >> "${blfile}"
					echo "${header}" "${ip}." >> "${dedupfile}"
					echo "${header}" "${ip}.0/24" >> "${addfile}"
					runonce=1
				else
					echo "${header}" "${ip}." >> "${dedupfile}"
				fi
			done
		done

		# Remove repeat offenders in masterfile
		echo '  Removing   [ Block ] IPs'
		> "${tempfile}"; > "${tempfile2}"
		sed 's/\./\\\./g' "${dedupfile}" > "${tempfile2}"
		while IFS=' ' read -r ips; do grep "${ips}" "${masterfile}" >> "${tempfile}"; done < "${tempfile2}"
		countb="$(grep -c ^ ${tempfile})"
		awk 'FNR==NR{a[$0];next}!($0 in a)' "${tempfile}" "${masterfile}" > "${tempfile2}"; mv -f "${tempfile2}" "${masterfile}"
		cat "${addfile}" >> "${masterfile}"
		cut -d ' ' -f2 "${masterfile}" > "${mastercat}"

		echo; echo '  Removed the following IP ranges:'
		sed -e 's/^.* //' -e 's/0\/24//' "${addfile}" | tr '\n' '|'; echo

		echo; echo '  Reputation - pMax Stats'
		echo '  ----------------'
		printf "%-8s %-8s\n" '  Ranges' 'IPs'
		echo '  ----------------'
		printf "%-8s %-8s\n" "  ${count}" "${countb}"

		emptyfiles # Call emptyfiles function
	else
		echo '  Reputation -pMax ( None )'
	fi
}


# Function to split ET Pro IPREP into category files and compile selected blocked categories into outfile.
processet() {
	if [ -s "${pfborig}${alias}.orig" ]; then
		# Remove previous ET IPRep files
		[ -d "${etdir}" ] && [ "$(ls -A ${etdir})" ] && rm -r "${etdir}/ET_"*
		> "${tempfile}"; > "${tempfile2}"

		# ET CSV format (IP, Category, Score)
		echo; echo; echo 'Compiling ET IPREP IQRisk based upon user selected categories'
		while IFS=',' read i j k; do
			# Some ET categories are not in use (For future use)
			case "${j}" in
				1)  echo "${i}" >> "${etdir}/ET_Cnc.txt";;
				2)  echo "${i}" >> "${etdir}/ET_Bot.txt";;
				3)  echo "${i}" >> "${etdir}/ET_Spam.txt";;
				4)  echo "${i}" >> "${etdir}/ET_Drop.txt";;
				5)  echo "${i}" >> "${etdir}/ET_Spywarecnc.txt";;
				6)  echo "${i}" >> "${etdir}/ET_Onlinegaming.txt";;
				7)  echo "${i}" >> "${etdir}/ET_Drivebysrc.txt";;
				8)  echo "${i}" >> "${etdir}/ET_Cat8.txt";;
				9)  echo "${i}" >> "${etdir}/ET_Chatserver.txt";;
				10) echo "${i}" >> "${etdir}/ET_Tornode.txt";;
				11) echo "${i}" >> "${etdir}/ET_Cat11.txt";;
				12) echo "${i}" >> "${etdir}/ET_Cat12.txt";;
				13) echo "${i}" >> "${etdir}/ET_Compromised.txt";;
				14) echo "${i}" >> "${etdir}/ET_Cat14.txt";;
				15) echo "${i}" >> "${etdir}/ET_P2P.txt";;
				16) echo "${i}" >> "${etdir}/ET_Proxy.txt";;
				17) echo "${i}" >> "${etdir}/ET_Ipcheck.txt";;
				18) echo "${i}" >> "$[etdir}/ET_Cat18.txt";;
				19) echo "${i}" >> "${etdir}/ET_Utility.txt";;
				20) echo "${i}" >> "${etdir}/ET_DDos.txt";;
				21) echo "${i}" >> "${etdir}/ET_Scanner.txt";;
				22) echo "${i}" >> "${etdir}/ET_Cat22.txt";;
				23) echo "${i}" >> "${etdir}/ET_Brute.txt";;
				24) echo "${i}" >> "${etdir}/ET_Fakeav.txt";;
				25) echo "${i}" >> "${etdir}/ET_Dyndns.txt";;
				26) echo "${i}" >> "${etdir}/ET_Undesireable.txt";;
				27) echo "${i}" >> "${etdir}/ET_Abusedtld.txt";;
				28) echo "${i}" >> "${etdir}/ET_Selfsignedssl.txt";;
				29) echo "${i}" >> "${etdir}/ET_Blackhole.txt";;
				30) echo "${i}" >> "${etdir}/ET_RAS.txt";;
				31) echo "${i}" >> "${etdir}/ET_P2Pcnc.txt";;
				32) echo "${i}" >> "${etdir}/ET_Sharedhosting.txt";;
				33) echo "${i}" >> "${etdir}/ET_Parking.txt";;
				34) echo "${i}" >> "${etdir}/ET_VPN.txt";;
				35) echo "${i}" >> "${etdir}/ET_Exesource.txt";;
				36) echo "${i}" >> "${etdir}/ET_Cat36.txt";;
				37) echo "${i}" >> "${etdir}/ET_Mobilecnc.txt";;
				38) echo "${i}" >> "${etdir}/ET_Mobilespyware.txt";;
				39) echo "${i}" >> "${etdir}/ET_Skypenode.txt";;
				40) echo "${i}" >> "${etdir}/ET_Bitcoin.txt";;
				41) echo "${i}" >> "${etdir}/ET_DDosattack.txt";;
				*)  echo "${i}" >> "${etdir}/ET_Unknown.txt";;
			esac
		done < "${pfborig}${alias}.orig"
		data="$(ls ${etdir} | sed 's/\.txt//')"
		printf "%-10s %-25s\n" '  Action' 'Category'
		echo '-------------------------------------------'

		for list in ${data}; do
			case "${etblock}" in
				*$list*)
					printf "%-10s %-25s\n" '  Block: ' "${list}"
					cat "${etdir}/${list}.txt" >> "${tempfile}"
					;;
			esac
			case "${etmatch}" in
				*$list*)
					printf "%-10s %-25s\n" '  Match: ' "${list}"
					cat "${etdir}/${list}.txt" >> "${tempfile2}"
					;;
			esac
		done
		echo '-------------------------------------------'

		if [ -f "${tempfile}" ]; then mv -f "${tempfile}" "${pfborig}${alias}.orig"; fi
		if [ "${etmatch}" != 'x' ]; then mv -f "${tempfile2}" "${pfbmatch}/ETMatch.txt"; fi
		counto="$(cat ${etdir}/ET_* | grep -cv '^#\|^$')"; countf="$(grep -cv '^1\.1\.1\.1$' ${pfborig}${alias}.orig)"
		echo; echo "All ET Folder count [ ${counto} ]  Final count [ ${countf} ]"
	else
		echo; echo 'No ET .orig File Found!'
	fi
}


# Function to extract IP addresses from XLSX files.
processxlsx() {
	if [ ! -x "${pathtar}" ]; then
		log='Application [ TAR ] Not found, cannot proceed.'
		echo "${log}" | tee -a "${errorlog}"
		exitnow
	fi

	if [ -s "${pfborig}${alias}.raw" ]; then
		"${pathtar}" -xf "${pfborig}${alias}.raw" -C "${tmpxlsx}"
		"${pathtar}" -xOf "${tmpxlsx}"*.[xX][lL][sS][xX] "xl/sharedStrings.xml" | \
			grep -aoEw "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" | sort | uniq > "${pfborig}${alias}.orig"
		rm -r "${tmpxlsx}"*

		countf="$(grep -cv '^1\.1\.1\.1$' ${pfborig}${alias}.orig)"
		echo; echo "Final count [ ${countf} ]"
	else
		echo 'XLSX download file missing'
		echo " [ ${alias} ] XLSX download file missing [ ${now} ]" >> "${errorlog}"
	fi
}


# Function to report final pfBlockerNG statistics.
closingprocess() {
	counto=0
	echo; echo '===[ FINAL Processing ]====================================='; echo
	if [ -d "${pfborig}" ] && [ "$(ls -A ${pfborig})" ]; then
		counto="$(find ${pfborig}*.orig 2>/dev/null | xargs cat | grep -cv '^#\|^$')"
	fi

	# Execute when 'de-duplication' is enabled
	if [ "${alias}" == 'on' ]; then
		sort -o "${masterfile}" "${masterfile}"
		sort -t . -k 1,1n -k 2,2n -k 3,3n -k 4,4n "${mastercat}" > "${tempfile}"; mv -f "${tempfile}" "${mastercat}"

		echo "   [ Original IP count   ]  [ ${counto} ]"
		countm="$(grep -c ^ ${masterfile})"
		echo; echo "   [ Final IP Count  ]  [ ${countm} ]"; echo

		s1="$(grep -cv '1\.1\.1\.1$' ${masterfile})"
		s2="$(find ${pfbdeny}*.txt ! -name *_v6.txt -type f 2>/dev/null | xargs cat | grep -cv '^1\.1\.1\.1$')"
		s3="$(sort ${mastercat} | uniq -d | tail -30)"
		s4="$(find ${pfbdeny}*.txt ! -name *_v6.txt -type f 2>/dev/null | xargs cat | sort | uniq -d | tail -30 | grep -v '^1\.1\.1\.1$')"
	else
		echo "   [ Original IP count   ]  [ ${counto} ]"
	fi

	if [ -d "${pfbpermit}" ] && [ "$(ls -A ${pfbpermit})" ]; then
		echo; echo '===[ Permit List IP Counts ]========================='; echo
		wc -l "${pfbpermit}"*.txt 2>/dev/null | sort -n -r
	fi
	if [ -d "${pfbmatch}" ] && [ "$(ls -A ${pfbmatch})" ]; then
		echo; echo '===[ Match List IP Counts ]=========================='; echo
		wc -l "${pfbmatch}"*.txt 2>/dev/null | sort -n -r
	fi
	if [ -d "${pfbdeny}" ] && [ "$(ls -A ${pfbdeny})" ]; then
		echo; echo '===[ Deny List IP Counts ]==========================='; echo
		wc -l "${pfbdeny}"*.txt 2>/dev/null | sort -n -r
	fi
	if [ -d "${pfbnative}" ] && [ "$(ls -A ${pfbnative})" ]; then
		echo; echo '===[ Native List IP Counts ] ==================================='; echo
		wc -l "${pfbnative}"*.txt 2>/dev/null | sort -n -r
	fi
	if [ -d "${pfbdeny}" ] && [ "$(ls -A ${pfbdeny})" ]; then
		emptylists="$(grep '^1\.1\.1\.1$' ${pfbdeny}*.txt | sed -e 's/^.*[a-zA-Z]\///' -e 's/\.txt:1.1.1.1/ /')"
		if [ ! -z "${emptylists}" ]; then
			echo; echo '====================[ Empty Lists w/1.1.1.1 ]=================='; echo
			for list in ${emptylists}; do
				echo "${list}"
			done
		fi
	fi
	if [ -d "${pfbdomain}" ] && [ "$(ls -A ${pfbdomain})" ]; then
		echo; echo '===[ DNSBL Domain/IP Counts ] ==================================='; echo
		wc -l "${pfbdomain}"* 2>/dev/null | sort -n -r
	fi
	if [ -d "${pfborig}" ] && [ "$(ls -A ${pfborig})" ]; then
		echo; echo '====================[ Last Updated List Summary ]=============='; echo
		ls -lahtr "${pfborig}"*.orig | sed -e 's/\/.*\// /' -e 's/.orig//' | awk -v OFS='\t' '{print $6" "$7,$8,$9}'
	fi

	# Execute when 'de-duplication' is enabled
	if [ "${alias}" == 'on' ]; then
		echo '==============================================================='; echo
		if [ "${s1} == ${s2}" ]; then
			echo 'Database Sanity check [  PASSED  ]'
		else
			echo 'Database Sanity check [  FAILED  ] ** These two counts should match! **'
			echo '------------'
			echo "Masterfile Count    [ ${s1} ]"
			echo "Deny folder Count   [ ${s2} ]"; echo
			echo 'Duplication sanity check (Pass=No IPs reported)'
		fi
		echo '------------------------'
		echo 'Masterfile/Deny folder uniq check'
		if [ ! -z "${s3}" ]; then echo "${s3}"; fi
		echo 'Deny folder/Masterfile uniq check'
		if [ ! -z "${s4}" ]; then echo "${s4}"; fi
		echo; echo 'Sync check (Pass=No IPs reported)'
		echo '----------'
	fi

	echo; echo 'IPv4 alias tables IP count'; echo '-----------------------------'
	find "${pfsensealias}"pfB_*.txt ! -name "*_v6.txt" -type f 2>/dev/null | xargs cat | grep -c ^

	echo; echo 'IPv6 alias tables IP count'; echo '-----------------------------'
	find "${pfsensealias}"pfB_*.txt -name "*_v6.txt" -type f 2>/dev/null | xargs cat | grep -c ^

	echo; echo 'Alias table IP Counts'; echo '-----------------------------'
	wc -l "${pfsensealias}"pfB_*.txt 2>/dev/null | sort -n -r

	echo; echo 'pfSense Table Stats'; echo '-------------------'
	"${pathpfctl}" -s memory | grep 'table-entries'
	pfctlcount="$(${pathpfctl} -vvsTables | awk '/Addresses/ {s+=$2}; END {print s}')"
	echo "Table Usage Count         ${pfctlcount}"
}

# Call appropriate processes using script argument $1.
case "${1}" in
	_*)
		if [ "$(echo ${1} | grep -c '_255')" -gt 0 ]; then process255; fi
		if [ "$(echo ${1} | grep -c '_agg')" -gt 0 ]; then cidr_aggregate; fi
		if [ "$(echo ${1} | grep -c '_rep')" -gt 0 ]; then reputation_depends; reputation_max; fi
		if [ "$(echo ${1} | grep -c '_dup')" -gt 0 ]; then duplicate; fi
		;;
	continent)
		duplicate
		;;
	domainduplicate)
		domainduplicate
		;;
	cidr_aggregate)
		agg_folder=true
		cidr_aggregate
		;;
	whoisconvert)
		whoisconvert
		;;
	suppress)
		suppress
		;;
	dmax)
		reputation_depends
		reputation_dmax
		;;
	pmax)
		reputation_depends
		reputation_pmax
		;;
	et)
		processet
		;;
	xlsx)
		processxlsx
		;;
	remove)
		remove
		;;
	aliastables)
		aliastables
		;;
	closing)
		emptyfiles
		closingprocess
		;;
	*)
		;;
esac
exitnow
