#!/usr/bin/perl 
#
# (c)2000 Benjamin Schmid, blueshift@gmx.net (emergency use only ;-)
# Copyleft by GNU GPL
#
#
# check_email_loop Nagios Plugin
#
# This script sends a mail with a specific id in the subject via
# an given smtp-server to a given email-adress. When the script
# is run again, it checks for this Email (with its unique id) on
# a given pop3 account and send another mail.
# 
#
# Example: check_email_loop.pl -poph=mypop -popu=user -pa=password
# 	   -smtph=mailer -from=returnadress@yoursite.com
#	   -to=remaileradress@friend.com -pendc=2 -lostc=0
#
# This example will send eacht time this check is executed a new
# mail to remaileradress@friend.com using the SMTP-Host mailer.
# Then it looks for any back-forwarded mails in the POP3 host
# mypop. In this Configuration CRITICAL state will be reached if  
# more than 2 Mails are pending (meaning that they did not came 
# back till now) or if a mails got lost (meaning a mail, that was
# send later came back prior to another mail).
# 

require 5.004;
use strict;
use lib utils.pm ;
use Plugin;
use Plugin::Parameter qw($helpparameterlist $versionparameterlist $passwordparameter
			 $timeoutparameter
			 $verboseparameter);

use Net::POP3;
use Net::SMTP;

use vars qw($opt_t $sender $receiver $pophost $popuser $poppasswd $poptimeout $smtphost
	    $smtptimeout $statfile $pinginterval $lostwarn $lostcrit $pendwarn
	    $pendcrit $PROGNAME);

use utils qw (%ERRORS &usage);

$timeoutparameter->default(120);
$passwordparameter->binding(\$poppasswd);
$passwordparameter->flags([ 'passwd' ]);
$passwordparameter->description("Password for the POP3 user");
my $fromparameter = new Plugin::Parameter(-name => "from", -flags => [ 'from' ],
					  -optional => "no", -valueoptional => "no", -type => "EMAILADDRESS",
					  -binding => \$sender,
					  -description => "email address of sender (for mail return on errors)");
my $toparameter = new Plugin::Parameter(-name => "to", -flags => [ 'to' ],
					-optional => "no", -valueoptional => "no", -type => "EMAILADDRESS",
					-binding => \$receiver,
					-description => "email address to which the mails should be sent");
my $pophostparameter = new Plugin::Parameter(-name => "pophost", -flags => [ "pophost" ],
					     -optional => "no", -valueoptional => "no", -type => "HOSTNAME",
					     -binding => \$pophost,
					     -description => "Hostname or IP address for the POP3 server");
my $popuserparameter = new Plugin::Parameter(-name => "popuser", -flags => [ "popuser" ],
					     -optional => "no", -valueoptional => "no", -type => "USER",
					     -binding => \$popuser,
					     -description => "Username for the POP3 account");
my $poptimeoutparameter = new Plugin::Parameter(-name => "poptimeout", -flags => [ "poptimeout" ],
						-optional => "yes", -valueoptional => "no", -type => "TIMEOUT",
						-default => 60, -binding => \$poptimeout,
						-description => "Timeout in seconds for the POP3 server");
my $smtphostparameter = new Plugin::Parameter(-name => "smtphost", -flags => [ 'smtphost' ],
					      -optional => "no", -valueoptional => 'no', -type => "HOSTNAME",
					      -binding => \$smtphost,
					      -description => "Hostname or IP address for the SMTP server");
my $smtptimeoutparameter = new Plugin::Parameter(-name => "smtptimeout", -flags => [ 'smtptimeout' ],
						 -optional => "yes", -valueoptional => 'no', -type => "TIMEOUT",
						 -default => 60, -binding => \$smtptimeout,
						 -description => "Timeout in seconds for the SMTP server");
my $statfileparameter = new Plugin::Parameter(-name => "statfile", -flags => [ 'statfile' ],
					      -optional => "yes", -valueoptional => "no", -type => "FILENAME",
					      -default => "/var/tmp/nagios/check_email_loop.stat", -binding => \$statfile,
					      -description => "Faile to save ids of messages");
