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