#
# Private BASHlib for Watcher-Report2
#
declare -A grands=([records] [cidrs] [plain] [nodes] [label])

_LABELW=34
OUTFORM="%-34s : %8d %8d %8d %'15d\n"
TOTFORM="%34s » %8d %8d %8d %'15d\n"
DETFORM="%34s . %8d %8d %8d %'15d\n"
COLFORM="%34s | %8s %8s %8s %15s\n"


# Draw a section headline
# $1 headline text
# $2 character for line
headline() {
local funtag="[${FUNCNAME[0]}]"
local	total=78

	awk -v text="$1" -v tot_width=$total -v char="$2" '
	BEGIN {
	prefixlen=5
	for (i=1; i<=prefixlen; i++) { prefix = prefix char }
	strlen = length(text);
	suffixlen = tot_width - length(prefix) - strlen - 1; # "- 2" für die Leerzeichen
	if (suffixlen < 0) suffixlen = 0;
	suffix = sprintf("%*s", suffixlen, ""); # Generiere leerzeichen-basiertes Padding
	gsub(" ", char, suffix);                # Ersetze Leerzeichen mit "-"
	print ""
	print prefix " " text " " suffix;
	}'
}


# Draw a simple separation line
# $1 character for line
sepline() {
local funtag="[${FUNCNAME[0]}]"
local	total=79

	awk -v tot_width=$total -v char="$1" '
	BEGIN {
	for (i=1; i<=tot_width; i++) { line = line char }
#	print ""
	print line;
	}'
}


# Print a section headline
sec_headline() {
# $1	Section name
# $2	character to use
local funtag="[${FUNCNAME[0]}]"
local	total=35

	awk -v text="$1" -v tot_width=$total -v char="$2" '
	BEGIN {
	prefixlen=5
	for (i=1; i<=prefixlen; i++) { prefix = prefix char }
	strlen = length(text);
	suffixlen = tot_width - length(prefix) - strlen - 1; # "- 2" für die Leerzeichen
	if (suffixlen < 0) suffixlen = 0;
	suffix = sprintf("%*s", suffixlen, ""); # Generiere leerzeichen-basiertes Padding
	gsub(" ", char, suffix);                # Ersetze Leerzeichen mit "-"
	print ""
	print prefix " " text " " suffix;
	}'
}

colheaders() {
local funtag="[${FUNCNAME[0]}]"
local	prefix suffix

	printf "$COLFORM" "" "Records" "CIDRs" "nIPs" "xIPs"
#	prefix=`printf "%34s"`
#	suffix=`printf "%8s %8s %8s %15s" "Records" "CIDRs" "nIPs" "xIPs" | tr " " "_"`
#	echo "$prefix | $suffix"
}

lineout() {
local funtag="[${FUNCNAME[0]}]"
	printf "$OUTFORM" "$1" $2 $3 $4 $5
}

tot_out() {
local funtag="[${FUNCNAME[0]}]"
local	tot_label
local 	-n map=$1 

: echo "$funtag"
: echo "incomming tot lable '${map[label]}'"
	if [ -z "${map[label]}" ]
	then tot_label="Total records & DROPs"
	else tot_label="${map[label]}"
	fi

	if [ -z "$tot_label" ]
	then echo "BANG!"; return
	fi
	sepline _
	printf "$TOTFORM" "$tot_label" ${map[records]} ${map[cidrs]} ${map[plain]} ${map[nodes]}
}

# Calculate total nodes for CIDRs
# $1 DROP list name
calc_subips() {
local funtag="[${FUNCNAME[0]}]"
local   elems

        elems=`ipset list $1 | grep '^[1-9]' | grep "/"` 

        awk '
        BEGIN   { total = 0 }
                { 
                        x = split($0, parts, "/" )
                        bits   = (32 - parts[2])
                        range  = 2^bits
                        total += range
                }
        END     {
                print total
        }
        ' <<< "$elems"
}

#
# Conditionally dump detals for an IPset
#
dump_details() {
# $1	the IPset's name
local funtag="[${FUNCNAME[0]}]"

: echo $funtag
: echo "»» IPset $1 ««"
	[ -n "$_TRACE" ] && echo $funtag
	case $1 in
		WatchLG*)	details_LG "$1" ;;
		WatchMX*)	details_MX "$1" ;;
		WatchWB*)	details_WB "$1" ;;
		*)		echo "IPset $1 is not known"
	;;
	esac
}

#
# Common for detail output ...
#
process_details() {
local	all="$1"
local	num num_cidrs num_plain num_nodes
local	detail
local	details="$2"
local	set="$3"
declare -A det

	sec_headline "$4" "$5"
	colheaders
	for detail in $details
	do
		num=$(grep "$detail" <<< "$all" | wc -l)
		num_cidrs=$(grep "$detail" <<< "$all" | grep -c  "/")
		num_plain=$(grep "$detail" <<< "$all" | grep -vc "/")

		num_nodes=0
		if [ $num_cidrs -gt 0 ]
		then num_nodes=$(calc_subips "$set")
		fi

		det[records]=$num
		det[cidrs]=$num_cidrs
		det[plain]=$num_plain
		det[nodes]=$num_nodes
		det[label]=$detail
		det[set]=$set
		detail_out det
	done
}