my $lostwarnparameter = new Plugin::Parameter(-name => "lostwarn", -flags => [ "lostwarn" ],
					      -optional => "yes", -valueoptional => "no", -type => "INTEGER",
					      -default => 0, -binding => \$lostwarn,
					      -description => "Report WARNING state if number of lost emails exceeds this number");
my $lostcritparameter = new Plugin::Parameter(-name => "lostcrit", -flags => [ 'lostcrit' ],
					      -optional => "yes", -valueoptional => "no", -type => "INTEGER",
					      -default => 1, -binding => \$lostcrit,
					      -description => "Report CRITICAL state if number of lost emails exceeds this number");
my $pendwarnparameter = new Plugin::Parameter(-name => "pendwarn", -flags => [ 'pendwarn' ],
					      -optional => "yes", -valueoptional => "no", -type => "INTEGER",
					      -default => 1, -binding => \$pendwarn,
					      -description => "Report WARNING state if number of pending emails exceeds this number");
my $pendcritparameter = new Plugin::Parameter(-name => "pendcrit", -flags => [ 'pendcrit' ],
					      -optional => "yes", -valueoptional => "no", -type => "INTEGER",
					      -default => 2, -binding => \$pendcrit,
					      -description => "Report CRITICAL state if number of pending emails exceeds this number");

my @commandparameterlist = ( $fromparameter,
			     $toparameter,
			     $pophostparameter,
			     $popuserparameter,
			     $passwordparameter,
			     $poptimeoutparameter,
			     $smtphostparameter,
			     $smtptimeoutparameter,
			     $statfileparameter,
			     $lostwarnparameter,
			     $lostcritparameter,
			     $pendwarnparameter,
			     $pendcritparameter,
			     $timeoutparameter,
			     $verboseparameter );

my $plugin = new Plugin(-revision => '$Revision: 1.1.1.1 $',
			-copyright => "2000 Benjamin Schmid, 2004 Howard Wilkinson <howard\@cohtech.com>",
			-shortcomment => "This plugin send an email with a specific id in the subject via a given smtp server to a given email address. When the plugin is run again, it checks for this email (with its unique id) on a given pop3 account and sends another email.",
			-longcomment => "Lost emails occur when later emails arrive first, pending emails are ones that have yet to arrive",
			-parameterlists => [ \@commandparameterlist, $helpparameterlist, $versionparameterlist ]);

$plugin->init();

# ----------------------------------------

my %ERRORS = ('UNKNOWN' , '-1',
              'OK' , '0',
              'WARNING', '1',
              'CRITICAL', '2');

my $state = "UNKNOWN";

# Internal Vars
my ($pop,$msgcount,@msglines,$statinfo,@messageids,$newestid);
my ($matchcount) = (0);

# Subs declaration
sub messagematchs;
sub nsexit;


$plugin->start_timeout($opt_t, "plugin timed out");

if (! -f $statfile) {
  $statfile =~ m/^(.*)\/([^\/]*$)/;
  my ($dir, $file) = ($1, $2);
  if ( ! -d $dir) {
    mkdir $dir;
  }
}
      
# Try to read the ids of the last send emails out of statfile
if (open STATF, "$statfile") {
  @messageids = <STATF>;
  chomp @messageids;
  close STATF;
}

# Try to open statfile for writing 
if (!open STATF, ">$statfile") {
  $plugin->usage();
  usage("$PROGNAME UNKNOWN: Failed to open mail-ID database $statfile for writing\n");
}

# Ok - check if it's time to release another mail

# ...

# creating new serial id
my $serial = time();
$serial = "ID#" . $serial . "#$$";

