--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/check_exim.pl Wed Dec 19 21:24:08 2007 +0000
@@ -0,0 +1,246 @@
+#! /usr/bin/perl
+# $Id$
+# $URL$
+
+use strict;
+use warnings;
+use File::Basename;
+use Getopt::Long;
+use Fatal qw(:void open flock seek truncate);
+use Fcntl qw(:flock);
+use if $ENV{DEBUG} => "Smart::Comments";
+use Storable;
+
+my $ME = basename $0;
+my $STATE_DIR = "/var/lib/nagios3/$ME";
+my $STATE_FILE = "$STATE_DIR/state";
+my $CACHE_FILE = "$STATE_DIR/exim";
+our $VERSION = "1.0";
+
+delete @ENV{ grep /PATH/, keys %ENV };
+$ENV{PATH} = "/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin";
+
+my %TRANSPORT = (lmtp => "local",
+ pipe => "local",
+ appendfile => "local",
+ autoreply => "local",
+ smtp => "remote",
+);
+
+my %opt = (init => undef,
+ warn => undef,
+ crit => undef,
+ update => 1,
+);
+
+my %exim;
+my %state = (time => 0,
+ inode => { mainlog => 0 },
+ offset => { mainlog => 0 },
+ counter => { transport => {},
+ protocol => {},
+ in => { local => 0, remote => 0 },
+ out => { local => 0, remote => 0 },
+ },
+);
+
+sub init();
+
+MAIN: {
+
+ GetOptions("init=s" => \$opt{init},
+ "warn=s" => \$opt{warn}, # rate mails/h
+ "crit=s" => \$opt{crit}, # rate mails/h
+ "update!" => \$opt{update},
+ ) or die "Bad Usage\n";
+
+ $opt{init} and do { init(); exit };
+
+ # read the exim (cache) file and the status
+ die "$0: $CACHE_FILE not found ($!). Try $0 --help\n"
+ if not -r $CACHE_FILE;
+ die "$0: $STATE_FILE not found ($!). Try $0 --help\n"
+ if not -r $STATE_FILE;
+ %exim = %{ $_ = retrieve($CACHE_FILE) };
+ %state = %{ $_ = retrieve($STATE_FILE) };
+
+ ### %exim
+ ### %state
+
+ {
+ my @files;
+
+ # it's important to have the real mainlog last!
+ if ($state{inode}{mainlog} == 0) {
+ ### never seen an inode
+ push @files, [ $exim{mainlog} => 0 ];
+ }
+ elsif ($state{inode}{mainlog} != (stat $exim{mainlog})[1]) {
+ ### inum changed
+ if ((my $moved_mainlog)
+ = grep { (stat)[1] == $state{inode}{mainlog} }
+ glob(dirname($exim{mainlog}) . "/*"))
+ {
+ ### found old one: $moved_mainlog
+ push @files, [ $moved_mainlog => $state{offset}{mainlog} ];
+ }
+ push @files, [ $exim{mainlog} => 0 ];
+ }
+ else {
+ push @files, [ $exim{mainlog} => $state{offset}{mainlog} ];
+ }
+
+ my $now = time();
+ my $dt = $now - $state{time};
+ my %out = (local => 0, remote => 0);
+ my %in = (local => 0, remote => 0);
+ my (%transport, %protocol);
+ FILE: foreach (@files) {
+ my ($file, $offset) = @$_;
+ open(my $fh, $file) or do {
+ warn "open $file: $!\n";
+ next FILE;
+ };
+ seek($fh, $offset, 0) or do {
+ warn "seek $file to offset $offset: $!\n";
+ next FILE;
+ };
+ while (<$fh>) {
+ if (/^\S+ \S+ (?:\S+ )?\S+ ?> .* T=(\S+)/) {
+ $transport{$1}++;
+ $out{ $exim{transport}{$1} }++;
+ next;
+ }
+ if (/^\S+ \S+ (?:\S+ )?\S+ <= .* P=(\S+)/) {
+ $protocol{$1}++;
+ $in{ $1 =~ /smtp/ ? "remote" : "local" }++;
+ next;
+ }
+ }
+
+ # will then hold the values from the *last*, that's why
+ # above I said that the order matters
+ $state{offset}{mainlog} = tell($fh);
+ $state{inode}{mainlog} = (stat $fh)[1];
+ }
+
+ # foreach (keys %transport) {
+ # print "$_: $transport{$_}\n";
+ # }
+ # print "\n";
+
+ # save status - status contains absolute counters
+ $state{time} = $now;
+ $state{counter}{in}{remote} += $in{remote};
+ $state{counter}{in}{local} += $in{local};
+ $state{counter}{out}{remote} += $out{remote};
+ $state{counter}{out}{local} += $out{local};
+
+ foreach (keys %transport) {
+ $state{counter}{transport}{$_} = 0
+ if not defined $state{counter}{transport}{$_};
+ $state{counter}{transport}{$_} += $transport{$_};
+ }
+
+ foreach (keys %protocol) {
+ $state{counter}{protocol}{$_} = 0
+ if not defined $state{counter}{protocol}{$_};
+ $state{counter}{protocol}{$_} += $protocol{$_};
+ }
+
+ store \%state, $STATE_FILE if $opt{update};
+
+ # start processing
+ my $in_tot = $in{local} + $in{remote};
+ my $out_tot = $out{local} + $out{remote};
+
+ # mails / minute
+ my $rate_in_remote = $in{remote} / $dt * 60;
+ my $rate_out_remote = $out{remote} / $dt * 60;
+ my $rate_in_local = $in{local} / $dt * 60;
+ my $rate_out_local = $out{local} / $dt * 60;
+ my $rate_in = $in_tot / $dt * 60;
+ my $rate_out = $out_tot / $dt * 60;
+
+ my $perfdata = "in_local=$state{counter}{in}{local}"
+ . " in_remote=$state{counter}{in}{remote}"
+ . " out_local=$state{counter}{out}{local}"
+ . " out_remote=$state{counter}{out}{remote}";
+
+ foreach (keys %{ $state{counter}{transport} }) {
+ $perfdata .= " $_=$state{counter}{transport}{$_}";
+ }
+
+ foreach (keys %{ $state{counter}{protocol} }) {
+ $perfdata .= " $_=$state{counter}{protocol}{$_}";
+ }
+
+ printf "OK i:%.1f(%.1f/%.1f) o:%.1f(%.1f/%.1f)|$perfdata\n", $rate_in,
+ $rate_in_local, $rate_in_remote, $rate_out, $rate_out_local,
+ $rate_out_remote;
+
+ }
+
+ exit 0;
+
+}
+
+sub init() {
+
+ # noch sind wir root (hoffentlich)
+ -d $STATE_DIR or do {
+ require File::Path;
+ import File::Path;
+ umask(022);
+ mkpath($STATE_DIR, 0, 0777);
+ };
+
+ my $uid = (getpwnam $opt{init})[2];
+ chown($uid, -1, $STATE_DIR)
+ or die "Can't chown $STATE_DIR to $opt{init} ($uid): $!\n";
+
+ umask 077;
+ $> = $uid; # EUID
+
+ # exim (logfile names) acquired from exim
+ my (%exim, @exim);
+ @exim = `exim -bP all` or `exim4 -bP all`;
+
+ my ($log) = (split " ", (grep /^\s*log_file_path\s*=/, @exim)[0])[2];
+
+ %exim = (mainlog => sprintf($log, "main"),
+ rejectlog => sprintf($log, "reject"),
+ paniclog => sprintf($log, "paniclog"),
+ );
+
+ {
+ local $/ = "";
+ @exim = `exim -bP transports` or `exim4 -bP transports`;
+ foreach (@exim) {
+ my ($transport, $driver) = /^(\S+) transport:.*^driver = (\S+)/ms;
+
+ # map the transport names to their driver and this to "local/remote"
+ $exim{transport}{$transport} = $TRANSPORT{$driver};
+ }
+
+ }
+
+ store \%exim, $CACHE_FILE;
+ store \%state, $STATE_FILE;
+
+}
+
+__END__
+
+my $FILE = "/var/log/exim4/mainlog";
+my $STATUS = "/var/run/nagios3/$ME/%s";
+
+sub unkn(@) { print "UNKNOWN @_\n"; exit 3 }
+sub crit(@) { print "CRIT @_\n"; exit 2 }
+sub warn(@) { print "WARN @_\n"; exit 1 }
+sub ok(@) { print "OK @_\n"; exit 0 }
+
+open(my $log, $FILE) or critical("file $FILE not found");
+
+__END__
+# vim:sts=4 sw=4 aw ai sm: