check_exim.pl
changeset 0 f5f2a126ebf8
child 2 e5c47baa6743
equal deleted inserted replaced
-1:000000000000 0:f5f2a126ebf8
       
     1 #! /usr/bin/perl
       
     2 # $Id$
       
     3 # $URL$
       
     4 
       
     5 use strict;
       
     6 use warnings;
       
     7 use File::Basename;
       
     8 use Getopt::Long;
       
     9 use Fatal qw(:void open flock seek truncate);
       
    10 use Fcntl qw(:flock);
       
    11 use if $ENV{DEBUG} => "Smart::Comments";
       
    12 use Storable;
       
    13 
       
    14 my $ME         = basename $0;
       
    15 my $STATE_DIR  = "/var/lib/nagios3/$ME";
       
    16 my $STATE_FILE = "$STATE_DIR/state";
       
    17 my $CACHE_FILE = "$STATE_DIR/exim";
       
    18 our $VERSION   = "1.0";
       
    19 
       
    20 delete @ENV{ grep /PATH/, keys %ENV };
       
    21 $ENV{PATH} = "/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin";
       
    22 
       
    23 my %TRANSPORT = (lmtp       => "local",
       
    24                  pipe       => "local",
       
    25                  appendfile => "local",
       
    26                  autoreply  => "local",
       
    27                  smtp       => "remote",
       
    28 );
       
    29 
       
    30 my %opt = (init   => undef,
       
    31            warn   => undef,
       
    32            crit   => undef,
       
    33            update => 1,
       
    34 );
       
    35 
       
    36 my %exim;
       
    37 my %state = (time    => 0,
       
    38              inode   => { mainlog => 0 },
       
    39              offset  => { mainlog => 0 },
       
    40              counter => { transport => {},
       
    41                           protocol  => {},
       
    42                           in        => { local => 0, remote => 0 },
       
    43                           out       => { local => 0, remote => 0 },
       
    44              },
       
    45 );
       
    46 
       
    47 sub init();
       
    48 
       
    49 MAIN: {
       
    50 
       
    51     GetOptions("init=s"  => \$opt{init},
       
    52                "warn=s"  => \$opt{warn},     # rate mails/h
       
    53                "crit=s"  => \$opt{crit},     # rate mails/h
       
    54                "update!" => \$opt{update},
       
    55     ) or die "Bad Usage\n";
       
    56 
       
    57     $opt{init} and do { init(); exit };
       
    58 
       
    59     # read the exim (cache) file and the status
       
    60     die "$0: $CACHE_FILE not found ($!). Try $0 --help\n"
       
    61         if not -r $CACHE_FILE;
       
    62     die "$0: $STATE_FILE not found ($!). Try $0 --help\n"
       
    63         if not -r $STATE_FILE;
       
    64     %exim  = %{ $_ = retrieve($CACHE_FILE) };
       
    65     %state = %{ $_ = retrieve($STATE_FILE) };
       
    66 
       
    67     ### %exim
       
    68     ### %state
       
    69 
       
    70     {
       
    71         my @files;
       
    72 
       
    73         # it's important to have the real mainlog last!
       
    74         if ($state{inode}{mainlog} == 0) {
       
    75             ### never seen an inode
       
    76             push @files, [ $exim{mainlog} => 0 ];
       
    77         }
       
    78         elsif ($state{inode}{mainlog} != (stat $exim{mainlog})[1]) {
       
    79             ### inum changed
       
    80             if ((my $moved_mainlog)
       
    81                 = grep { (stat)[1] == $state{inode}{mainlog} }
       
    82                 glob(dirname($exim{mainlog}) . "/*"))
       
    83             {
       
    84                 ### found old one: $moved_mainlog
       
    85                 push @files, [ $moved_mainlog => $state{offset}{mainlog} ];
       
    86             }
       
    87             push @files, [ $exim{mainlog} => 0 ];
       
    88         }
       
    89         else {
       
    90             push @files, [ $exim{mainlog} => $state{offset}{mainlog} ];
       
    91         }
       
    92 
       
    93         my $now = time();
       
    94         my $dt  = $now - $state{time};
       
    95         my %out = (local => 0, remote => 0);
       
    96         my %in  = (local => 0, remote => 0);
       
    97         my (%transport, %protocol);
       
    98     FILE: foreach (@files) {
       
    99             my ($file, $offset) = @$_;
       
   100             open(my $fh, $file) or do {
       
   101                 warn "open $file: $!\n";
       
   102                 next FILE;
       
   103             };
       
   104             seek($fh, $offset, 0) or do {
       
   105                 warn "seek $file to offset $offset: $!\n";
       
   106                 next FILE;
       
   107             };
       
   108             while (<$fh>) {
       
   109                 if (/^\S+ \S+ (?:\S+ )?\S+ ?> .* T=(\S+)/) {
       
   110                     $transport{$1}++;
       
   111                     $out{ $exim{transport}{$1} }++;
       
   112                     next;
       
   113                 }
       
   114                 if (/^\S+ \S+ (?:\S+ )?\S+ <= .* P=(\S+)/) {
       
   115                     $protocol{$1}++;
       
   116                     $in{ $1 =~ /smtp/ ? "remote" : "local" }++;
       
   117                     next;
       
   118                 }
       
   119             }
       
   120 
       
   121             # will then hold the values from the *last*, that's why
       
   122             # above I said that the order matters
       
   123             $state{offset}{mainlog} = tell($fh);
       
   124             $state{inode}{mainlog}  = (stat $fh)[1];
       
   125         }
       
   126 
       
   127         # foreach (keys %transport) {
       
   128         #    print "$_: $transport{$_}\n";
       
   129         # }
       
   130         # print "\n";
       
   131 
       
   132         # save status - status contains absolute counters
       
   133         $state{time} = $now;
       
   134         $state{counter}{in}{remote}  += $in{remote};
       
   135         $state{counter}{in}{local}   += $in{local};
       
   136         $state{counter}{out}{remote} += $out{remote};
       
   137         $state{counter}{out}{local}  += $out{local};
       
   138 
       
   139         foreach (keys %transport) {
       
   140             $state{counter}{transport}{$_} = 0
       
   141                 if not defined $state{counter}{transport}{$_};
       
   142             $state{counter}{transport}{$_} += $transport{$_};
       
   143         }
       
   144 
       
   145         foreach (keys %protocol) {
       
   146             $state{counter}{protocol}{$_} = 0
       
   147                 if not defined $state{counter}{protocol}{$_};
       
   148             $state{counter}{protocol}{$_} += $protocol{$_};
       
   149         }
       
   150 
       
   151         store \%state, $STATE_FILE if $opt{update};
       
   152 
       
   153         # start processing
       
   154         my $in_tot  = $in{local} + $in{remote};
       
   155         my $out_tot = $out{local} + $out{remote};
       
   156 
       
   157         # mails / minute
       
   158         my $rate_in_remote  = $in{remote} / $dt * 60;
       
   159         my $rate_out_remote = $out{remote} / $dt * 60;
       
   160         my $rate_in_local   = $in{local} / $dt * 60;
       
   161         my $rate_out_local  = $out{local} / $dt * 60;
       
   162         my $rate_in         = $in_tot / $dt * 60;
       
   163         my $rate_out        = $out_tot / $dt * 60;
       
   164 
       
   165         my $perfdata = "in_local=$state{counter}{in}{local}"
       
   166             . " in_remote=$state{counter}{in}{remote}"
       
   167             . " out_local=$state{counter}{out}{local}"
       
   168             . " out_remote=$state{counter}{out}{remote}";
       
   169 
       
   170         foreach (keys %{ $state{counter}{transport} }) {
       
   171             $perfdata .= " $_=$state{counter}{transport}{$_}";
       
   172         }
       
   173 
       
   174         foreach (keys %{ $state{counter}{protocol} }) {
       
   175             $perfdata .= " $_=$state{counter}{protocol}{$_}";
       
   176         }
       
   177 
       
   178         printf "OK i:%.1f(%.1f/%.1f) o:%.1f(%.1f/%.1f)|$perfdata\n", $rate_in,
       
   179             $rate_in_local, $rate_in_remote, $rate_out, $rate_out_local,
       
   180             $rate_out_remote;
       
   181 
       
   182     }
       
   183 
       
   184     exit 0;
       
   185 
       
   186 }
       
   187 
       
   188 sub init() {
       
   189 
       
   190     # noch sind wir root (hoffentlich)
       
   191     -d $STATE_DIR or do {
       
   192         require File::Path;
       
   193         import File::Path;
       
   194         umask(022);
       
   195         mkpath($STATE_DIR, 0, 0777);
       
   196     };
       
   197 
       
   198     my $uid = (getpwnam $opt{init})[2];
       
   199     chown($uid, -1, $STATE_DIR)
       
   200         or die "Can't chown $STATE_DIR to $opt{init} ($uid): $!\n";
       
   201 
       
   202     umask 077;
       
   203     $> = $uid;    # EUID
       
   204 
       
   205     # exim (logfile names) acquired from exim
       
   206     my (%exim, @exim);
       
   207     @exim = `exim -bP all` or `exim4 -bP all`;
       
   208 
       
   209     my ($log) = (split " ", (grep /^\s*log_file_path\s*=/, @exim)[0])[2];
       
   210 
       
   211     %exim = (mainlog   => sprintf($log, "main"),
       
   212              rejectlog => sprintf($log, "reject"),
       
   213              paniclog  => sprintf($log, "paniclog"),
       
   214     );
       
   215 
       
   216     {
       
   217         local $/ = "";
       
   218         @exim = `exim -bP transports` or `exim4 -bP transports`;
       
   219         foreach (@exim) {
       
   220             my ($transport, $driver) = /^(\S+) transport:.*^driver = (\S+)/ms;
       
   221 
       
   222           # map the transport names to their driver and this to "local/remote"
       
   223             $exim{transport}{$transport} = $TRANSPORT{$driver};
       
   224         }
       
   225 
       
   226     }
       
   227 
       
   228     store \%exim,  $CACHE_FILE;
       
   229     store \%state, $STATE_FILE;
       
   230 
       
   231 }
       
   232 
       
   233 __END__
       
   234 
       
   235 my $FILE = "/var/log/exim4/mainlog";
       
   236 my $STATUS = "/var/run/nagios3/$ME/%s";
       
   237 
       
   238 sub unkn(@) { print "UNKNOWN @_\n"; exit 3 }
       
   239 sub crit(@) { print "CRIT @_\n"; exit 2 }
       
   240 sub warn(@) { print "WARN @_\n"; exit 1 }
       
   241 sub ok(@)   { print "OK @_\n"; exit 0 }
       
   242 
       
   243 open(my $log, $FILE) or critical("file $FILE not found");
       
   244 
       
   245 __END__
       
   246 # vim:sts=4 sw=4 aw ai sm: