check_email_loop.pl 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. #!/usr/bin/perl -w
  2. #
  3. # $Id: check_email_loop.pl 1290 2005-11-29 23:21:06Z harpermann $
  4. #
  5. # (c)2000 Benjamin Schmid, blueshift@gmx.net (emergency use only ;-)
  6. # Copyleft by GNU GPL
  7. #
  8. #
  9. # check_email_loop Nagios Plugin
  10. #
  11. # This script sends a mail with a specific id in the subject via
  12. # an given smtp-server to a given email-adress. When the script
  13. # is run again, it checks for this Email (with its unique id) on
  14. # a given pop3 account and send another mail.
  15. #
  16. #
  17. # Example: check_email_loop.pl -poph=mypop -popu=user -pa=password
  18. # -smtph=mailer -from=returnadress@yoursite.com
  19. # -to=remaileradress@friend.com -pendc=2 -lostc=0
  20. #
  21. # This example will send eacht time this check is executed a new
  22. # mail to remaileradress@friend.com using the SMTP-Host mailer.
  23. # Then it looks for any back-forwarded mails in the POP3 host
  24. # mypop. In this Configuration CRITICAL state will be reached if
  25. # more than 2 Mails are pending (meaning that they did not came
  26. # back till now) or if a mails got lost (meaning a mail, that was
  27. # send later came back prior to another mail).
  28. #
  29. # Michael Markstaller, mm@elabnet.de various changes/additions
  30. # MM 021003: fixed some unquoted strings
  31. # MM 021116: fixed/added pendwarn/lostwarn
  32. # MM 030515: added deleting of orphaned check-emails
  33. # changed to use "top" instead of get to minimize traffic (required changing match-string from "Subject: Email-ping [" to "Email-Ping ["
  34. use Net::POP3;
  35. use Net::SMTP;
  36. use strict;
  37. use Getopt::Long;
  38. &Getopt::Long::config('auto_abbrev');
  39. # ----------------------------------------
  40. my $TIMEOUT = 120;
  41. my %ERRORS = ('OK' , '0',
  42. 'WARNING', '1',
  43. 'CRITICAL', '2');
  44. 'UNKNOWN' , '3');
  45. my $state = "UNKNOWN";
  46. my ($sender,$receiver, $pophost, $popuser, $poppasswd, $smtphost,$keeporphaned);
  47. my ($poptimeout,$smtptimeout,$pinginterval,$maxmsg)=(60,60,5,50);
  48. my ($lostwarn, $lostcrit,$pendwarn, $pendcrit,$debug);
  49. $debug = 0;
  50. # Internal Vars
  51. my ($pop,$msgcount,@msglines,$statinfo,@messageids,$newestid);
  52. my (%other_smtp_opts);
  53. my ($matchcount,$statfile) = (0,"check_email_loop.stat");
  54. # Subs declaration
  55. sub usage;
  56. sub messagematchs;
  57. sub nsexit;
  58. # Just in case of problems, let's not hang Nagios
  59. $SIG{'ALRM'} = sub {
  60. print ("ERROR: $0 Time-Out $TIMEOUT s \n");
  61. exit $ERRORS{"UNKNOWN"};
  62. };
  63. alarm($TIMEOUT);
  64. # Evaluate Command Line Parameters
  65. my $status = GetOptions(
  66. "from=s",\$sender,
  67. "to=s",\$receiver,
  68. "debug", \$debug,
  69. "pophost=s",\$pophost,
  70. "popuser=s",\$popuser,
  71. "passwd=s",\$poppasswd,
  72. "poptimeout=i",\$poptimeout,
  73. "smtphost=s",\$smtphost,
  74. "smtptimeout=i",\$smtptimeout,
  75. "statfile=s",\$statfile,
  76. "interval=i",\$pinginterval,
  77. "lostwarn=i",\$lostwarn,
  78. "lostcrit=i",\$lostcrit,
  79. "pendwarn=i",\$pendwarn,
  80. "pendcrit=i",\$pendcrit,
  81. "maxmsg=i",\$maxmsg,
  82. "keeporphaned=s",\$keeporphaned,
  83. );
  84. usage() if ($status == 0 || ! ($pophost && $popuser && $poppasswd &&
  85. $smtphost && $receiver && $sender ));
  86. # Try to read the ids of the last send emails out of statfile
  87. if (open STATF, "$statfile") {
  88. @messageids = <STATF>;
  89. chomp @messageids;
  90. close STATF;
  91. }
  92. # Try to open statfile for writing
  93. if (!open STATF, ">$statfile") {
  94. nsexit("Failed to open mail-ID database $statfile for writing",'CRITICAL');
  95. }
  96. # Ok - check if it's time to release another mail
  97. # ...
  98. # creating new serial id
  99. my $serial = time();
  100. $serial = "ID#" . $serial . "#$$";
  101. # sending new ping email
  102. %other_smtp_opts=();
  103. if ( $debug == 1 ) {
  104. $other_smtp_opts{'Debug'} = 1;
  105. }
  106. my $smtp = Net::SMTP->new($smtphost,Timeout=>$smtptimeout, %other_smtp_opts)
  107. || nsexit("SMTP connect timeout ($smtptimeout s)",'CRITICAL');
  108. ($smtp->mail($sender) &&
  109. $smtp->to($receiver) &&
  110. $smtp->data() &&
  111. $smtp->datasend("To: $receiver\nSubject: E-Mail Ping [$serial]\n\n".
  112. "This is an automatically sent E-Mail.\n".
  113. "It is not intended for a human reader.\n\n".
  114. "Serial No: $serial\n") &&
  115. $smtp->dataend() &&
  116. $smtp->quit
  117. ) || nsexit("Error delivering message",'CRITICAL');
  118. # no the interessting part: let's if they are receiving ;-)
  119. $pop = Net::POP3->new( $pophost,
  120. Timeout=>$poptimeout)
  121. || nsexit("POP3 connect timeout (>$poptimeout s, host: $pophost)",'CRITICAL');
  122. $msgcount=$pop->login($popuser,$poppasswd);
  123. $statinfo="$msgcount mails on POP3";
  124. nsexit("POP3 login failed (user:$popuser)",'CRITICAL') if (!defined($msgcount));
  125. # Check if more than maxmsg mails in pop3-box
  126. nsexit(">$maxmsg Mails ($msgcount Mails on POP3); Please delete !",'WARNING') if ($msgcount > $maxmsg);
  127. my ($mid, $nid);
  128. # Count messages, that we are looking 4:
  129. while ($msgcount > 0) {
  130. @msglines = @{$pop->top($msgcount,1)};
  131. for (my $i=0; $i < scalar @messageids; $i++) {
  132. if (messagematchsid(\@msglines,$messageids[$i])) {
  133. $matchcount++;
  134. # newest received mail than the others, ok remeber id.
  135. if (!defined $newestid) {
  136. $newestid = $messageids[$i];
  137. } else {
  138. $messageids[$i] =~ /\#(\d+)\#/;
  139. $mid = $1;
  140. $newestid =~ /\#(\d+)\#/;
  141. $nid = $1;
  142. if ($mid > $nid) {
  143. $newestid = $messageids[$i];
  144. }
  145. }
  146. $pop->delete($msgcount); # remove E-Mail from POP3 server
  147. splice @messageids, $i, 1;# remove id from List
  148. last; # stop looking in list
  149. }
  150. }
  151. # Delete orphaned Email-ping msg
  152. my @msgsubject = grep /^Subject/, @msglines;
  153. chomp @msgsubject;
  154. # Scan Subject if email is an Email-Ping. In fact we match and delete also successfully retrieved messages here again.
  155. if (!defined $keeporphaned && $msgsubject[0] =~ /E-Mail Ping \[/) {
  156. $pop->delete($msgcount); # remove E-Mail from POP3 server
  157. }
  158. $msgcount--;
  159. }
  160. $pop->quit(); # necessary for pop3 deletion!
  161. # traverse through the message list and mark the lost mails
  162. # that mean mails that are older than the last received mail.
  163. if (defined $newestid) {
  164. $newestid =~ /\#(\d+)\#/;
  165. $newestid = $1;
  166. for (my $i=0; $i < scalar @messageids; $i++) {
  167. $messageids[$i] =~ /\#(\d+)\#/;
  168. my $akid = $1;
  169. if ($akid < $newestid) {
  170. $messageids[$i] =~ s/^ID/LI/; # mark lost
  171. }
  172. }
  173. }
  174. # Write list to id-Database
  175. foreach my $id (@messageids) {
  176. print STATF "$id\n";
  177. }
  178. print STATF "$serial\n"; # remember send mail of this session
  179. close STATF;
  180. # ok - count lost and pending mails;
  181. my @tmp = grep /^ID/, @messageids;
  182. my $pendingm = scalar @tmp;
  183. @tmp = grep /^LI/, @messageids;
  184. my $lostm = scalar @tmp;
  185. # Evaluate the Warnin/Crit-Levels
  186. if (defined $pendwarn && $pendingm > $pendwarn) { $state = 'WARNING'; }
  187. if (defined $lostwarn && $lostm > $lostwarn) { $state = 'WARNING'; }
  188. if (defined $pendcrit && $pendingm > $pendcrit) { $state = 'CRITICAL'; }
  189. if (defined $lostcrit && $lostm > $lostcrit) { $state = 'CRITICAL'; }
  190. if ((defined $pendwarn || defined $pendcrit || defined $lostwarn
  191. || defined $lostcrit) && ($state eq 'UNKNOWN')) {$state='OK';}
  192. # Append Status info
  193. $statinfo = $statinfo . ", $matchcount mail(s) came back,".
  194. " $pendingm pending, $lostm lost.";
  195. # Exit in a Nagios-compliant way
  196. nsexit($statinfo);
  197. # ----------------------------------------------------------------------
  198. sub usage {
  199. print "check_email_loop 1.1 Nagios Plugin - Real check of a E-Mail system\n";
  200. print "=" x 75,"\nERROR: Missing or wrong arguments!\n","=" x 75,"\n";
  201. print "This script sends a mail with a specific id in the subject via an given\n";
  202. print "smtp-server to a given email-adress. When the script is run again, it checks\n";
  203. print "for this Email (with its unique id) on a given pop3 account and sends \n";
  204. print "another mail.\n";
  205. print "\nThe following options are available:\n";
  206. print " -from=text email adress of send (for mail returnr on errors)\n";
  207. print " -to=text email adress to which the mails should send to\n";
  208. print " -pophost=text IP or name of the POP3-host to be checked\n";
  209. print " -popuser=text Username of the POP3-account\n";
  210. print " -passwd=text Password for the POP3-user\n";
  211. print " -poptimeout=num Timeout in seconds for the POP3-server\n";
  212. print " -smtphost=text IP oder name of the SMTP host\n";
  213. print " -smtptimeout=num Timeout in seconds for the SMTP-server\n";
  214. print " -statfile=text File to save ids of messages ($statfile)\n";
  215. print " -interval=num Time (in minutes) that must pass by before sending\n";
  216. print " another Ping-mail (gibe a new try);\n";
  217. print " -lostwarn=num WARNING-state if more than num lost emails\n";
  218. print " -lostcrit=num CRITICAL \n";
  219. print " -pendwarn=num WARNING-state if more than num pending emails\n";
  220. print " -pendcrit=num CRITICAL \n";
  221. print " -maxmsg=num WARNING if more than num emails on POP3 (default 50)\n";
  222. print " -keeporphaned Set this to NOT delete orphaned E-Mail Ping msg from POP3\n";
  223. print " -debug send SMTP tranaction info to stderr\n\n";
  224. print " Options may abbreviated!\n";
  225. print " LOST mails are mails, being sent before the last mail arrived back.\n";
  226. print " PENDING mails are those, which are not. (supposed to be on the way)\n";
  227. print "\nExample: \n";
  228. print " $0 -poph=host -pa=pw -popu=popts -smtph=host -from=root\@me.com\n ";
  229. print " -to=remailer\@testxy.com -lostc=0 -pendc=2\n";
  230. print "\nCopyleft 19.10.2000, Benjamin Schmid / 2003 Michael Markstaller, mm\@elabnet.de\n";
  231. print "This script comes with ABSOLUTELY NO WARRANTY\n";
  232. print "This programm is licensed under the terms of the ";
  233. print "GNU General Public License\n\n";
  234. exit $ERRORS{"UNKNOWN"};
  235. }
  236. # ---------------------------------------------------------------------
  237. sub nsexit {
  238. my ($msg,$code) = @_;
  239. $code=$state if (!defined $code);
  240. print "$code: $msg\n" if (defined $msg);
  241. exit $ERRORS{$code};
  242. }
  243. # ---------------------------------------------------------------------
  244. sub messagematchsid {
  245. my ($mailref,$id) = (@_);
  246. my (@tmp);
  247. my $match = 0;
  248. # ID
  249. $id =~ s/^LI/ID/; # evtl. remove lost mail mark
  250. @tmp = grep /E-Mail Ping \[/, @$mailref;
  251. chomp @tmp;
  252. if (($tmp[0] =~ /$id/))
  253. { $match = 1; }
  254. # Sender:
  255. # @tmp = grep /^From:\s+/, @$mailref;
  256. # if (@tmp && $sender ne "")
  257. # { $match = $match && ($tmp[0]=~/$sender/); }
  258. # Receiver:
  259. # @tmp = grep /^To: /, @$mailref;
  260. # if (@tmp && $receiver ne "")
  261. # { $match = $match && ($tmp[0]=~/$receiver/); }
  262. return $match;
  263. }
  264. # ---------------------------------------------------------------------