#
# IPset details for the login module LG
#
details_LG() {
# $1	the set name
local funtag="[${FUNCNAME[0]}]"
local	module_txt='Login'
local	details="NXDOMAIN FAKEHOST TRUEHOST"
local	all=`grep "DB,$module_txt," <<< "$ALL"`

	process_details "$all" "$details" "$1" "$module_txt" "-"
}

#
# Details MX
# Views 'Mail' and 'Mailbox' details
#
details_MX() {
# $1	The set name	
local funtag="[${FUNCNAME[0]}]"
local	all module_txt
local	details="NXDOMAIN FAKEHOST TRUEHOST"

: echo $funtag
	# Phase #1 - Mail MTA
	module_txt='Mail'
	all=`grep "DB,$module_txt," <<< $ALL`
	process_details "$all" "$details" "$1" "$module_txt" "-"

	# Phase #2 - Mailbox POP/IMAP
	module_txt='Mailbox'
	all=`grep "DB,$module_txt," <<< $ALL`
	process_details "$all" "$details" "$1" "$module_txt" "-"
}

#
# Details WB
# The WEB module has arbitrary classes defined
# by 'WEB_CLASS=...' in the *.rule files
#
details_WB() {
# $1	The set name	
local funtag="[${FUNCNAME[0]}]"
local	module_txt='WEB'
local	all
local	details
#declare -A det

: echo $funtag
	details=`get_wb_details`
	all=`grep "DB,$module_txt," <<< $ALL`
	process_details "$all" "$details" "$1" "$module_txt" "-"
}

get_wb_details() {
local funtag="[${FUNCNAME[0]}]"
local	all
local	section="DB,WEB,"
local	details

	all=`ipset l | grep "$section"`
	awk '
	BEGIN {}
		{
			split($0,parts,",")
			detail=parts[3]
			gsub("\"","",detail)
			details[detail]=detail
		}
	END	{
		for (detail in details) {
			print detail
		}
	}
	' <<< $all | sort
}

#
# Switch some ugly row labels
#
pretty() {
# $1	A row label
local	pretty

	case $1 in
		NXDOMAIN) pretty="NX Domains" ;;
		FAKEHOST) pretty="Fake hosts" ;;
		TRUEHOST) pretty="True hosts" ;;
		*)	  pretty="$1"
	esac

	echo "$pretty"
}

detail_out() {
# $1	Map of detail values
local funtag="[${FUNCNAME[0]}]"
local	-n map=$1
#local	row_label="${map[set]},${map[label]}"
local	row_label=`pretty "${map[label]}"`

	[ -n "$_TRACE" ] && echo $funtag
	: echo ${map[*]}
	printf "$DETFORM" "$row_label" ${map[records]} ${map[cidrs]} ${map[plain]} ${map[nodes]}
}


#
# Dump a single set
#
dump_set() {
# $1	set name
# $2	row label
# $3	map for totals cumulation
local funtag="[${FUNCNAME[0]}]"
local	-n map=$3
local	set=$1
local	row_label="$2"
local	all num num_cidrs num_plain num_nodes
local	detail_sets="WatchLG-DB WatchMX-DB WatchWB-DB"

	all=`$IPSET list $set | grep '^[1-9]'`

#	if [[ "$detail_sets" =~ "$set" ]]
#	then dump_details $set
#	fi
	case $set in
		Watch*) dump_details $set
	;;
	esac

 	num=`wc -l <<< $all`
	num_cidrs=`grep -c  "/" <<< $all`
	num_plain=`grep -vc "/" <<< $all`

	if [ $num_cidrs -gt 0 ]
	then num_nodes=`calc_subips $set`
	else num_nodes=0
	fi

	(( map[records]  += num ))
	(( map[cidrs]    += num_cidrs ))
	(( map[plain]    += num_plain ))
	(( map[nodes]    += num_nodes ))
	# Add to the grand-totals (global) as well ..
	(( grands[records]  += num ))
	(( grands[cidrs]    += num_cidrs ))
	(( grands[plain]    += num_plain ))
	(( grands[nodes]    += num_nodes ))

	lineout "$row_label" $num $num_cidrs $num_plain $num_nodes
}

#
# Dump out a bunch of sets
#
dump_sets() {
# $1	List of sets
# $2	Row prefix
# $3	Passed label for totals
# $4	Section headings
local funtag="[${FUNCNAME[0]}]"
local   set sets="$1"
local   row_label tot_label
local	section section_headers="$4" section_texts
declare -A tot=([records]=0 [cidrs]=0 [plain]=0 [nodes]=0 [label]='')

: echo "$funtag"
	if [ -z "$3" ]
	then tot[label]="Tot label empty, $funtag"
	else tot[label]="$3"
	fi

	for set in `echo $sets`
	do
		row_label="$2 '$set'"
		dump_set $set "$row_label" tot
	done

	tot_out tot
}

report_ipsets() {
local funtag="[${FUNCNAME[0]}]"
local	row_label="Internal IPset"
local	tot_label="Totals internal IPsets"
#local	sets=`$IPSET l -n`
#local	sets='custody'
local	sets=$RELEVANT_SETS	# from the conf file

	headline "Internal IP sets" _
	colheaders
	dump_sets "$sets" "$row_label" "$tot_label"
}

report_module_dbs() {
local funtag="[${FUNCNAME[0]}]"
local	modules="LG MX WB"
local	row_label="DROPs in module DB"
local	tot_label="Total of module DBs"
local	set sets db 
local	sections="DB,Login DB,Mail DB,Mailbox DB,WEB"

	headline "Module databases" _
	# colheaders

	sets=`printf "Watch%s-DB " $modules | xargs`
	dump_sets "$sets" "$row_label" "$tot_label" "$sections"
}

report_dynloader() {
local funtag="[${FUNCNAME[0]}]"
local	sets="spamhaus nixspam"	# should be in the conf file
local	row_label="DROPS contributed by"
local	tot_label="Total DROPS from dynloaders"

	headline "Dynloader" _
	colheaders
	dump_sets "$sets" "$row_label" "$tot_label"
}

report_geo_tracker() { # the pseudo module's DB
local funtag="[${FUNCNAME[0]}]"
local	modules="GeoTrack"
local	row_label="Pseudo module DB"
local	tot_label="Total DROPs in GeoTrack"
local	set sets db 

	headline "Geo Tracker" _
	colheaders
	sets=`printf "%s-DB " $modules`
	dump_sets "$sets" "$row_label" "$tot_label"
}

report_geo_blocker() { # stuff from ipdeny
local funtag="[${FUNCNAME[0]}]"
local	row_label="DROPs contrib. by ipdeny"
local	tot_label="Total geo blockings"
local	sets

	CMD=`grep ZONES $MASTER_PATH/dynload/geo/geo.conf `
	eval $CMD

	headline "Geo blockings" _
	echo "($CMD)"
	colheaders

	sets=`printf "geo-%s " $ZONES | xargs`
	: echo "Sets: '$geosets'"
	dump_sets "$sets" "$row_label" "$tot_label"
}

report_black() {
local funtag="[${FUNCNAME[0]}]"
local	sets="blacklist" 
local	row_label="DROPs in"
local	tot_label="Blacklist totals"

	headline "Blacklist (manually maintained in $MASTER_PATH)" _
	colheaders
	dump_sets "$sets" "$row_label" "$tot_label"
}

report_white() {
local funtag="[${FUNCNAME[0]}]"
local	sets="whitelist" 
local	row_label="ACCEPTS in"
local	tot_label="Whitelist totals"

	headline "Whitelist (manually maintained in $MASTER_PATH)" _
	colheaders
	dump_sets "$sets" "$row_label" "$tot_label"
}

report_summary() {
local funtag="[${FUNCNAME[0]}]"
	
	echo
	headline "Grand totals" "#"
	sepline "#"
	report_black
	report_white

	headline "Total firewall" =
	colheaders
#	lineout "Total drops in firewall" ${grands[records]} ${grands[cidrs]} ${grands[plain]} ${grands[nodes]}
	grands[label]="Total drops in firewall" 
	tot_out grands
}

dropped-packets() {
local	all

	echo "
	««««« Continued access of DROPed bandits »»»»»
	"
	all=`grep '\ packets\ [0-9]*' <<< $ALL`
:	echo "$all"

	awk '
	function drawline (len,char,	i,line) {
		for (i=1; i<=len; i++) {
			line = line char
		}
		print line
	}

	/(packets 0)/	{ next }
			{
				# Extract IPSET Name ...
				match($0, /comment "([^"]+)"/, tmp)
				set = tmp[1]
				gsub(" ", "", set)  # Entferne Leerzeichen aus dem Set-Namen
				sets[set] = set

				# Extract packet count ...
				match($0, /packets ([0-9]+)/, tmp)
				packets = tmp[1]

				# Extract byte count ...
				match($0, /bytes ([0-9]+)/, tmp)
				bytes = tmp[1]

			#	printf "Packets: %8d, Bytes %15d\n", packets, bytes	# Debug ...
				packet_sum[set] += packets
				byte_sum[set] 	+= bytes
			}
	END	{
		 	printf	"%-30s %8s %15s\n", "IPset comment", "Packets","Bytes"
			#drawline (50,"-")
			print	"-------------------------------------------------------"
			asort (sets)
			for (s in sets) {
		 		printf "%-30s %8d %15d\n", sets[s], packet_sum[sets[s]], byte_sum[sets[s]]
				total_packets 	+= packet_sum[sets[s]]
				total_bytes	+= byte_sum[sets[s]]
			}

			print	"-------------------------------------------------------"
		 	printf	"%30s %8d %15d\n", "Totals:", total_packets, total_bytes
	}
	' <<< $all
}