#! /usr/bin/perl
#
# Exim stats grabber for NET-SNMP
#
# 0.01
# Matthew Newton
# Copyright (c) University of Leicester, 2005
#
# This program 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.
#
# This program 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 this program; if not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.

use strict;


# Options to set up here.
#
# $conf is where to store the current state file.
# $statistics is the file that contains the current data.
# $maillog is the latest Exim main log.
# $mainlogold is the last rotated out main log.
# $archive is a directory. If it exists then old data is written
#          there, too.

my $suffix = "";
my $conf = "/etc/snmp/scripts/exim-current-state$suffix";
my $statsfile = "/etc/snmp/scripts/exim-statistics$suffix";
my $mainlog = "/var/log/exim4/mainlog";
my $mainlogold = "/var/log/exim4/mainlog.1";
my $archive = "/etc/snmp/scripts/archive";

# The regular expressions will need tweaking below, especially the
# IP addresses that determine local or remote. This should be
# moved to a configuration file some day.


# read conf file of when last run and where we got to

my %stats = ();

# set some defaults. If the current-state file does not exist, lets
# start searching through the current mainlog from the beginning.
# Maybe in this case we should really just set $seek to be the last
# position in the file so that the beginning stats are not large?
my $seek = 0;
my $inode = inode_number($mainlog);
my $cantseek = 0;

if (-r $conf) {
  open CONF, "< $conf";
  while (<CONF>) {
    chomp;
    s/^\s*//;
    s/\s*$//;
    next if /^$/;
    if (/^inode=(\d+)$/) {
      $inode = $1;
    }
    if (/^seek=(.*)$/) {
      $seek = $1;
    }
  }
  close CONF;
}

$stats{"localin"} = 0;
$stats{"localout"} = 0;
$stats{"in"} = 0;
$stats{"out"} = 0;
$stats{"spam"} = 0;
$stats{"virus"} = 0;
$stats{"queued"} = 0;
$stats{"bounces"} = 0;
$stats{"lequeued"} = 0;

# see if we can seek to current position in the mainlog. If not, then
# it has most likely been rotated
open LOG, "< $mainlog" or die "cannot open exim log file $mainlog!";
if (!seek(LOG, $seek, 0)) {
  $cantseek = 1;
}
close LOG;

if ($inode != inode_number($mainlog) or
    $cantseek or
    $inode == inode_number($mainlogold)) {
  # we have changed to a new log file; read the previous mainlog first
  read_log($mainlogold, $seek, \%stats);
  $inode = inode_number($mainlog);
  $seek = 0;
}

$seek = read_log($mainlog, $seek, \%stats);

if (!read_queue(\%stats)) {
  $stats{"queued"} = -1;
  $stats{"bounces"} = -1;
  $stats{"lequeued"} = -1;
}

write_stats($statsfile, \%stats);

# create config file
open CONF, "> $conf";
print CONF "inode=$inode\n";
print CONF "seek=$seek\n";
close CONF;

if (defined $archive and $archive ne "" and -d $archive) {
  write_stats($archive."/".now_time(), \%stats);
}

exit;

sub now_time
{
  my @n = gmtime();
  my ($y, $m, $d) = ($n[5]+1900, $n[4]+1, $n[3]);
  my ($hr, $mn, $sc) = ($n[2], $n[1], $n[0]);
  $m = "0$m" if ($m < 10);
  $d = "0$d" if ($d < 10);
  $hr = "0$hr" if ($hr < 10);
  $mn = "0$mn" if ($mn < 10);
  $sc = "0$sc" if ($sc < 10);
  return "$y-$m-$d-$hr-$mn-$sc";
}

sub inode_number
{
  my $file = shift;
  my ($dummy, $inode) = stat($file);
  return $inode;
}

sub read_log
{
  my ($file, $seek, $stats) = @_;
  my $prevline = undef;
  my $line;
  my ($prevpos, $pos);
  local *LOG;

  open LOG, "< $file" or die "cannot open exim log file $file!";
  if (!seek(LOG, $seek, 0)) {
    close LOG;
    return $seek;
  }
  while ($line = <LOG>) {
    if (defined $prevline) {
      if ($line =~ /<=.*\s\[143.210/) {
        $$stats{"localin"}++;
      } elsif ($line =~ /[=-]>.*\s\[143.210/) {
        $$stats{"localout"}++;
      } elsif ($line =~ /<=/) {
        $$stats{"in"}++;
      } elsif ($line =~ /[=-]>/) {
        $$stats{"out"}++;
      } elsif ($line =~ /Rejected SPAM/) {
        $$stats{"spam"}++;
      } elsif ($line =~ /Caught by ClamAV/) {
        $$stats{"virus"}++;
      }
    }
    $prevline = $line;
    $prevpos = $pos;
    $pos = tell LOG;
  }
  close LOG;

  $prevpos = $seek unless defined $prevpos;

  return $prevpos;
}

sub read_queue
{
  my $stats = shift;
  my $queued = 0;
  my $bounces = 0;

  open Q, "/usr/local/bin/exim -bp |" or return 0;
  while (<Q>) {
    $$stats{"queued"}++ if (/\</);
    $$stats{"bounces"}++ if (/\<\>/);
    $$stats{"lequeued"}++ if (/^\s+[^\s@]+@([a-z0-9]+\.)?le(icester)?\.ac\.uk/);
  }
  close Q;

  return 1;
}

sub write_stats
{
  my ($file, $stats) = @_;
  local *STATS;

  open STATS, "> $file";
  print STATS <<EOF;
$$stats{"localin"}
$$stats{"localout"}
$$stats{"in"}
$stats{"out"}
$$stats{"spam"}
$$stats{"virus"}
$$stats{"queued"}
$$stats{"bounces"}
$$stats{"lequeued"}
EOF
  close STATS;
}

