#!/usr/bin/perl
#< mailmon.pl - Gather Postfix statistics from the current /var/log/maillog file
# on a simple SMTP relay.
# This script will gather statistics showing the top 10 authorised hosts relaying
# through the SMTP relay on a Postfix host. It will also show a count of relay attempts
# made from hosts that aren't authorised to use the relay.
#
# Change the $log_source and $log_dest variables as appropriate.
# Will also check against /etc/postfix/mynetworks to ensure that any host being denied
# access hasn't recently been added to the ACL. If it has, it'll ignore it.

use strict;
use warnings;
use File::Copy;

our @lines;
our $hostname;

&check_uid;
&copy_maillog;
&get_hostname;
&process_maillog;

sub check_uid() {
   my $effective_uid = $<;

   if ( $effective_uid ne 0 ) {
      printf( "<- You must be root to run this script\n" );
      exit 1;
   }
}

sub get_hostname() {
   open( HOSTNAME, "/bin/hostname |" );
   $hostname = <HOSTNAME>;
   chomp( $hostname );
   close( HOSTNAME );
}

sub copy_maillog() {
   # change the following two variables to suit your environment
   my $log_source = "/var/log/maillog";
   my $log_dest = "/home/mailmon/maillog";
   printf( "-> Copying %s to %s\n", $log_source, $log_dest );
   copy( $log_source, $log_dest ) || die ( "<- Cannot copy maillog\n" );
   printf( "-> Copy operation successful\n" );

   open( LOG, "<$log_dest" ) || die ( "<- Cannot open maillog for reading\n" );
   @lines = <LOG>;
   close( LOG );
}

sub process_maillog() {
   my $first_line;
   my $last_line;
   my $first_line_timestamp;
   my $last_line_timestamp;
   my $denied_line;
   my $count;
   my $denied_count;
   my @denied_array;
   my $accepted_count;
   my @accepted_array;
   my %clients;
   $count = 0;

   foreach ( @lines ) {
       $count++;
       $first_line = $_ if ( $count == 1 );

       # relay access denied
       if ( $_ =~ /Relay access denied/ ) {
           $denied_line = $_;
           my $denied_ip;
           #my $denied_hostname;
           #$denied_line =~ s/^.*from (.*)\[([0-9.]*)\].*$/$2 \( $1 \)/;
           $denied_ip = $denied_line;
           #$denied_hostname = $denied_line;
           $denied_ip =~ s/^.*from .*\[([0-9.]*)\].*$/$1/;
           #$denied_hostname =~ s/^.*from (.*)\[[0-9.]*\].*$/$1/;
           chomp( $denied_ip );
           #chomp( $denied_hostname );
           #$denied_array[ $denied_count++ ] = sprintf( "%-16s( %s )", $denied_ip, $denied_hostname);
           $denied_array[ $denied_count++ ] = $denied_ip;
       }
       if ( $_ =~ /client/ ) {
           my $client_line;
           my $client_message_id;
           my $client_ip;
           #my $client_hostname;
           my $client_id;
           $client_line = $_;
           $client_message_id = $client_line;
           $client_ip = $client_line;
           #$client_hostname = $client_line;
           $client_message_id =~ s/^.*smtpd\[[0-9]+\]: ([^:]+):.*$/$1/;
           $client_ip =~ s/^.*client\=[^\[]+\[([0-9.]+)\].*$/$1/;
           #$client_hostname =~ s/^.*client\=([^\[]+).*$/$1/;
           chomp( $client_message_id );
           chomp( $client_ip );
           #chomp( $client_hostname );
           #$client_id = sprintf( "%-16s( %s )", $client_ip, $client_hostname); 
           $client_id = $client_ip;
           $clients{$client_message_id} = $client_id;
       }
       if ( $_ =~ /accepted/ ) {
           my $accepted_line;
           $accepted_line = $_;
           my $accepted_message_id;
           $accepted_message_id = $accepted_line;
           $accepted_message_id =~ s/^.*smtp\[[0-9]+\]: ([^:]+):.*$/$1/;
           chomp( $accepted_message_id );
           if ( $clients{$accepted_message_id} ) {
               $accepted_array[ $accepted_count++ ] = $clients{$accepted_message_id};
           }
       }
   }
   $last_line = $lines[-1];

   # print header information
   $first_line_timestamp = $first_line;
   $last_line_timestamp = $last_line;
   $first_line_timestamp =~ s/^(.*)$hostname postfix.*$/$1/;
   $last_line_timestamp =~ s/^(.*)$hostname postfix.*$/$1/;
   chomp( $first_line_timestamp );
   chomp( $last_line_timestamp );
   printf( "\n\n-> %s maillog report\n", $hostname );
   printf( "-> Data range from %s to %s\n", $first_line_timestamp, $last_line_timestamp );

   printf( "\n-> Hosts denied access to this relay\n\n" );
   &sort_denied_array ( @denied_array );
   printf( "\n-> Top 10 authorised hosts using this relay\n\n" );
   &sort_accepted_array ( @accepted_array );
   printf( "\n" );
}

sub sort_accepted_array() {
   my @array = @_;
   my %count;
   my $good_ip;
   my $good_count;
   my $key;
   my $limiter;
   map { $count{$_}++ } @array;
   #printf( "%-10s %s\n%-10s %s\n", "Count", "Host", '='x10, '='x60 );
   printf( "%-10s %s\n%-10s %s\n", "Count", "Host", '='x10, '='x20 );
   foreach $key ( sort { $count{$b} <=> $count{$a} } keys %count ) {
      $good_ip = $key;
      $good_count = $count{$key};
      chomp( $good_ip );
      chomp( $good_count );
      printf( "%-10d %s\n", $good_count, $good_ip );
      $limiter++;
      return if ( $limiter == 10 );
   }
}

sub sort_denied_array() {
   my @array = @_;
   my %count;
   my $bad_ip;
   my $bad_count;
   my $key;
   open( MYNETWORKS, "< /etc/postfix/mynetworks" );
   my @mynetworks;
   my $flag;
   @mynetworks = <MYNETWORKS>;
   close(MYNETWORKS);
   map { $count{$_}++ } @array;
   #printf( "%-10s %s\n%-10s %s\n", "Count", "Host", '='x10, '='x60 );
   printf( "%-10s %s\n%-10s %s\n", "Count", "Host", '='x10, '='x20 );
   foreach $key ( sort { $count{$b} <=> $count{$a} } keys %count ) {
      $flag = 0;
      $bad_ip = $key;
      $bad_count = $count{$key};
      chomp( $bad_ip );
      chomp( $bad_count );
      # this check is to ensure that the host hasn't been added to the ACL
      foreach ( @mynetworks ) {
         my $mynetworksline;
         $mynetworksline = $_;
         if ( $mynetworksline =~ /^[            ]*#/ ) {
            # do nothing
         } else {
            $mynetworksline =~ s/^[ ]?([0-9.]+)$/$1/;
            chomp( $mynetworksline );
            if ( $bad_ip eq $mynetworksline ) {
               $flag = 1;
            }
         }
      }
      if ( $flag eq 0 ) {
         printf( "%-10d %s\n", $bad_count, $bad_ip );
      }
   }
}

exit 0;