#!/usr/local/bin/perl
#
# ============== imslog.pl =================
# A simple program for gathering statistics from 
# PMDF/iMS/Sun ONE Messaging Server mail.log* files.
#
# Sample usage:
#   % imslog.pl < InstanceRoot/log/imta/mail.log_yesterday > IMTA.stats
#
# You must have at least V5.005 of perl to run this script.  Specifically,
# there must be support for the "use vars qw" feature.
#
# To run it this tool nightly, you might make a small script, say 
# collect_stats:
#
# #!/bin/csh
# /usr/local/bin/imslog.pl < InstanceRoot/log/imta/mail.log_current >& \
#   /usr/tmp/mail_stats.log
# mailx -s "mail_stats, `date`" root@mynode.com < /usr/tmp/mail_stats.log
#
# Then make a crontab entry for root to launch it nightly:
# 15 0 * * * /usr/local/bin/collect_stats > /dev/null 2>&1
#
# ----------------------------------------------------------------------
#
# iMS mail.log file codes:
#
# A -- Print request failed, FAX temp failure, page aborted, 
#      or IMAP connection failed
# C -- Connection closed
# D -- Message dequeue
# E -- Message enqueue
# F -- Mailserv command or page failed
# I -- ETRN received
# J -- Connection rejected
# M -- Maximum number of FAX delivery attempts exceeded
# O -- Connection opened
# Q -- Message requeue
# R -- Paging switch rejected message
# S -- Print request, mailserv operation, or page sent success
# X -- Connection rejected
# Y -- Connection failed
# Z -- Delivery and reqeue (currently treated the same as a Q)
# ----------------
# Global Variables:
#
# %bad_domains - count of Q records caused by domains
# %chan_enq - time a particular message was enqueued
# %conn_close - count of closed connecions per server
# %conn_close_source - source of connection closes
# %conn_failed - number of failed connections per server
# %conn_fail_source - source of connection failures
# %conn_open - count of opened connections per server
# %conn_open_source - source of connection opens
# %conn_reject - count of connection rejects per server
# %conn_reject_source - source of connection rejects
# %delay_bins_chan - counts of messages based on delays per channel
# %delay_bins_host - counts of messages based on delays per host
# %dequeues_by_date - count of dequeues by date
# %etrn_domains - the domains that have requested etrn support
# %failed_messages - count of Q records by channel
# %fax_destinations - count of successful faxes sent by source address by phone number
# %fax_senders - count of successful faxes sent by source address
# %giant_messages - count of big message senders by source
# %in_messages - count of messages enqueued by channel
# %in_size - size of messages enqueued by channel
# %into_chan - count of messages enqueued to channel
# %into_size - size of messages enqueued to channel
# %mailserv_users - domains using the mailserv channel
# %max_chan_delay - maximum delay time between enqueues and dequeues
# %max_host_delay - maximum delay time between enqs and deqs, by host
# %out_messages - count of messages dequeued by channel
# %out_messages_host - count of messages sent to a particular host
# %out_size - size of messages dequeued by channel
# %pager_successes - count of successful pages sent by channel
# %rectypes - count of each type of record we find, keyed by record type
# %rejected_messages - count of R records per channel
# %warning_cnt - count of W records per channel
# %retries_done - number of times a channel took n retries to deliver a msg
# %retry_num - index of letters (Z..A) to numbers (0..26)
# %size_bins - counts of messages based on size
# %total_host_delay - total delay time by host
#
# @all_dates - list of dates covered by this log file
# @bin_names - list of names used as keys into size_bins
# @delay_bin_names - list of names used as keys into delay_bins
# @dequeues_per_hour - count of dequeus in an hour
# @retry_bin_names - names of bins used to count retries
#
# $etrn_count - the number of etrn requests received
# $fax_failures - count of failed fax channel attempts
# $fax_max_failures - count of maximum delivery attempts exceeded
# $fax_successes - count of successful fax channel uses
# $last_date - last date processed
# $line_count - current line number we're on
# $mailserv_failures - count of failed mailserv uses
# $mailserv_successes - count of successful mailserv channel uses
# $pager_failures - count of failed pager channel uses
# $pager_successes - count of successful pager channel uses
# $printer_failures - count of failures on printer channels
# $printer_successes - count of successful printer channel uses
# $total_dequeues - total number of dequeue operations performed
# $total_dequeue_size - total size of messages dequeued
#
# -------
# Updates:
#
# 15 December 97 - Added support for Y records.  Thanks to Chris
#                  Pappagianis for this one.
# 15 December 97 - Added channel delay time processing.
# 16 December 97 - Added host delay time processing.
# 17 December 97 - Fixed a couple of bugs: failed connections weren't
#                  getting counted correctly; remove leading space from
#                  dates with single-digit days.  Also did some minor
#                  efficiency enhancements.
# 22 December 97 - Added handling for F records with mailserv channels
#                  and A records with IMAP servers
# 29 December 97 - Added message size bins
# 31 December 97 - Added dequeues per hour recording
#  7 January  98 - Redid delay time processing -- keep counts in bins
#                  and display those numbers rather than averages
#  9 January  98 - Fixed a bug in timeinsecs()
# 18 February 98 - Added big message source records
#  6 March    98 - Fixed some printing ugliness; changed the format of
#                  printed enqueue statistics
# 26 March    98 - Fixed a bug in the big message source records -- when
#                  messages had an empty envelope from address, the
#                  destination showed as the source
#  6 November 98 - Add support for logs that include pid's, bfc
#  9 November 98 - Add support for uucp channels, bfc
# 13 November 98 - Add support for 'J' field, bfc
# 17 November 98 - Allow x400 channel to use 'A' record, add processing for
#          W records, bfc
# 22 December 98 - Added support for FAX channels, specifically A, S,
#                  and M record types. (whm)
# 24 December 98 - Restructured the parsing of input records by centralizing
#                  the parse logic.  Added VMS conditional logic for gethost
#                  routines.  Optionally support the generation of mail or
#                  connection information.
#  8-JAN-1999:whm - Strip trailing '>' or '"' from hosts in transit 
#                   summary.  Fixed a typo in the server summary.
#  2-JUN-1999:whm - Comment out the VMS conditionals now that the latest
#                   perl for VMS has socket support via DECC.
#  3-Mar-2000:jwa - Added some support for PMDF 6.0 log file changes.
#                   Specifically the A and/or S that can be appended to
#                   E, D, and Q records is now tallied.
#  3-Mar-2000:jwa - Added support for debugging.  If you set the environment
#                   variable PMDFLOGPLDEBUG to 1, the $debug varible will
#                   be set.
use vars qw(%bad_domains
            %chan_enq
            %conn_close
            %conn_close_source
            %conn_failed
            %conn_fail_source
            %conn_open
            %conn_open_source
            %conn_reject
            %conn_reject_source
            %delay_bins_chan
            %delay_bins_host
            %dequeues_by_date
            %etrn_domains
            %failed_messages
            %fax_senders
            %fax_destinations
            %giant_messages
            %in_messages
            %in_size
            %into_chan
            %into_size
            %mailserv_users
            %max_chan_delay
            %max_host_delay
            %out_messages
            %out_messages_host
            %out_size
            %pager_successes
            %rectypes
            %rejected_messages
            %secure_connections
            %warning_cnt
            %retries_done
            %retry_num
            %size_bins
            %total_host_delay
            @all_dates
            @bin_names
            @delay_bin_names
            @dequeues_per_hour
            @retry_bin_names
            @token
            $connection_record
            $debug
            $etrn_count
            $fax_failures
            $fax_max_failures
            $fax_successes
            $generate_connection_report
            $generate_mail_report
            $last_date
            $line_count
            $mailserv_failures
            $mailserv_successes
            $pager_failures
            $pager_successes
            $printer_failures
            $printer_successes
            $processing_conninfo
            $processing_date
            $processing_host
            $processing_inchan
            $processing_outchan
            $processing_pid
            $processing_rectype
            $processing_rectype_fld_no
            $processing_server
            $processing_size
            $processing_time
            $rest
            $total_dequeues
            $total_dequeue_size
            );

