As I promised, here is the latest vesion of my Sendmail and MailScanner scripts and templates. This is an update to my original post. Visually there are no differences to the graphs. See, here are what the graphs are looking like now,
Not too much different, just a bit cleaner with the new version of RRDTool that Cacti is using.
Now to what has changed. This version runs significantly differently. There are basically two scripts, a maillog watcher script and a read counters script. Both of these scripts run on your mail server.
NOTE: All the scripts and an empty counter file is attached at the bottom of this post.
maillog Watcher Script
First, the source code to the script. I store these scripts in /opt/watchmaillog on my mail servers, but you can put them wherever you like on your server. Just update the first couple of lines of the code to reflect where you've put the files.
/opt/watchmaillog/watchmaillog.sh
Code: Select all
#!/usr/bin/perl
#
# Daemon used to watch the maillog messages for certain messages and trigger events when
# certain messages occur
#
# By Jason Warnes
#
# Change Log
# ~~~~~~~~~~
# 2006-08-22: Initial release
# 2006-09-05: Fixed signal handling
# Added /var/run/watchmaillog.pid file for logrotate usage
# 2006-09-07: Added proper signal handling (Thanks pvenezia!)
# Added SpamAssassin spamd checking support for SPAM (Thanks raiten!)
# 2006-09-18: Added new item mess_waiting, which is the number of messages MailScanner
# detects when a new batch is started.
# 2006-11-02: Changed the way SPAM messages were detected so the script works
# on servers configured for less verbose logging. (Thanks sdetroch!)
# 2006-11-08: Added new item mess_rejected, which is the number of rejected
# messages by Sendmail.
# 2007-02-06: Fixed <MAILLOG> close statement at end of main program. (Thanks Avenger!)
# Fixed warning messages about uninitialized $line used in pattern
# matching (Thanks Avenger!)
# 2007-05-04: Properly closed the maillog file on SIGHUP received. (Thansks thomasch!)
$debug=0; # 1=Debug messages are displayed, 0=No debug messages are displayed
$daemon=1; # 1=Daemonize the program, 0=Run interactive
$syslog=1; # 1=Log stuff to syslog, 0=No logging to syslog
$self="/opt/watchmaillog/watchmaillog.sh"; # Location of this script
$counterfile="/opt/watchmaillog/watchmaillog_counters"; # Location to store the counter file
$resetfile="/opt/watchmaillog/watchmaillog_reset"; # Location of the reset counter flag file
$pidfile="/var/run/watchmaillog.pid"; # Location of the running process ID file (used in logrotate)
use Sys::Syslog;
use POSIX;
use Time::HiRes qw( gettimeofday tv_interval );
$|=1;
my $sigset = POSIX::SigSet->new();
my $hupaction = POSIX::SigAction->new('hup_signal_handler',
$sigset,
&POSIX::SA_NODEFER);
my $osigaction = POSIX::SigAction->new('signal_handler',
$sigset,
&POSIX::SA_NODEFER);
POSIX::sigaction(&POSIX::SIGHUP, $hupaction);
POSIX::sigaction(&POSIX::SIGINT, $osigaction);
POSIX::sigaction(&POSIX::SIGTERM, $osigaction);
if($daemon){
$pid=fork;
if($pid) {
open(PID,">".$pidfile) or die "Cannot open PID file: $!.";
print PID ("$pid\n"); # Write the PID out to the PID file for logrotate
close(PID);
}
exit if $pid;
die "Couldn't fork : $!" unless defined($pid);
setsid() or die "Can't start a new session: $!";
$time_to_die=0;
}
sub signal_handler {
$time_to_die=1;
}
sub hup_signal_handler {
if($debug){print "got SIGHUP\n";}
close(MAILLOG);
exec($self) or die "Couldn't restart: $!\n";
}
if($syslog){openlog("watchmaillog","pid","daemon");}
if($syslog){syslog("notice","Starting.");}
if($debug){print("watchmaillog is starting.\n");}
# Main part of the program
open(MAILLOG, "tail -n 0 -f /var/log/maillog|") or die "Cannot open maillog: $!.";
my $line="";
while(!$time_to_die){
$line=<MAILLOG>;
# Look for received messages where the sender is not from our domain(s)
if(($line=~/from\=/) && ($line!~/\@domain1.com|\@domain2.com/)){
$item="mess_recv";
&readcounterfile;
$counter{$item}++;
if($debug){print("Found an inbound message, incrementing the message recieve counter to $counter{$item}.\n");}
&writecounterfile;
}
# Look for messages sent to our domain(s), indicates an inbound message relayed to an internal server
if(($line=~/stat\=Sent/) && ($line=~/\@domain1.com|\@domain2.com/)){
$item="mess_relay";
&readcounterfile;
$counter{$item}++;
if($debug){print("Found an clean inbound message, incrementing the clean message recieve counter to $counter{$item}.\n");}
&writecounterfile;
}
# Look for sent messages to NOT our email domain(s), indicates an outbound message
if(($line=~/stat\=Sent/) && ($line!~/\@domain1.com|\@domain2.com/)){
$item="mess_sent";
&readcounterfile;
$counter{$item}++;
if($debug){print("Found an outbound message, incrementing the message sent counter to $counter{$item}.\n");}
&writecounterfile;
}
# Look for rejected messages
if((($line=~/ruleset/) && ($line=~/reject\=/)) || ($line =~/rejecting/)){
$item="mess_rejected";
&readcounterfile;
$counter{$item}++;
if($debug){print("Found a rejected message, incrementing the message rejected counter to $counter{$item}.\n");}
&writecounterfile;
}
# Look for MailScanner spam scanning batch results
if($line=~/Spam\ Checks\:\ Found/){
$item="spam";
$spam_count_pos = index($line,"Spam\ Checks\:\ Found");
$spam_count_pos2 = index($line, "\ spam\ messages");
$spam_count = substr($line,($spam_count_pos+19),($spam_count_pos2-($spam_count_pos+19)));
&readcounterfile;
$counter{$item}=$counter{$item}+$spam_count;
if($debug){print("Found $spam_count SPAM in the MailScanner batch, incrementing the spam counter to $counter{$item}.\n");}
&writecounterfile;
}
# Look for MainScanner virus scanning batch results
if($line=~/Virus\ Scanning\:\ Found/){
$item="virus";
$virus_count_pos = index($line,"Virus\ Scanning\:\ Found");
$virus_count_pos2 = index($line, "\ viruses");
$virus_count = substr($line,($virus_count_pos+22),($virus_count_pos2-($virus_count_pos+22)));
&readcounterfile;
$counter{$item}=$counter{$item}+$virus_count;
if($debug){print("Found $virus_count viruses in the MailScanner batch, incrementing the virus counter to $counter{$item}.\n");}
&writecounterfile;
}
# Look for MailScanner waiting messages
if($line=~/New\ Batch\:\ Found/){
$item="mess_waiting";
$mess_waiting_pos = index($line,"New\ Batch\:\ Found");
$mess_waiting_pos2 = index($line,"\ messages\ waiting");
$mess_waiting = substr($line,($mess_waiting_pos+17),($mess_waiting_pos2-($mess_waiting_pos+17)));
&readcounterfile;
$counter{$item}=$mess_waiting;
if($debug){print("Mailscanner found $mess_waiting messages waiting, setting the mess_waiting counter to $counter{$item}.\n");}
&writecounterfile;
}
}
close(MAILLOG);
if($debug){print("watchmaillog is ending.\n");}
if($syslog){syslog("notice","Ending.");}
unlink($pidfile);
# Subroutine to read the contents of the counter file
sub readcounterfile {
# Read the counter values from the file
if($debug){print("Reading contents of counter file.\n");}
open(COUNTER,$counterfile);
while($line=<COUNTER>){
@line=split(/\:/,$line);
chop($line[1]); # Drop the trailing LF off the value
# Check for reset counter flag file
if(-e $resetfile."_".$line[0]){
if($debug){print("Reset counter flag file found for counter $line[0], resetting counter value to 0.\n");}
$counter{$line[0]}=0;
unlink($resetfile."_".$line[0]);
} else {
$counter{$line[0]}=$line[1];
}
if($debug){print("Counter $line[0] = $counter{$line[0]}.\n");}
}
close(COUNTER);
}
# Subrouting to write the contents of the counter file
sub writecounterfile {
if($debug){print("Writing counter values to counter file.\n");}
open(COUNTER,">".$counterfile);
# Write each counter item out to the counter file
foreach $item (sort keys(%counter)) {
print COUNTER ($item."\:".$counter{$item}."\n");
}
close(COUNTER);
chmod(0666,$counterfile);
}
WHOA you're thinking, what the heck is going on here! Well let me explain. This is the biggest change since the first version and it was done purely for performance. Now instead of scraping through the last 5000 lines of the maillog file (which was a performance killer on high load servers), now this maillog watcher script runs daemonized and constantly is watching your maillog file in realtime for particular keywords. These keywords will trigger to increment counters for messages sent, messages recieved, spam blocked, viruses detected, and messages relayed (I'll explain this one in a second). As these counters are incremented they are written out to a file on your mail server's disk (to survive reboots, etc).
Now the counters are all pretty self explanitory except for "messages relayed". To understand what messages relayed is you need to understand a little bit out my environment. My Linux Sendmail & MailScanner servers are front-end servers, meaning that these server scan inbound and outbound messages and if the messages are clean then they are relayed to internal corporate email servers. Basically you can consider messages relayed like this,
messages received - spam messages = messages relayed
Simple enough? Most of us probably won't care about messages relayed (and in fact I'm not even graphing it, because it's kind of infered in the graph automatically by the bit of green you can see on the graph).
Okay now that you've created the maillog watcher script (remember to make it executable, chmod 755 is your friend), you need to make sure it runs when you reboot your server. The simplest way is to just add it to your rc.local file. Just tag /opt/watchmaillog/watchmaillog.sh (or where ever you stored it) at the end. Let's not start the watchmaillog.sh script yet. I know you want to, but hold on, we're only about one quarter of the way there.
Next we need to create an empty counters file. Below is an empty counters file. Mine is stored in /opt/watchmaillog (which is the same place that the rest of the scripts reside for simplicity sake).
/opt/watchmaillog/watchmaillog_counters
Code: Select all
mess_recv:0
mess_rejected:0
mess_relay:0
mess_sent:0
mess_waiting:0
spam:0
virus:0
Read Counters Script
So here's the source code. Again, I've chosen to store my code in /opt/watchmaillog, but you can put it anywhere.
/opt/watchmaillog/readcounters.sh
Code: Select all
#!/usr/bin/perl
#
# Script called by Net-SNMP to read the watchmaillog counters
#
# By Jason Warnes
#
# 2006-09-18: Added new item mess_waiting, which is the number of messages MailScanner
# detects when a new batch is started.
# 2006-10-11: Fixed bug where counter is flaged to be reset but the main watchmaillog
# daemon hasn't run to reset the counter. Don't reset it here, just return a
# zero value. (Thanks rbl!)
# 2006-11-08: Added new item mess_rejected, which is the number of rejected
# messages by Sendmail.
$counterfile="/opt/watchmaillog/watchmaillog_counters";
$resetfile="/opt/watchmaillog/watchmaillog_reset";
$oid{"spam"}=".1.3.6.1.4.100.2";
$oid{"virus"}=".1.3.6.1.4.100.4";
$oid{"mess_recv"}=".1.3.6.1.4.100.5";
$oid{"mess_sent"}=".1.3.6.1.4.100.6";
$oid{"mess_relay"}=".1.3.6.1.4.100.7";
$oid{"mess_waiting"}=".1.3.6.1.4.100.8";
$oid{"mess_rejected"}=".1.3.6.1.4.100.9";
&readcounterfile;
# If the counter is nothing set it to zero
if($counter{$ARGV[0]}==""){$counter{$ARGV[0]}=0;}
# If a counter reset file exists set the counter to zero
if(-e $resetfile."_".$ARGV[0]){$counter{$ARGV[0]}=0;}
# Return the value of the counter
print "$oid{$ARGV[0]}\ngauge\n$counter{$ARGV[0]}\n";
&createresetflagfile;
sub readcounterfile { # Subroutine to read the contents of the counter file
open(COUNTER,$counterfile);
while($line=<COUNTER>){
@line=split(/\:/,$line);
chop($line[1]); # Drop the trailing LF off the value
$counter{$line[0]}=$line[1];
}
close(COUNTER);
}
sub createresetflagfile { # Subroutine to create the reset counter flag file
open(RESET,">".$resetfile."_".$ARGV[0]);
close(RESET);
chmod(0666,$resetfile."_".$ARGV[0]);
}
In order to get Net-SNMP to run the script you need to modify your snmpd.conf file and add these lines,
snmpd.conf additions
Code: Select all
pass .1.3.6.1.4.100.2 /opt/watchmaillog/readcounters.sh spam
pass .1.3.6.1.4.100.4 /opt/watchmaillog/readcounters.sh virus
pass .1.3.6.1.4.100.5 /opt/watchmaillog/readcounters.sh mess_recv
pass .1.3.6.1.4.100.6 /opt/watchmaillog/readcounters.sh mess_sent
pass .1.3.6.1.4.100.7 /opt/watchmaillog/readcounters.sh mess_relay
pass .1.3.6.1.4.100.8 /opt/watchmaillog/readcounters.sh mess_waiting
pass .1.3.6.1.4.100.9 /opt/watchmaillog/readcounters.sh mess_rejected
logrotate Configuration
In order to have logrotate restart the watchmaillog.sh script when it rolls the maillog file we need to modify the logrotate configuration for syslog because this is the one that handles the /var/log/maillog rotating. On my FC5 system I had to modify /etc/logrotate.d/syslog and now mine looks like this,
/etc/logrotate.d/syslog
Code: Select all
/var/log/messages /var/log/secure /var/log/maillog /var/log/spooler /var/log/boot.log /var/log/cron {
sharedscripts
postrotate
/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
/bin/kill -HUP `cat /var/run/watchmaillog.pid 2> /dev/null` 2> /dev/null || true
endscript
}
Cacti Templates
Below are the data and graph templates that I made too. Then you'll need to,
- Import the data and host templates.
- Add the data sources to the Cacti device for your mail server. This is done using the Data Sources menu item on the console. The new data sources are
- watchmaillog - MailScanner SPAM
- watchmaillog - MailScanner Viruses
- watchmaillog - Sendmail Messages Recieved
- watchmaillog - Sendmail Messages Rejected
- watchmaillog - Sendmail Messages Relayed
- watchmaillog - Sendmail Messages Sent
- watchmaillog - MailScanner Messages Waiting
- Then add the watchmaillog - Sendmail & MailScanner Stats and watchmaillog - MailScanner Messages Waiting graphs to your mail server device. This is done through the Graph Management menut item in the console. Select the appropriate data sources for the graphs. They should be pretty self-explanitory as to what data source to use.
Well I hope it works for everyone. It's been tested and running on Fedora Core 5, but the script isn't doing anything too crazy and should work on any other Linux or BSD distros with few (or no) modifications. I went into quite a bit of detail here that most probably don't care, but I wanted to make sure that people understood what was going on (in case some people wanted to hack it up a bit). And I'll try and be a bit more active in the forums to help people that may be having problems with it. But I do have a "day job" so most of my replies may come at night.
I already know what I want to do next to this version. I'm going to look at converting the counters to act more like true SNMP 32-bit integer counters, so instead of relying on the reset file to be created and the maillog watcher script noticing it and reseting the counter, I'll just let the counter wrap past the 32-bit max value and then start at zero again. This is how interface octet SNMP counters work. So I'll start a new thread once that version is ready.