#!/usr/bin/perl
#
# License: GPLv2
# Todd Lyons (c) 2013

use strict;
use warnings;
use Getopt::Long;

$|=1;

sub mydie($) {
  print shift(),"\n";
  exit 1;
}

my %opts;
GetOptions( \%opts,
  'debug',
  'limit:i',
  'nopass'
);

$opts{'limit'} ||= 10;

my @ignored_emails = qw//;
my @ignored_ips = qw/^172\.(1[6789]|2\d|3[01])\. ^192.168. ^10\./;
push(@ignored_ips, &ignore_ISPs);

my $found;
my $date = &syslog_date();
my $fh = &get_handle();

while (<$fh>) {
  if ( $_ =~ /^(\d\d\d\d-\d\d-\d\d) (\d\d:\d\d:\d\d).*H=.*\[(\d+\.\d+\.\d+\.\d+)\].*A=(?:plain|login):([^ ]+)/ ) { 
    next if ( grep {/^$4$/} @ignored_emails );
    next if ( grep {$3 =~ /$_/} @ignored_ips );
    $found->{$1}->{$4}->{'ip'}->{$3}++;
    $found->{$1}->{$4}->{'lasttime'} = $2;
    $found->{$1}->{$4}->{'lastip'}   = $3;
  } 
};

for my $date  (sort keys %$found) {
  (my $logdir = "/tmp/".$date) =~ s/ /_/g;
  my $f = $found->{$date};
  for my $email (sort keys %$f) {
    next if ( not defined $opts{'debug'} ||
              scalar keys %{$f->{$email}->{'ip'}} > $opts{'limit'} );
    # Make the directory IFF found something to log
    mkdir $logdir if ( ! -d $logdir && ! $opts{'debug'});
    my $logfile = $logdir."/$email";
    if (!$opts{'debug'}) {
      next if ( -f $logfile );
      open(F,'>',$logfile) && close(F);
    }
    print "$date -> mailbox $email: (", scalar keys %{$f->{$email}->{'ip'}}, ")\n";
    print map { "  $_ => " . $f->{$email}->{'ip'}->{$_} ."\n" }
          sort keys %{$f->{$email}->{'ip'}};
    print "    Last connection from ", $f->{$email}->{'lastip'},
          " at ", $f->{$email}->{'lasttime'},
          "\n";
    next if ($opts{'debug'});
    &reset_password($email) unless($opts{'nopass'});
    print &create_admin_alert($f,$email);
  }
}

sub syslog_date {
  my $date = `date '+%Y-%m-%d'`;
  return($date);
}

sub get_handle {
  my $fh = *STDIN;
  if ( @ARGV ) {
    # filenames passed on cli, only use first one
    open($fh,'<',shift(@ARGV)) or
      mydie("Unable to open logfile: $!");
  }
  else { # ( -t STDIN ) {
    # No ARGV, use default file
    open($fh,'<','/var/log/exim/main.log') or 
      mydie("Unable to open default logfile: $!");
  }
  return $fh;
}

# Randomly blocking accounts is a good way to run off customers.  Send a
# message to yourself, and maybe the customer or their boss about what was
# found and why the mailbox "stopped working".
sub create_admin_alert {
  my $f = shift() or
    return("Unable to access data from logs\n");
  my $email = shift() or
    return("Unable to determine mailbox for alert\n");
  my $count = scalar keys %{$f->{$email}->{'ip'}} or
    return ("Unable to create alert, can't determine count for mailbox $email\n");
  my $response = "";
  # Do something here to create an alert that a hacked account was detected
  return ($response);
}

# Resetting the password will cause the smtp auth abuse to stop, but it
# will not lock the user's mailbox so it will continue to deliver
# inbound email.
sub reset_password {
  my $email = shift() or return;
  # Do something here to reset password
}

# Certain wireless carriers seem to make devices switch IP's as they
# move from tower to tower. Just ignore those problem ranges.
sub ignore_ISPs {
  my @i;
  # Google IP's
  push(@i, qw/^209.85.16[01]. ^209.85.21[2-6]. ^209.85.220\. ^74.125.82./);
  # VZ Wireless
  push(@i, qw/^50.29\. ^174.2\d\d ^174.19[2-9]/);
  # Cingular Wireless
  push(@i, qw/^198.228.19[678]/);
  # TMobile (tmodns.net)
  push(@i, qw/^208.54.36./);

  return(@i);
}