use Time::Local;
use strict;

sub process_enqueue {
    my ($local_part, $end, $host, $source_host, $source_local, $tmp);
    my ($id);

   $secure_connections{"Auth"} += 1 if ($processing_rectype =~ /A/);
   $secure_connections{"Secure"} += 1 if ($processing_rectype =~ /S/);

    if (($rest =~ /^\s/) || ($rest =~ /^<>/)) {
        $source_host = "Empty envelope from address";
        ($host, $end) = split / /o, $rest, 2;
    }
    else {
        if ($processing_outchan eq 'l') {
            $host = 'local delivery';
            ($source_host, $end) = split / /o, $rest, 2;
        }
        else {
            ($source_local, $end) = split /@/o, $rest, 2;
            ($source_host, $tmp) = split / /o, $end, 2;
            if ($processing_outchan =~ /uucp/) {
                # A uucp_* channel, will be in the form host!user
                ($host,$local_part,$tmp) = split /\s+|\!/o,$tmp,3;
                $host = lc($host);
                $source_host = $host.'!'.$local_part;
            }
            else {
                ($local_part, $end) = split /@/o, $tmp, 2;
                ($host, $tmp) = split / /o, $end, 2;
                $host = lc($host);
                $source_host = $source_local.'@'.$source_host;
            }
        }
    }

    $end =~ /(<[^>]+>)/o;
    $id = $1;

    $in_messages{$processing_inchan}++;
    $in_size{$processing_inchan} += $processing_size;
    $into_chan{$processing_outchan}++;
    $into_size{$processing_outchan} += $processing_size;

    if ($processing_size > 1000) {
        $giant_messages{$source_host}++;
    }

    $chan_enq{$processing_outchan}{$id} 
        = timeinsecs($processing_date, $processing_time) 
            if (not exists $chan_enq{$processing_outchan}{$id});
}

sub process_dequeue {
   my ($local_part, $end, $host, $id, $deqtime, $delta, $tmp);
   my ($hours, $minutes, $seconds, $bin, $num_retries, $found_file);

   $secure_connections{"Auth"} += 1 if ($processing_rectype =~ /A/);
   $secure_connections{"Secure"} += 1 if ($processing_rectype =~ /S/);

   # Increment the message count for the hour

   ($hours, $minutes, $seconds) = split /:/o, $processing_time;
   $dequeues_per_hour[$hours]++;

   # Split the record into two parts: before and after the @ sign that
   # should be in the destination address

   if ($processing_outchan !~ /uucp/) {
        ($local_part, $end) = split /@/o, $_, 2;
   }

   # Now grab the host name from the destination address

   if ($processing_outchan eq 'l') {
      $host = 'local delivery';
   }
   elsif ($processing_outchan =~ /uucp/) {
      ($local_part,$end) = split /\!/o, $_, 2;
      $local_part =~ /(.*)([\s]+)([\S]+)/;
      $host = $3;
      $host = lc($host);
   }
   else {
      ($host, $tmp) = split / /o, $end, 2;
      ($local_part, $end) = split /@/o, $tmp, 2;
      ($host, $tmp) = split / /o, $end, 2;
      $host = lc($host);
   }
   $host =~ s/>$//;
   $host =~ s/\"$//;

   # Look for the message ID, enclosed in angle brackets
   $end =~ /(<[^>]+>)/o;
   $id = $1;

   $out_messages{$processing_outchan}++;
   $out_messages_host{$host}++;
   $out_size{$processing_outchan} += $processing_size;
   $total_dequeues++;
   $total_dequeue_size += $processing_size;
   if ($processing_date ne $last_date) {
      $last_date = $processing_date;
      push @all_dates, $last_date;
   }
   $dequeues_by_date{$processing_date}++;

   # Increment the proper bin counter

   if ($processing_size == 1) {
      $size_bins{'1K'}++;
   }
   elsif ($processing_size == 2) {
      $size_bins{'2K'}++;
   }
   elsif ($processing_size == 3) {
      $size_bins{'3K'}++;
   }
   elsif ($processing_size == 4) {
      $size_bins{'4K'}++;
   }
   elsif ($processing_size == 5) {
      $size_bins{'5K'}++;
   }
   elsif ($processing_size == 6) {
      $size_bins{'6K'}++;
   }
   elsif ($processing_size <= 10) {
      $size_bins{'<=10K'}++;
   }
   elsif ($processing_size <= 100) {
      $size_bins{'10K - 100K'}++;
   }
   elsif ($processing_size <= 1000) {
      $size_bins{'100K - 1M'}++;
   }
   else {
      if ($processing_size <= 10000) {
         $size_bins{'1M - 10M'}++;
      }
      else {
         $size_bins{'Over 10M'}++;
      }
   }

   # See if we have an enqueue time for this message.  If so, compute
   # the delay time ($delta) and increment the proper bin for the channel
   # and the host.  Also keep track of the maximum delay time per
   # channel and host.

   if (exists $chan_enq{$processing_outchan}{$id}) {
      $deqtime = timeinsecs($processing_date, $processing_time);
      $delta = $deqtime - $chan_enq{$processing_outchan}{$id};

      # Initialize the bin counters to zero if this is a newly seen
      # channel or host

      if (not exists $delay_bins_chan{$processing_outchan}) {
         foreach $bin (@delay_bin_names) {
            $delay_bins_chan{$processing_outchan}{$bin} = 0;
         }
      }
      if (not exists $delay_bins_host{$host}) {
         foreach $bin (@delay_bin_names) {
            $delay_bins_host{$host}{$bin} = 0;
         }
      }

      if ($delta < 2) {
         $delay_bins_chan{$processing_outchan}{'<2 sec'}++;
         $delay_bins_host{$host}{'<2 sec'}++;
      }
      elsif ($delta < 10) {
         $delay_bins_chan{$processing_outchan}{'<10 sec'}++;
         $delay_bins_host{$host}{'<10 sec'}++;
      }
      elsif ($delta < 60) {
         $delay_bins_chan{$processing_outchan}{'<1 min'}++;
         $delay_bins_host{$host}{'<1 min'}++;
      }
      elsif ($delta < 300) {
         $delay_bins_chan{$processing_outchan}{'<5 mins'}++;
         $delay_bins_host{$host}{'<5 mins'}++;
      }
      elsif ($delta < 3600) {
         $delay_bins_chan{$processing_outchan}{'<1 hour'}++;
         $delay_bins_host{$host}{'<1 hour'}++;
      }
      else {
         $delay_bins_chan{$processing_outchan}{'>1 hour'}++;
         $delay_bins_host{$host}{'>1 hour'}++;
      }

      if ((not (exists $max_chan_delay{$processing_outchan})) or
          ($delta > $max_chan_delay{$processing_outchan})) {
         $max_chan_delay{$processing_outchan} = $delta;
      }

      if ((not (exists $max_host_delay{$host})) or
          ($delta > $max_host_delay{$host})) {
         $max_host_delay{$host} = $delta;
      }

      # We keep a total delay time for hosts because we display the top
      # 20 hosts by total delay time

      $total_host_delay{$host} += $delta;
   }

   # If file name logging is on, then we can track messages by number
   # of retries before delivery

   # Look for a VMS-style file name first

   $found_file = 0;
   if ($_ =~ /PMDF_QUEUE:\[.*\]([A-Z])([A-Z])/) {
      $num_retries = $retry_num{$1}*26 + $retry_num{$2};
      $found_file = 1;
   }
   elsif ($_ =~ /imta\/queue\/.*\/([A-Z][A-Z])/) {
      $num_retries = $retry_num{$1}*26 + $retry_num{$2};
      $found_file = 1;
   }
   if ($found_file) {
      if (not exists $retries_done{$processing_outchan}) {
         foreach $bin (@retry_bin_names) {
            $retries_done{$processing_outchan}{$bin} = 0;
         }
      }

      if ($num_retries == 0) {
         $retries_done{$processing_outchan}{'1st time'}++;
      }
      elsif ($num_retries == 1) {
         $retries_done{$processing_outchan}{'2nd time'}++;
      }
      elsif ($num_retries < 5) {
         $retries_done{$processing_outchan}{'<5 times'}++;
      }
      else
      {
         $retries_done{$processing_outchan}{'>5 times'}++;
      }
   }
}

sub process_requeue {
    my ($from_address, $to_address, $other, $mbx, $domain);

    if ($rest =~ /^\s/) {
        $other = $rest;
        $other =~ s/^ //;
        $from_address = "";
    } elsif ($rest =~ /^<>/) {
        $other = $rest;
        $other =~ s/^<>//;
        $from_address = "";
    } else {
        ($mbx,$other) = split /@/o, $rest, 2;
        ($domain, $other) = split / /o, $other, 2;
        $from_address = $mbx . "@" .$domain;
    }

    # Check for header to, otherwise use envelope to
    if ($other =~ /^rfc822;/) {
        $other =~ s/^rfc822;//;
        ($mbx, $other) = split /@/o, $other, 2;
        ($domain, $other) = split / /o, $other, 2;
        $to_address = $mbx . "@" . $domain;
    } else {
        # Envelope to might not have a domain
        ($to_address, $other) = split / /o, $other, 2;
        if ($to_address =~ /@/o ) {
            ($mbx, $domain) = split /@/o, $to_address;
        } else {
            $domain = $to_address;
        }
    }

   $domain =~ s/>$//;
   $domain =~ s/\"$//;

    $failed_messages{$processing_inchan}++;
    $bad_domains{lc($domain)}++;
}

sub process_success {
   my ($address, $other, $user, $domain);
   my ($tag);

    ($address, $other) = split / /o, $rest, 2;

   # S records are only defined for mailserv, printer, FAX,
   # and pager channels
   # If we find something else, make a note of it for future extension

   if ($processing_inchan =~ /^mailserv/io) {
      $mailserv_successes++;
      ($user, $domain) = split /@/o, $address;
      $mailserv_users{lc($domain)}++;
   }
   elsif ($processing_inchan =~ /^printer/io) {
      $printer_successes++;
   }
   elsif ($processing_inchan =~ /^pager/io) {
      $pager_successes{$processing_inchan}++;
   }
   elsif ($processing_inchan =~ /^g3/io) {
      ($tag, $other) = split / /o, $other, 2;
      $other =~ m/^([\d| |-]+)/o;
      $fax_destinations{$address}{$1}++;
      $fax_senders{$address}++;
      $fax_successes++;
   }
   else {
      print "Unexpected channel $processing_inchan in S record at line $line_count\n";
   }
}

sub process_failure {
   # F records can be generated by the printer, pager, or mailserv channel

   # We only record the number of failures, no more.

   if ($processing_inchan =~ /^printer/io) {
      $printer_failures++;
   }
   elsif ($processing_inchan =~ /^pager/io) {
      $pager_failures++;
   }
   elsif ($processing_inchan =~ /^mailserv/io) {
      $mailserv_failures++;
   }
   else {
      print "** Found unexpected channel $processing_inchan in F record\n";
   }
}

sub process_abort {
   # A records can be generated by the printer channel, the pager channel, 
   # the fax channel, an IMAP server, or an X400 channel.  If it's an IMAP 
   # server, we'll call the connection fail routine.  Otherwise this routine 
   # is currently just like process_failure; they are separate in case 
   # additional data can be extracted from the different records

   if ($processing_inchan =~ /^printer/io) {
      $printer_failures++;
   }
   elsif ($processing_inchan =~ /^pager/io) {
      $pager_failures++;
   }
   elsif ($processing_inchan =~ /^g3/io) {
      $fax_failures++;
   }
   elsif ($processing_inchan =~ /^IMAP/io) {
      &process_connfail;
   }
   elsif ($processing_inchan =~ /POP3/io) {
      # successful login, ignore for now, bfc ; this was fixed in a later
      # version of POP, but let's make allowances..
      ;
   }
   elsif ($processing_inchan =~ /x400/io) {
      # ignore for now, probably just an aborted connection
      ;
   }
   else {
      print "Unexpected channel $processing_inchan in A record at line $line_count\n";
   }
}

sub process_max_fax {
   # M records are generated when the FAX channel finally gives up
   # trying to deliver faxen.

    my ($address, $other);

    ($address, $other) = split / /o, $rest, 2;

   if ($processing_inchan =~ /^g3/io) {
      $fax_max_failures++;
   }
   else {
      print "Unexpected channel $processing_inchan in M record at line $line_count\n";
   }
}

sub process_etrn {
   my ($sourceip, $tmp, $domain);

    $processing_conninfo =~ 
        /(^TCP\|)(\d+\.\d+\.\d+\.\d+\|)(\d+\|)(\d+\.\d+\.\d+\.\d+)(\|.)/o;
    $sourceip = $4;
    ($tmp, $domain) = split / /o, $rest, 2;
    $etrn_count++;
    $etrn_domains{$sourceip}{$domain}++;
}

sub process_reject {
   $rejected_messages{$processing_inchan}++;
}

sub process_warning {
   $warning_cnt{$processing_inchan}++;
}

sub process_open {
   my ($sourceip);

   $processing_conninfo =~ 
        /(^TCP\|)(\d+\.\d+\.\d+\.\d+\|)(\d+\|)(\d+\.\d+\.\d+\.\d+)(\|.)/o;
   $sourceip = $4;
   $conn_open{$processing_server}++;
   $conn_open_source{$processing_server}{$sourceip}++;
}

sub process_close {
   my ($sourceip);

   $processing_conninfo =~ 
        /(^TCP\|)(\d+\.\d+\.\d+\.\d+\|)(\d+\|)(\d+\.\d+\.\d+\.\d+)(\|.)/o;
   $sourceip = $4;
   $conn_close{$processing_server}++;
   $conn_close_source{$processing_server}{$sourceip}++;
}

sub process_connreject {
   my ($sourceip);

   $processing_conninfo =~ 
        /(^TCP\|)(\d+\.\d+\.\d+\.\d+\|)(\d+\|)(\d+\.\d+\.\d+\.\d+)(\|.)/o;
   $sourceip = $4;
   $conn_reject{$processing_server}++;
   $conn_reject_source{$processing_server}{$sourceip}++;
}

sub process_connfail {
   my ($sourceip);

   $processing_conninfo =~ 
        /(^TCP\|)(\d+\.\d+\.\d+\.\d+\|)(\d+\|)(\d+\.\d+\.\d+\.\d+)(\|.)/o;
   $sourceip = $4;
   $conn_failed{$processing_server}++;
   $conn_fail_source{$processing_server}{$sourceip}++;
}
#
#  MAIN PROGRAM BEGINS
#
#  Note: if this process_table gets additions, you must also edit the
#  RECORD TYPE CHECK if statement further below.
#  
my %process_table = (
   'E' => \&process_enqueue,
   'D' => \&process_dequeue,
   'Q' => \&process_requeue,
   'Z' => \&process_requeue,
   'S' => \&process_success,
   'F' => \&process_failure,
   'A' => \&process_abort,
   'R' => \&process_reject,
   'O' => \&process_open,
   'C' => \&process_close,
   'X' => \&process_connreject,
   'Y' => \&process_connfail,
   'J' => \&process_reject,
   'W' => \&process_warning,
   'M' => \&process_max_fax,
   'I' => \&process_etrn,
);

# List of valid records follows.  To add support for new or unsupported
# record types, just add the type to the list and add a processing
# routine to %process_table.  Also change the pattern below where we
# check for the record type we found.  The fact that you have to change
# 3 things might indicate that this could be done better...

my @valid_records = ('E','D','Q','Z','S','F','A','R','O','C','X','Y','J','W','M','I');
my %valid_records;
my ($date_printed, $rectype, $last_record, $i);

$debug = $ENV{PMDFLOGPLDEBUG};
print "Debugging is on\n" if $debug;
print ">>> imslog.pl                v: 3-MAR-2000\n";

# Initialize the size bins

%size_bins = (
   '1K' => 0,
   '2K' => 0,
   '3K' => 0,
   '4K' => 0,
   '5K' => 0,
   '6K' => 0,
   '<=10K'     => 0,
   '10K - 100K'   => 0,
   '100K - 1M'    => 0,
   '1M - 10M'     => 0,
   'Over 10M'     => 0,
);
@bin_names = ('1K',
	      '2K',
	      '3K',
	      '4K',
	      '5K',
	      '6K',
              '<=10K',
              '10K - 100K',
              '100K - 1M',
              '1M - 10M',
              'Over 10M',);

@delay_bin_names = ('<2 sec',
                    '<10 sec',
                    '<1 min',
                    '<5 mins',
                    '<1 hour',
                    '>1 hour');

@retry_bin_names = ('1st time',
                    '2nd time',
                    '<5 times',
                    '>5 times');

# Construct a hash to use when computing number of retries

$i=25;
for ('A'..'Z') {
   $retry_num{$_} = $i--;
}


# Construct a hash of valid record types.  This speeds up record type
# validation.
for (@valid_records) {
   $valid_records{$_}++;
}

# Keep track of information by day.  Even "single day" logs often have
# remnants of the previous day in them.

$last_date = '1-JAN-1900';
$generate_mail_report = 0;
$generate_connection_report = 0;

