#!/usr/bin/perl -T
#########################################################################################
# listrbl.pl
# Version 3.0.0
#
# Copyright 1997, 1998, 2004 Neil Harkins, William R. Thomas, Harlann Stenn,
# William W. Kimball Jr.
#
#      This file is part of popauth.
#
#      popauth is free software; you can redistribute it and/or modify
#      it under the terms of the GNU General Public License as published by
#      the Free Software Foundation; either version 2 of the License, or
#      (at your option) any later version.
#
#      popauth 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.
#
#      You should have received a copy of the GNU General Public License
#      along with popauth; if not, write to the Free Software
#      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# This program generates an HTML report that lists the entire contents of the popauth3
# cache file.  Be sure that $path_postfix, $path_postfix_data, and $rbl_cache accurately
# reflect the location of the RBL cache file.
#
# Created by:  William W. Kimball Jr. <bkimball1@yahoo.com>
#########################################################################################
# vi: tabstop=3

# Pragmas
use warnings;
use strict;
use diagnostics;

# Variable Declarations
my $path_postfix      = "/etc/postfix/";				# Path to your installation of the postfix config files (indicate where main.cf is located).
my $path_postfix_data = $path_postfix . "data/";	# Path to your postfix data files (indicate where you want $popauth_checks to be located -- must be the same location as you indicate within main.cf).
my $rbl_cache         = $path_postfix_data . "rbl_cache";
my $now               = time();
my $now_nice          = &tstamp($now);
my $hotzone_time      = $now - (60 * 60 * 24);		# Highlight last-hit-dt's that occur within the last 24 hours.
my $inhotzone         = 0;
my $input             = "";
my $ip                = "";
my $period_hits       = 1;
my $total_hits        = 1;
my $firsthit          = $now;
my $lasthit           = $now;
my $frequency         = 0.0;
my $unban             = -1;
my $host              = "";
my $rbl_service       = "";
my $rbl_claim         = "";
my $newhitspread      = 0;
my $newfrequency      = 0.0;
my $firsthit_nice     = "";
my $lasthit_nice      = "";
my $unban_nice        = "";
my $frequency_nice    = 0.0;
my $newfrequency_nice = 0.0;
my $old_mps           = 0;
my $new_mps           = 0;
my $first_seen_secs   = 0;
my $last_seen_secs    = 0;
my $rownumber         = 0;
my @fields;

# Open the report output stream.
print("Content-type: text/html\n\n");
print("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
print("<html>\n");
print("<head>\n");
print("\t<title>popauth RBL Cache Report For $now_nice</title>\n");
print("\t<meta http-equiv=\"content-type\" content=\"text/html; charset=iso-8859-1\">\n");
print("\t<meta http-equiv=\"content-language\" content=\"en-us\">\n");
print("\t<meta http-equiv=\"content-style-type\" content=\"text/css\">\n");
print("\t<meta http-equiv=\"Refresh\" content=\"600\">\n");
print("\t<meta name=\"robots\" content=\"noindex,nofollow\">\n");
print("\t<link rel=\"stylesheet\" href=\"/css/listreport.css\" media=\"screen\" type=\"text/css\">\n");
print("\t<link rel=\"stylesheet\" href=\"/css/printable.css\" media=\"print\" type=\"text/css\">\n");
print("</head>\n\n");
print("<body>\n");

# Open the repeat_list queue file.  First, make sure such a file exists...
if (-e $rbl_cache) {
	if (open(REPEATLIST, "< $rbl_cache")) {
		print("<table border=\"1\" cellspacing=\"0\" cellpadding=\"2\">\n");
		print("\t<tr>\n");
		print("\t\t<th>#</th>\n");
		print("\t\t<th>IP Address</th>\n");
		print("\t\t<th>Total Hits</th>\n");
		print("\t\t<th>First Seen</th>\n");
		print("\t\t<th>Last Seen</th>\n");
		print("\t\t<th>Period Frequency</th>\n");
		print("\t\t<th>Null-Routed?</th>\n");
		print("\t\t<th>RBL Provider</th>\n");
		print("\t</tr>\n");

		while ($input = <REPEATLIST>) {
			chomp($input);

			# Split the input line into an array for closer evaluation.
			@fields         = split(/\t/, $input);
			$ip             = $fields[0];
			$firsthit       = $fields[1];
			$lasthit        = $fields[2];
			$unban          = $fields[3];
			$period_hits    = $fields[4];
			$total_hits     = $fields[5];
			$frequency      = $fields[6];
			$host           = $fields[7];
			$rbl_service    = $fields[8];
			$rbl_claim      = $fields[9];
			$rbl_claim      =~ s/</&lt;/g;
			$rbl_claim      =~ s/>/&gt;/g;

			$newhitspread   = $now - $firsthit;
			$firsthit_nice  = &tstamp($firsthit);
			$lasthit_nice   = &tstamp($lasthit);
			$unban_nice     = (($unban >= $now) ? "<b>until " . &tstamp($unban) . "</b>" : (($unban > 0) ? "Expired" : "Never"));
			$frequency_nice = sprintf("%.6f", $frequency);
			$old_mps        = (($frequency > 0) ? 1 / $frequency : 0);
			$first_seen_secs = $now - $firsthit;
			$last_seen_secs = $now - $lasthit;
			$inhotzone      = $lasthit >= $hotzone_time;
			++$rownumber;

			# Calculate the new frequency only if the hit spread is not zero (to avoid a division by zero error).
			if (0 < $newhitspread) {
				$newfrequency = ($period_hits) / $newhitspread;
			} else {
				$newfrequency = $frequency;
			}
			$newfrequency_nice = sprintf("%.6f", $newfrequency);
			$new_mps = (($newfrequency > 0) ? 1 / $newfrequency : 0);

			# Write the current line out in a friendly format.
			print("\t<tr>\n");
			print("\t\t<td align=\"right\" nowrap>$rownumber</td>\n");
			print("\t\t<td nowrap>$ip</td>\n");
			print("\t\t<td nowrap>$total_hits</td>\n");
			print("\t\t<td nowrap>$firsthit_nice</td>\n");
			print("\t\t<td nowrap>$lasthit_nice</td>\n");
			print("\t\t<td nowrap>" . (($frequency > 0) ? $frequency_nice : "&nbsp;") . "</td>\n");
			print("\t\t<td nowrap>$unban_nice</td>\n");
			print("\t\t<td nowrap>$rbl_service</td>\n");
			print("\t</tr>\n");
			print("\t<tr class=\"oddrow\">\n");
			print("\t\t<td nowrap><a href=\"removerblentry.pl?id=" . $ip . "\">del</a></td>\n");
			print("\t\t<td nowrap colspan=\"2\" nowrap><b>Host:</b> $host</td>\n");
			print("\t\t<td nowrap>" . pretty_time($first_seen_secs) . " ago</td>\n");
			print("\t\t<td nowrap>" . ($inhotzone ? "<span class=\"hotzone\">" : "") . pretty_time($last_seen_secs) . ($inhotzone ? "</span>" : "") . " ago</td>\n");
			print("\t\t<td nowrap>" . (($frequency > 0) ? "1 msg every " . (($old_mps < 1.0) ? sprintf("%.6f", $old_mps) . "s" : pretty_time($old_mps)) : "&nbsp;") . "</td>\n");
			print("\t\t<td nowrap>" . (($unban > $now) ? "<span class=\"hotzone\">for " . pretty_time($unban - $now) . "</span>" : "&nbsp;") . "</td>\n");
			print("\t\t<td nowrap>$rbl_claim</td>\n");
			print("\t</tr>\n");
		}

		print("</table>\n");

		# Close the repeat_offenders queue file.
		close(REPEATLIST);
	}
	else {
		print("<p><b>ERROR:</b> Unable to open $rbl_cache for input!</p>\n");
	}
}
else {
	print("<p>The $rbl_cache datafile has not yet been initialized (you have not yet experienced repeat RBL attempts). Please try again later.</p>\n");
}

# Close the HTML report.
print("</body>\n");
print("</html>\n");

exit(0);

#########################################################################################
# tstamp
#
# Returns a formatted date/time stamp.
#
# Receives:
#   OPTIONAL:  A date-time value to format.  time() is used if this is not provided.
# Returns:
#   A formatted version of the date-time value.
#########################################################################################
sub tstamp {
	my $datetime = time();

	# Read an arbitrary time value from the argument list, if provided.
	if (@_ > 0) {
		$datetime = $_[0];
	}

	use POSIX qw(strftime);
	return(POSIX::strftime("%b %d %H:%M:%S", localtime($datetime)));
}

#########################################################################################
# pretty_seconds
#
# Returns a formatted count of seconds parsed into weeks, days, hours, minutes, and
# remaining seconds.
#
# Receives:
#   A number of seconds to format.
# Returns:
#   A formatted version of the number of seconds in "Xw Xd Xh Xm Xs" format.
#########################################################################################
sub pretty_seconds {
	my $bigseconds = $_[0];
	my $weeks = 0;
	my $days = 0;
	my $hours = 0;
	my $minutes = 0;
	my $seconds = 0;
	my $pretty = "";

	$weeks   = int($bigseconds / (7 * 24 * 60 * 60));
	$seconds = int($bigseconds % (7 * 24 * 60 * 60));
	$days    = int($seconds / (24 * 60 * 60));
	$seconds = int($seconds % (24 * 60 * 60));
	$hours   = int($seconds / (60 * 60));
	$seconds = int($seconds % (60 * 60));
	$minutes = int($seconds / 60);
	$seconds = int($seconds % 60);

	$pretty = (($weeks > 0) ? $weeks . "w " : "") .
				 ((($weeks > 0) || ($days > 0)) ? $days . "d " : "") .
				 ((($weeks > 0) || ($days > 0) || ($hours > 0)) ? $hours . "h " : "") .
				 ((($weeks > 0) || ($days > 0) || ($hours > 0) || ($minutes > 0)) ? $minutes . "m " : "") .
				 $seconds . "s";

	return($pretty);
}