# sending new ping email
my $smtp = Net::SMTP->new($smtphost,Timeout=>$smtptimeout);
if (!$smtp) {
  my $elapsed = time() - $serial;
  my $error = ($elapsed >= $smtptimeout)?"timed out":"unknown error";
  nsexit("SMTP failed ($error)", 'CRITICAL');
}
($smtp->mail($sender) &&
 $smtp->to($receiver) &&
 $smtp->data() &&
 $smtp->datasend("To: $receiver\nSubject: E-Mail Ping [$serial]\n\n".
		 "This is a automatically sended E-Mail.\n".
		 "It ist not intended for human reader.\n\n".
		 "Serial No: $serial\n") &&
 $smtp->dataend() &&
 $smtp->quit
) || nsexit("Error delivering message",'CRITICAL');


# now the interessting part: let's see if they are receiving ;-)

$pop = Net::POP3->new( $pophost, Timeout=>$poptimeout)
  || nsexit("POP3 connect timeout (>$poptimeout s, host: $pophost)",'CRITICAL');

$msgcount=$pop->login($popuser,$poppasswd);

$statinfo="$msgcount mails on POP3";

nsexit("POP3 login failed (user:$popuser)",'CRITICAL') if (!defined($msgcount));

# Count messages, that we are looking 4:
while ($msgcount > 0) {
  @msglines = @{$pop->get($msgcount)};

  for (my $i=0; $i < scalar @messageids; $i++) {
    if (messagematchsid(\@msglines,$messageids[$i])) {
      $matchcount++;
      # newest received mail than the others, ok remeber id.
      $newestid = $messageids[$i] if ($messageids[$i] > $newestid || !defined $newestid);
      $pop->delete($msgcount);  # remove E-Mail from POP3 server
      splice @messageids, $i, 1;# remove id from List
      last;                     # stop looking in list
    }
  }

  $msgcount--;
}

$pop->quit();  # necessary for pop3 deletion!

$plugin->stop_timeout();

# traverse through the message list and mark the lost mails
# that mean mails that are older than the last received mail.
if (defined $newestid) {
  $newestid =~ /\#(\d+)\#/;
  $newestid = $1;
  for (my $i=0; $i < scalar @messageids; $i++) {
    $messageids[$i] =~ /\#(\d+)\#/;
    my $akid = $1;
    if ($akid < $newestid) {
      $messageids[$i] =~ s/^ID/LI/; # mark lost
    }
  }
}

# Write list to id-Database
foreach my $id (@messageids) {
  print STATF  "$id\n";
}
print STATF "$serial\n";     # remember send mail of this session
close STATF;

# ok - count lost and pending mails;
my @tmp = grep /^ID/, @messageids;
my $pendingm = scalar @tmp;
@tmp = grep /^LI/, @messageids;
my $lostm = scalar @tmp; 

# Evaluate the Warnin/Crit-Levels
if (defined $pendwarn && $pendingm > $pendwarn) { $state = 'WARNING'; }
if (defined $lostwarn && $lostm > $lostwarn) { $state = 'WARNING'; }
if (defined $pendcrit && $pendingm > $pendcrit) { $state = 'CRITICAL'; }
if (defined $lostcrit && $lostm > $lostcrit) { $state = 'CRITICAL'; }

if ((defined $pendwarn || defined $pendcrit || defined $lostwarn 
     || defined $lostcrit) && ($state eq 'UNKNOWN')) {$state='OK';}

# Append Status info
$statinfo = $statinfo . ", $matchcount mail(s) came back,".
            " $pendingm pending, $lostm lost.";

# Exit in a Nagios-compliant way
nsexit($statinfo);

# ---------------------------------------------------------------------

sub nsexit {
  my ($msg,$code) = @_;
  $code=$state if (!defined $code);
  print "$PROGNAME $code: $msg\n" if (defined $msg);
  exit $ERRORS{$code};
}

# ---------------------------------------------------------------------

sub messagematchsid {
  my ($mailref,$id) = (@_);
  my (@tmp);
  my $match = 0;

  # ID
  $id =~ s/^LI/ID/;    # evtl. remove lost mail mark
  @tmp = grep /Subject: E-Mail Ping \[/, @$mailref;
  chomp @tmp;
  if (($tmp[0] =~ /$id/)) { $match = 1; }

  return $match;
}

# ---------------------------------------------------------------------