log_read:
while (<>) {

    # Single-digit days have a space prepended.  Take it off.
    s/^ //;

    $line_count++;

    # One reasonably consistent feature of the log is that the date and time
    # seem to always be the first two fields.
    ($processing_date, $processing_time, $rest) = split / /o,$_,3;
    if (!$date_printed) {
        print "\n Log started on $processing_date at $processing_time\n";
        $date_printed = 1;
    }

    # RECORD TYPE CHECK
    # We'll assume that a single upper-case character in the set of things
    # we regard as valid record types occuring between two spaces is a real
    # record type identifier

    # PMDF v6.0 change -- record types are no longer always a single 
    # character.  E, D, and Q records can be followed by A (authenticated)
    # and/or S (secure)

    if (/(\s+)([EDQZSFAROCXYJWMI]|[EDQ][AS]+)(\s+)/o) {
        $processing_rectype = $2;
    } else  {
        print "Found an unsupported record type in line $line_count\n";
        print "$_\n" if $debug;
        next log_read;
    }

    # Now that we know what the record type is find out what field 
    # number it is.  This is required since we do not know if we have
    # to deal with a host and/or a pid.  Note we start at 2 because
    # we have already pulled off the date and time.
    $processing_rectype_fld_no = 2;
  find_rectype:
    while (1==1) {
        $processing_rectype_fld_no++;
        ($token[$processing_rectype_fld_no],$rest) = split /\s+/io,$rest,2;
        if ($token[$processing_rectype_fld_no] eq $processing_rectype) 
            {last find_rectype;}
        if ($processing_rectype_fld_no > 7) {
            print "Unknown record type at line $line_count\n";
            next log_read;
        }
    }

    $connection_record = 0;
    if ($processing_rectype =~ "D") {
        # dequeue records have one less field
        $processing_inchan = "";
        dequeue_field_case: {

          if ($processing_rectype_fld_no == 6) {
            # both a host name and a pid are present
            $processing_host = $token[3];
            $processing_pid = $token[4];
            $processing_outchan = $token[5];
            last dequeue_field_case;}

          if ($processing_rectype_fld_no == 4) {
            # neither host or pid are present
            $processing_host = "";
            $processing_pid = "";
            $processing_outchan = $token[3];
            last dequeue_field_case;}

          if ($token[3] =~ /^([a-f0-9]*\.[a-f0-9])/io) {
            # looks like a pid, so that is what we guess
            $processing_host = "";
            $processing_pid = $token[3];
            $processing_outchan = $token[4];
            last dequeue_field_case;}

          # doesn't look like a pid, must be a host
          $processing_host = $token[3];
          $processing_pid = "";
          $processing_outchan = $token[4];
        }
    } elsif (($processing_rectype  eq "Q") 
            || ($processing_rectype  eq "Z")
            || ($processing_rectype  eq "J")
            || ($processing_rectype  eq "R")
            || ($processing_rectype  eq "W")) {

        # delivery attempts have one less field as well
        $processing_outchan = "";
        dequeue_field_case: {

          if ($processing_rectype_fld_no == 6) {
            # both a host name and a pid are present
            $processing_host = $token[3];
            $processing_pid = $token[4];
            $processing_inchan = $token[5];
            last dequeue_field_case;}

          if ($processing_rectype_fld_no == 4) {
            # neither host or pid are present
            $processing_host = "";
            $processing_pid = "";
            $processing_inchan = $token[3];
            last dequeue_field_case;}

          if ($token[3] =~ /^([a-f0-9]*\.[a-f0-9])/io) {
            # looks like a pid, so that is what we guess
            $processing_host = "";
            $processing_pid = $token[3];
            $processing_inchan = $token[4];
            last dequeue_field_case;}

          # doesn't look like a pid, must be a host
          $processing_host = $token[3];
          $processing_pid = "";
          $processing_inchan = $token[4];
        }

    } else {
        $connection_record = 1 if (($processing_rectype eq "O")
                                || ($processing_rectype eq "C")
                                || ($processing_rectype eq "I")
                                || ($processing_rectype eq "X")
                                || ($processing_rectype eq "Y"));
        field_case: {

          if ($processing_rectype_fld_no == 7) {
            # both a host name and a pid are present
            $processing_host = $token[3];
            $processing_pid = $token[4];
            if ($connection_record) {
                $processing_server = $token[5];
                $processing_inchan = "";
                $processing_outchan = "";
            } else {
                $processing_server = "";
                $processing_inchan = $token[5];
                $processing_outchan = $token[6];
            }
            last field_case;}

          if ($processing_rectype_fld_no == 5) {
            # neither host or pid are present
            $processing_host = "";
            $processing_pid = "";
            if ($connection_record) {
                $processing_server = $token[3];
                $processing_inchan = "";
                $processing_outchan = "";
            } else {
                $processing_server = "";
                $processing_inchan = $token[3];
                $processing_outchan = $token[4];
            }
            last field_case;}

          if ($token[3] =~ /^([a-f0-9]*\.[a-f0-9])/io) {
            # looks like a pid, so that is what we guess
            $processing_host = "";
            $processing_pid = $token[3];
            if ($connection_record) {
                $processing_server = $token[4];
                $processing_inchan = "";
                $processing_outchan = "";
            } else {
                $processing_server = "";
                $processing_inchan = $token[4];
                $processing_outchan = $token[5];
            }
            last field_case;}

          # doesn't look like a pid, must be a host
          $processing_host = $token[3];
          $processing_pid = "";
          if ($connection_record) {
                $processing_server = $token[4];
                $processing_inchan = "";
                $processing_outchan = "";
          } else {
                $processing_server = "";
                $processing_inchan = $token[4];
                $processing_outchan = $token[5];
          }
        }
    }

    if ($connection_record) {
        # Get rid of the annoying NULL in IMAP records.  Note that this was
        # a bug in the logging software, but it does occur in some versions
        # of PMDF
        $rest =~ s/\0/ /g;
        $processing_server =~ s/\0/ /g;
        ($processing_conninfo, $rest) = split /\s/io,$rest,2;
        $processing_size = "";
        $generate_connection_report = 1;
    } else {
        $processing_server = "";
        $processing_conninfo = "";
        ($processing_size, $rest) = split /\s/io,$rest,2;
        if (!($processing_inchan =~ /^IMAP/io) &
                !($processing_inchan =~ /POP3/io) &
                ! ($processing_inchan =~ /x400/io)) {
            $generate_mail_report = 1;
        }
    }

   # Bump the record type counter (we don't do anything we this, but
   # we keep it anyway).  Then call the appropriate routine to deal with
   # this record.

   $rectypes{$processing_rectype}++;

   # The hash test is just paranoia; it could be removed.  Since we
   # checked for a valid record type above, we shouldn't get here unless
   # we have a handling routine for this record.  Still...

   # PMDF v6.0 change -- the substr() fxn is used in case we have a 
   # D or E record with an A and/or S appended.

   if ($valid_records{substr ($processing_rectype, 0, 1)}) {
      &{$process_table{substr ($processing_rectype, 0, 1)}};
   }
   else {
      print "There's no processing routine for $processing_rectype records\n";
   }
   $last_record = $_;
}

$last_record =~ s/^\s+//;
($processing_date, $processing_time, $rest) = split / /o, $last_record;
print " Log ended on $processing_date at $processing_time\n\n";

if ($generate_mail_report)       {print_mail_report();}
if ($generate_connection_report) {print_connection_report();}
#
# MAIN PROGRAM ENDS
#

# All the report printing stuff can probably be done better with formats,
# but I read through the format part in the camel book and came away
# baffled.  So converting this over to using formats is left as an
# exercise for the reader.

sub print_mail_report {

   my ($key, $channel, $avg_delay, $avg_delay_string, $max_delay_string,
       $i, $host, $server, $source, $hostname, $msg_total, $header_printed,
       $first, $sender, $number, $tmp, $last);
   my (@all_servers);
   my (%printed);

   print "\n\n Enqueue Statistics\n\n";
   printf "%15s %8s %14s %8s %14s\n", "Channel","Messages","             ",
                                      "Messages", "             ";
   printf "%15s %8s %14s %8s %14s\n", " Name  ","Q'd from","Size (Kbytes)",
                                      "Q'd to  ", "Size (Kbytes)";
   printf "%15s %8s %14s %8s %14s\n", '-'x7, '-'x8, '-'x13, '-'x8, '-'x13;

   foreach $key (sort keys %in_messages) {
      printf "%15s %8d %14d %8d %14d\n",
       $key, $in_messages{$key}, $in_size{$key},
       ($into_chan{$key} ? $into_chan{$key} : 0),
       ($into_size{$key} ? $into_size{$key} : 0);
      $printed{$key} = ' ';
   }

#   print "\n\n Messages Enqueued to:\n\n";
#   printf "%15s %8s %14s\n", "Channel","Messages","Size (Kbytes)";
#   printf "%15s %8s %14s\n", '-'x7, '-'x8, '-'x13;
   foreach $key (sort keys %into_chan) {
      if (!$printed{$key}) {
         printf "%15s %8d %14d %8d %14d\n",
          $key, 0, 0, $into_chan{$key}, $into_size{$key};
      }
   }

   print "\n\n Dequeue statistics:\n\n";
   printf "%15s %8s %14s\n", "Channel","Messages","Size (Kbytes)";
   printf "%15s %8s %14s\n", '-'x7, '-'x8, '-'x13;
   foreach $key (sort keys %out_messages) {
      printf "%15s %8d %14d\n", $key, $out_messages{$key}, $out_size{$key};
   }

   print "\n\n Secure connections:\n\n";
   print "$secure_connections{Auth} connections were authenticated\n";
   print "$secure_connections{Secure} connections were secure\n";

   print "\n\n Trouble statistics:\n\n";
   printf "%15s %13s\n", "Channel", "Retry Entries";
   printf "%15s %13s\n", '-'x7, '-'x13;
   foreach $key (sort keys %failed_messages) {
      printf "%15s %13d\n", $key, $failed_messages{$key};
   }

   print "\n Top 10 requeues by domain/user:\n\n";
   printf "  %11s %35s\n", 'Domain/User', 'Num Retries';
   printf "  %11s %35s\n", '-'x11, '-'x11;
   $i = 1;
   foreach $key (sort bynum keys %bad_domains) {
      printf "  %-35.35s %d\n", $key, $bad_domains{$key};
      last if $i++ == 10;
   }

   if (keys %rejected_messages) {
      print "\n\n Rejected Messages \n\n";
      printf "%15s %14s\n", "Channel", "Reject Entries";
      printf "%15s %14s\n", '-'x7, '-'x14;
      foreach $key (sort keys %rejected_messages) {
         printf "%15s %14d\n", $key, $rejected_messages{$key};
      }
   }

   if (keys %warning_cnt) {
      print "\n\n Warnings \n\n";
      printf "%15s %14s\n", "Channel", "Warnings";
      printf "%15s %14s\n", '-'x7, '-'x14;
      foreach $key (sort keys %warning_cnt) {
         printf "%15s %14d\n", $key, $warning_cnt{$key};
      }
   }

   if ($printer_successes || $mailserv_successes ||
       $printer_failures || $mailserv_failures) {
      print "\n\n Mailserv, Pager, and Printer Channels:\n\n";

      print " There were $printer_successes successful uses of the printer channel\n"
         if $printer_successes;

      print " There were $printer_failures attempts to use the printer channel that failed\n"
            if $printer_failures;

      print " There were $pager_successes successful uses of pager channels\n"
            if $pager_successes;

      print " There were $pager_failures attempts to use pager channels that failed\n"
            if $pager_failures;

      if ($mailserv_successes) {
         print " There were $mailserv_successes successful uses of the mailserv channel\n";
         printf " Users were from %d different domains.  Top 10 domains follow.\n\n", scalar keys %mailserv_users;
         printf "  %6s %40s\n", 'Domain', 'Num Accesses';
         printf "  %6s %40s\n", '-'x6, '-'x12;
         $i=1;
         foreach $key (sort byuse keys %mailserv_users) {
            printf "  %-35.35s %d\n", $key, $mailserv_users{$key};
            last if $i++==10;
         }
      }
      print "\nThere were $mailserv_failures attempts to use the mailserv channel that failed\n"
         if $mailserv_failures;
   }

   if ($fax_successes || $fax_failures || $fax_max_failures) {
    print "\n FAX Summary\n\n";
   }
   if ($fax_failures > 0) {
      printf "%4s %4s %-60s\n",
        " ", $fax_failures, "delivery attempts by FAX channels failed.";
   }
   if ($fax_max_failures > 0) {
      printf "%4s %4s %-60s\n",
        " ", $fax_max_failures, "FAXes exceeded the maximum number of delivery attemps.";
   }
   if ($fax_successes > 0) {
      printf "%4s %4s %-60s\n",
        " ", $fax_successes, "FAXes were successfully delivered.";

# Uncomment the following if you want more fax details
#      print "\n";
#      printf "%7s %32s %6s %-24s\n", 
#            " ", "Source", "Count", "Destination";
#      printf "%7s %32s %6s %-24s\n", 
#            " ", "-"x32, "-"x6 , "-"x24;
#      foreach $sender (sort keys %fax_destinations) {
#        $last = " ";
#        foreach $number (keys %{$fax_destinations{$sender}}) {
#            $tmp = $sender;
#            if ($sender eq $last) {$tmp = " ";}
#            printf "%7s %32s %6s %-24s\n",
#                    " ", $sender, $fax_destinations{$sender}{$number}, $number;
#            $last = $sender;
#        }
#      }

# comment the following if you are printing more fax details
      $i=1;
      print "\n";
      printf "%7s %s\n\n", " ", "Top 10 FAX users where:";
      printf "%7s %32s %6s\n", 
        " ", "Source", "Count";
      printf "%7s %32s %6s\n", 
        " ", "-"x32, "-"x6;
      foreach $key (sort byfaxsource keys %fax_senders) {
        printf "%7s %32s %6s\n", 
            " ", $key, $fax_senders{$key};
        last if $i++ == 10;
      }

   }

   print "\n\n Messaging Summary \n\n";
   print " $total_dequeues messages were dequeued.  Total size was $total_dequeue_size Kbytes\n";

   print "\n Message sizes:\n\n";
   foreach $key (@bin_names) {
      printf "  %12.12s: %d\n", $key, $size_bins{$key};
   }

   $i=1;
   foreach $key (sort bycount keys %giant_messages) {
      if ($i == 1) {
         print "\n Top 10 sources of large messages (>1M)\n\n";
         printf "  %6s %57s\n", 'Originator', 'Num Enqueues';
         printf "  %6s %57s\n", '-'x10, '-'x12;
      }
      printf "  %-55.55s %d\n", $key, $giant_messages{$key};
      last if $i++ == 10;
   }

   print "\n\n  Message load by channel (dequeues):\n\n";
   printf "%15s   %8s\n", "Channel","% of dequeues";
   printf "%15s   %8s\n", '-'x7, '-'x13;
   foreach $key (sort {$out_messages{$b} <=> $out_messages{$a}}
                    keys %out_messages) {
      printf "%15s   %5.2f%%\n", $key, ($out_messages{$key}/$total_dequeues)*100.00;
   }
   printf "\n\n Message load by day (dequeues):\n\n";
   printf "%15s  %8s\n", "Date    ","# of dequeues";
   printf "%15s  %8s\n", '----    ', '-'x13;
   while ($tmp = pop @all_dates) {
      printf "%15s   %8d\n", $tmp, $dequeues_by_date{$tmp};
   }

   print "\n\n Message load by hour (dequeues):\n\n";
   printf "%15s  %8s     %15s   %8s\n",
          "Hour    ","# of dequeues","Hour   ","# of dequeus";
   printf "%15s  %8s     %15s   %8s\n",
          '----    ','-------------','----   ','------------';
   for ($i=0; $i<12; $i++) {
      printf "%10.2d   %11d     %17.2d   %11d\n", $i, $dequeues_per_hour[$i],
             $i+12, $dequeues_per_hour[$i+12];
   }

   printf "\n\n Message delays (time from enqueue to dequeue):\n\n";
   printf "  %14.14s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s\n",
          "Channel   ","<2 sec ","<10 sec","<1 min ","<5 mins","<1 hour",">1 hour","Max  ";
   printf "  %14.14s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s\n",
          '-'x14,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7;
   foreach $channel (sort keys %delay_bins_chan) {
      printf "  %14.14s", $channel;

      # We print out a percentage, so total up the number of messages first
      # then compute the percentage of the total in a second loop

      $msg_total = 0;
      foreach $key (@delay_bin_names) {
         $msg_total += $delay_bins_chan{$channel}{$key};
      }
      foreach $key (@delay_bin_names) {
         printf " %5.1f%% ", ($delay_bins_chan{$channel}{$key}/$msg_total)*100;
      }
      if ($max_chan_delay{$channel} > 3600) {
         $max_delay_string = sprintf "%2.2f hours", $max_chan_delay{$channel}/3600;
      }
      elsif ($max_chan_delay{$channel} > 60) {
         $max_delay_string = sprintf "%2.2f mins", $max_chan_delay{$channel}/60;
      }
      else {
         $max_delay_string = sprintf "%d secs", $max_chan_delay{$channel};
      }
      print " $max_delay_string\n";
   }

   printf "\n\n Message transit times\n    Time from enqueue to dequeue, top 20 hosts by total transit time):\n\n";
   printf "  %14.14s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s\n",
          "Host      ","<2 sec ","<10 sec","<1 min ","<5 mins","<1 hour",">1 hour","Max  ";
   printf "  %14.14s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s %7.7s\n",
          '-'x14,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7,'-'x7;
   $i = 1;
   foreach $host (sort bydelay keys %total_host_delay) {
      last if $i++ == 20;
      printf "  %s\n  %14.14s", $host, ' 'x14;
      $msg_total = 0;
      foreach $key (@delay_bin_names) {
         $msg_total += $delay_bins_host{$host}{$key};
      }
      foreach $key (@delay_bin_names) {
         printf " %5.1f%% ", ($delay_bins_host{$host}{$key}/$msg_total)*100;
      }
      if ($max_host_delay{$host} > 3600) {
         $max_delay_string = sprintf "%2.2f hours", $max_host_delay{$host}/3600;
      }
      elsif ($max_host_delay{$host} > 60) {
         $max_delay_string = sprintf "%2.2f mins", $max_host_delay{$host}/60;
      }
      else {
         $max_delay_string = sprintf "%d secs", $max_host_delay{$host};
      }
      print " $max_delay_string\n";
   }

   print "\n\n Retry counts\n\n";
   printf " %14.14s %9.9s %9.9s %9.9s %9.9s\n",
          "Channel   ","1st time ","2nd time ","<5 times ",">5 times ";
   printf " %14.14s%9.9s %9.9s %9.9s %9.9s\n",
          '-'x14,'-'x8,'-'x8,'-'x8,'-'x8;
   foreach $channel (sort keys %retries_done) {
      printf " %14.14s", $channel;
      foreach $key (@retry_bin_names) {
         printf "%9d ", $retries_done{$channel}{$key};
      }
      print "\n";
   }

}

sub print_connection_report {

   my ($key, $channel, $avg_delay, $avg_delay_string, $max_delay_string,
       $i, $host, $server, $source, $hostname, $msg_total, $header_printed,
       $first, $sender, $number, $tmp, $last);
   my (@all_servers);
   my (%printed);

   print "\n\n >> Server Summary <<\n\n";

   print "\n\n  Connections:\n\n";
   printf " %-15.15s %-6.6s %-6.6s %-8.8s %-6.6s\n", "Server", "Opened",
    "Closed", "Rejected", "Failed";
   printf " %-15.15s %-6.6s %-6.6s %-8.8s %-6.6s\n",
     '-'x6, '-'x6, '-'x6, '-'x8, '-'x6;
   foreach $key (keys %conn_open) {
      next if grep /$key/, @all_servers;
      push @all_servers, $key;
   }
   foreach $key (keys %conn_close) {
      next if grep /$key/, @all_servers;   # had to do this to grep
                                           # a '+', may miss other keys?
      push @all_servers, $key;
   }
   foreach $key (keys %conn_reject) {
      next if grep /$key/, @all_servers;
      push @all_servers, $key;
   }
   foreach $key (keys %conn_failed) {
      next if grep /$key/, @all_servers;
      push @all_servers, $key;
   }

   foreach $server (@all_servers) {
      printf " %-15.15s %6d %6d %8d %6d\n", $server,
             (exists $conn_open{$server}) ? $conn_open{$server} : 0,
             (exists $conn_close{$server}) ? $conn_close{$server} : 0,
             (exists $conn_reject{$server}) ? $conn_reject{$server} : 0,
             (exists $conn_failed{$server}) ? $conn_failed{$server} : 0;
   }

   if ($etrn_count) {
    print "\n ERTN request sources:\n\n";
    printf "    %6s %-17s %-48s\n",
        "Count", "    Source IP", "Requested Domain";
    printf "    %6s %-17s %-48s\n",
        '-'x6, '-'x17, "-"x48;
    foreach $source (sort keys %etrn_domains) {
       foreach $tmp (sort keys %{$etrn_domains{$source}}) {
        printf "    %6s %-17s %-48s\n",
            $etrn_domains{$source}{$tmp}, "[".$source."]", $tmp;
       }
    }
   }

   $first = 1;
   foreach $server (sort keys %conn_open_source) {
      print "\n Connection open sources:\n" if $first;
      $first = 0;
      print "\n  $server\n";
      printf "  %s\n", '-' x length($server);
#      if ($^O eq "VMS") {
#       foreach $source (sort keys %{$conn_open_source{$server}}) {
#         print "    [$source]: \t$conn_open_source{$server}{$source}\n";
#       }
#      } else {
       foreach $source (sort keys %{$conn_open_source{$server}}) {
         $hostname = gethostbyip ($source);
         printf ("    %17.17s %-32s %7s\n", "[".$source."]", $hostname, 
                        $conn_open_source{$server}{$source});
#       }
      }
   }

   $first = 1;
   foreach $server (sort keys %conn_close_source) {
      print "\n\n Connection close sources:\n" if $first;
      $first = 0;
      print "\n  $server\n";
      printf "  %s\n", '-' x length($server);
#      if ($^O eq "VMS") {
#       foreach $source (sort keys %{$conn_close_source{$server}}) {
#         print "    [$source]: \t$conn_close_source{$server}{$source}\n";
#       }
#      } else {
       foreach $source (sort keys %{$conn_close_source{$server}}) {
         $hostname = gethostbyip ($source);
         printf ("    %17.17s %-32s %7s\n", "[".$source."]", $hostname, 
                        $conn_close_source{$server}{$source});
#       }
      }
   }

   $first = 1;
   foreach $server (sort keys %conn_reject_source) {
      print "\n Connection reject sources:\n" if $first;
      $first = 0;
      print "\n  $server\n";
      printf "  %s\n", '-' x length($server);
#      if ($^O eq "VMS") {
#       foreach $source (sort keys %{$conn_reject_source{$server}}) {
#         print "    [$source]: \t$conn_reject_source{$server}{$source}\n";
#       }
#      } else {
       foreach $source (sort keys %{$conn_reject_source{$server}}) {
         $hostname = gethostbyip ($source);
         printf ("    %17.17s %-32s %7s\n", "[".$source."]", $hostname, 
            $conn_reject_source{$server}{$source});
#       }
      }
   }

   $first = 1;
   foreach $server (sort keys %conn_fail_source) {
      print "\n Connection fail sources:\n" if $first;
      $first = 0;
      print "\n  $server\n";
      printf "  %s\n", '-' x length($server);
#      if ($^O eq "VMS") {
#       foreach $source (sort keys %{$conn_fail_source{$server}}) {
#         print "    [$source]: \t$conn_fail_source{$server}{$source}\n";
#       }
#      } else {
       foreach $source (sort keys %{$conn_fail_source{$server}}) {
         $hostname = gethostbyip ($source);
         printf ("    %17.17s %-32s %7s\n", "[".$source."]", $hostname, 
            $conn_fail_source{$server}{$source});
#       }
      }
   }
}

# Utility routines follow

# timeinsecs takes a date and time as found in the log file (nn-MMM-yyyy)
# and (hh:mm:ss) and computes a seconds since 1970 value

sub timeinsecs {
   my $date = shift;
   my $time = shift;
   my ($hours, $mins, $secs, $day, $mon, $year, $monthnum, $computed_time);

   my %monthnums = ("JAN" => 0,
                    "FEB" => 1,
                    "MAR" => 2,
                    "APR" => 3,
                    "MAY" => 4,
                    "JUN" => 5,
                    "JUL" => 6,
                    "AUG" => 7,
                    "SEP" => 8,
                    "OCT" => 9,
                    "NOV" => 10,
                    "DEC" => 11);

   ($hours, $mins, $secs) = split /:/, $processing_time;
   # truncate any fractional seconds
   $secs =~ s/\..*//;
   ($day, $mon, $year) = split /-/, $processing_date;
   $monthnum = $monthnums{$mon};

   if ($day < 1 or $day > 31) {
      print "Found bogus day $day at line $line_count\n";
      $day = 1;
   }
   $computed_time = timelocal($secs, $mins, $hours, $day, $monthnum, $year);

   return $computed_time;
}

# gethostbyip takes a dot-separated IP address (10.20.30.40) as an
# argument and returns the associated host name

sub gethostbyip {
   my $ipaddr = shift;
   my ($a, $b, $c, $d, $hostname);

   ($a, $b, $c, $d) = split /\./, $ipaddr;
   $hostname = gethostbyaddr (pack ("c4", $a,$b,$c,$d), 2);
   if ($hostname) {
      return $hostname;
   } else {
      return "Unknown";
   }
}

# A couple of comparison routines to use in sorts:

sub bynum {
   $bad_domains{$b} <=> $bad_domains{$a}
      or
   $a cmp $b;
}
sub byuse {
   $mailserv_users{$b} <=> $mailserv_users{$a}
      or
   $a cmp $b;
}
sub bydelay {
   $total_host_delay{$b} <=> $total_host_delay{$a}
      or
   $a cmp $b;
}
sub bycount {
   $giant_messages{$b} <=> $giant_messages{$a}
      or
   $a cmp $b;
}
sub byfaxsource {
   $fax_senders{$b} <=> $fax_senders{$a}
      or
   $a cmp $b;
}


