#!/usr/bin/perl -T ######################################################################################### # listrepeats.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 records from the popauth RBL cache # file that represent black-listed IPs that have repeated attempts to contact your mail # server. 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. ######################################################################################### # 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("\n"); print("\n"); print("\n"); print("\tRBL Repeat Offenders Report For $now_nice\n"); print("\t\n"); print("\t\n"); print("\t\n"); print("\t\n"); print("\t\n"); print("\t\n"); print("\t\n"); print("\n\n"); print("\n"); # Open the repeat_list queue file. First, make sure such a file exists... if (-e $rbl_cache) { if (open(REPEATLIST, "< $rbl_cache")) { print("\n"); print("\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\n"); while ($input = ) { 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]; # Output rows only for records that represent repeat offenders. if ($total_hits > 1) { $newhitspread = $now - $firsthit; $firsthit_nice = &tstamp($firsthit); $lasthit_nice = &tstamp($lasthit); $unban_nice = (($unban >= $now) ? "until " . &tstamp($unban) . "" : (($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; $rbl_claim =~ s//>/g; ++$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\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\n"); print("\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\t\n"); print("\t\n"); } } print("
#IP AddressTotal HitsPeriod HitsFirst SeenLast SeenPeriod FrequencyAverage FrequencyNull-Routed?RBL Provider
$rownumber$ip$total_hits$period_hits$firsthit_nice$lasthit_nice" . (($frequency > 0) ? $frequency_nice : " ") . "" . (($newfrequency > 0) ? $newfrequency_nice : " ") . "$unban_nice$rbl_service
delHost: $host" . pretty_time($first_seen_secs) . " ago" . ($inhotzone ? "" : "") . pretty_time($last_seen_secs) . ($inhotzone ? "" : "") . " ago" . (($frequency > 0) ? "1 msg every " . (($old_mps < 1.0) ? sprintf("%.6f", $old_mps) . "s" : pretty_time($old_mps)) : " ") . "" . (($newfrequency > 0) ? "1 msg every " . (($new_mps < 1.0) ? sprintf("%.6f", $new_mps) . "s" : pretty_time($new_mps)) : " ") . "" . (($unban > $now) ? "for " . pretty_time($unban - $now) . "" : " ") . "$rbl_claim
\n"); # Close the repeat_offenders queue file. close(REPEATLIST); } else { print("

ERROR: Unable to open $rbl_cache for input!

\n"); } } else { print("

The $rbl_cache datafile has not yet been initialized (you have not yet experienced repeat RBL attempts). Please try again later.

\n"); } # Close the HTML report. print("\n"); print("\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); }