- nagios plugin to check the exim in/out traffic draft
authorheiko
Wed, 19 Dec 2007 21:24:08 +0000
changeset 0 f5f2a126ebf8
child 1 e9f92c845844
- nagios plugin to check the exim in/out traffic
Makefile
check_exim.pl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Wed Dec 19 21:24:08 2007 +0000
@@ -0,0 +1,25 @@
+# $Id$
+# $URL$
+
+SCRIPTS = check_exim
+CLEANFILES = ${SCRIPTS}
+DESTDIR =
+prefix  = /usr/local
+
+plugindir = ${prefix}/lib/nagios/plugins/ius
+
+.PHONY:	all clean install
+
+all:	${SCRIPTS}
+
+clean:
+	-rm -f ${CLEANFILES}
+
+install: all
+	install -d -m 0755 ${DESTDIR}/${plugindir}
+	install -m 0755 $(SCRIPTS) ${DESTDIR}/${plugindir}/
+
+%:	%.pl
+	@perl -c $<
+	@cp -f $< $@
+	@chmod -+x $@
--- /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: