#!/usr/bin/perl # $Id: mrtg-mailstats,v 1.9 2002/09/22 00:04:29 rowan Exp $ # collect mailstats for MRTG use, and let MRTG do the differencing # # Copyright (C) 1999-2002 John "Rowan" Littell # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # ######################################################################## # # The latest version should be available from: # http://www.earlham.edu/~littejo/software/mrtg-mailstats.tgz # # A web page describing this program is available at: # http://www.earlham.edu/~littejo/software/mrtg-mailstats.html # ######################################################################## # # sendmail config selection code added by # Alexandre Roberto Zia # sendmail 8.12.x mailstats (MTA & MSP) processing code provided by # Derek J. Balling # messages rejected/discarded count (-r option) suggected by # Antonio Casado Rodriguez # # inspired by code from: # rachel polanskis # Petter Reinholdsten (findFirstExecutable()) # Rick Horowitz # ######################################################################## # # usage: mrtg-mailstats [-hvbr] [-S from|to|rej|dis|total] [-f factor] # [-m mailer] [-s host [-p port]] # # -h this screen # -v print version number and exit # -b count total bytes (default is number of messages) # -r count rejected and discarded messages # -m specify mailer (default is total (T)) # -s specify remote host name (default is local) # -p specify remote host port (default is 7256) # -f specify multiplication factor (default is 1) # -S use output appropriate for UCD-SNMP exec MIB # -C specify sendmail configuration file # ######################################################################## ## program ID $VERSION="1.30"; $NAME="mrtg-mailstats"; ## using... require 5.002; use Getopt::Std; use Socket; ## needed programs, if running local @MAILSTATS=( "/usr/sbin/mailstats", "/usr/bin/mailstats" ); $HOSTNAME="/bin/hostname"; ## default values $D_PORT="7256"; $D_MAILER="T"; $D_FACTOR=1; $D_MAILSTATS_PARAM=""; MAIN: { my ($opts, $FACTOR, $REMOTE, $BYTES, $HOST, $PORT, $MAILER, $SNMP, $MAILSTATS_PARAM); getopts ('vhbrf:m:s:p:S:C:', \%opts); if (!%opts) { # we're using an old version of Getopt::Std -- foo $opts{'v'} = $opt_v; $opts{'h'} = $opt_h; $opts{'b'} = $opt_b; $opts{'r'} = $opt_r; $opts{'f'} = $opt_f; $opts{'m'} = $opt_m; $opts{'s'} = $opt_s; $opts{'p'} = $opt_p; $opts{'S'} = $opt_S; $opts{'C'} = $opt_C; } ($opts{'h'}) && (usage($opts{'v'})); if ($opts{'v'}) { version(); exit; } if ($opts{'s'}) { $HOST = $opts{'s'}; if (!$opts{'p'}) { $PORT = $D_PORT; } else { $PORT = $opts{'p'}; } $REMOTE = 1; } else { usage() if ($opts{'p'}); $HOST = `$HOSTNAME`; chop $HOST; $REMOTE = 0; } $MAILSTATS_PARAM = $D_MAILSTATS_PARAM; ($opts{'C'} && -r $opts{'C'}) && ($MAILSTATS_PARAM = "-C $opts{'C'}"); $FACTOR = $D_FACTOR; ($opts{'f'}) && ($FACTOR = $opts{'f'}); $MAILER = $D_MAILER; ($opts{'m'}) && ($MAILER = $opts{'m'}); $BYTES = 0; ($opts{'b'}) && ($BYTES = 1); $REJECT = 0; ($opts{'r'}) && ($REJECT = 1); $SNMP = ""; ($opts{'S'}) && ($SNMP = $opts{'S'}); stats ($REMOTE, $BYTES, $REJECT, $HOST, $PORT, $MAILER, $FACTOR, $SNMP, $MAILSTATS_PARAM); } sub usage { my ($ver) = @_; version() if ($ver); print "usage: $NAME [-hvbr] [-f factor] [-m mailer] [-s host [-p port]]\n\n"; print "Options:\n"; print "\t-h\tthis screen\n"; print "\t-v\tprint version number and exit\n"; print "\t-b\tcount total bytes (default is number of messages)\n"; print "\t-r\tcount rejected and discarded messages\n"; print "\t-m\tspecify mailer (default is total ($D_MAILER))\n"; print "\t-s\tspecify remote host name (default is to run local)\n"; print "\t-p\tspecify remote host port (default is $D_PORT)\n"; print "\t-f\tspecify multiplication factor (default is $D_FACTOR)\n"; print "\t-S from|to|total specify output in a format that can be used in SNMP\n"; print "\t-C\tspecify sendmail configuration file\n"; print "\nFor further information, run 'perldoc mrtg-mailstats'\n"; exit; } sub version { print "$NAME $VERSION\n" } sub findFirstExecutable { my($filename); foreach $filename (@_) { return $filename if ( -x $filename && ! -d $filename ); } } sub stats { my ($remote, $bytes, $reject, $host, $port, $mailer, $factor, $snmp, $mailstats_param) = @_; my ($line, @line, $start, $m); my ($msgsfr, $bytesfr, $msgsto, $bytesto, $msgsrej, $msgsdis) = 0; my ($mailerstr); my ($stringmailer, $mailerfield); my ($errstr) = ""; if ($remote) { # open network pipe my ($iaddr, $paddr, $proto); if ($port =~ /\D/) { $sourceport = $port; $port = getservbyname ($port, 'tcp') or ErrDie ("Error: unknown port $sourceport", $host, $snmp); } $iaddr = inet_aton ($host) or ErrDie ("Error: host not found \"$host\"", $host, $snmp); $paddr = sockaddr_in ($port, $iaddr); $proto = getprotobyname ('tcp') or ErrDie ("Error: bad proto, no biscuit", $host, $snmp); socket (STATS, PF_INET, SOCK_STREAM, $proto) or ErrDie ("Error: socket error $!", $host, $snmp); connect (STATS, $paddr) or ErrDie ("Error: connect error $!", $host, $snmp); } else { # open local pipe from the mailstats program my ($mailstats) = findFirstExecutable (@MAILSTATS); ErrDie ("Error: can't find `mailstats`", $host, $snmp) if (!$mailstats); open STATS, "$mailstats $mailstats_param|" or ErrDie ("Error: mailstats error $mailstats: $!", $host, $snmp); } $mailer = "T" if ($mailer eq "total"); $stringmailer = 1 if ($mailer =~ /\D/ && ($mailer ne "T" && $mailer ne "C")); while () { chop; $line = $_; if ($. == 1) { if (/MSP statistics/) { # we have post-8.12.0 sendmail that reports mail submission # as well as mail transport -- ignore mail submission while ($line !~ /MTA statistics/) { $line = ; } $_ = ; } ($start) = m/Statistics from (.*)/; } elsif ($line =~ /^\s+M/) { # check whether we've got a new (post-8.9.0) sendmail if (/msgsrej/ || /msgsdis/) { $mailerfield = 8; } else { $mailerfield = 6; } } else { @line = split (/\s+/, $line); if ($line[1] eq $mailer || ($stringmailer && $line[$mailerfield] =~ /^$mailer$/)) { ($mailerfield == 6) && (($msgsfr, $bytesfr, $msgsto, $bytesto, $mailerstr) = ($line[2], $line[3], $line[4], $line[5], $line[$mailerfield])); ($mailerfield == 8) && (($msgsfr, $bytesfr, $msgsto, $bytesto, $msgsrej, $msgsdis, $mailerstr) = ($line[2], $line[3], $line[4], $line[5], $line[6], $line[7], $line[$mailerfield])); if ($bytes) { chop $bytesfr; # remove "K" chop $bytesto; $bytesfr *= 1024; $bytesto *= 1024; } } } } shutdown STATS, 2 if ($remote); close STATS; # the totals line lacks a mailer name ($mailer eq "T") && ($mailerstr = "total"); # did we find the mailer that was requested? ($mailerstr eq "") && ($errstr = "Error: mailer \"$mailer\" not found"); # the first line should tell stats start date, otherwise it's not Sendmail ($start eq "") && ($errstr = "Error: mailstats output can't be parsed"); ($errstr ne "") && ErrDie ($errstr, "$host ($mailer)", $snmp); if ($bytes) { if ($reject) { ErrDie ("byte count not available", "$host ($mailer)", $snmp); } $bytesfr *= $factor; $bytesto *= $factor; if (!$snmp) { MRTGOutput ($bytesfr, $bytesto, $start, "$host ($mailer)"); } else { my ($output); ($snmp =~ /^from$/i) && ($output = $bytesfr); ($snmp =~ /^to$/i) && ($output = $bytesto); ($snmp =~ /^total$/i) && ($output = $bytesfr + $bytesto); ($snmp =~ /^rej/i || $snmp =~ /^dis/i) && (ErrDie ("byte count not available", "$host ($mailer)", $snmp)); SNMPOutput ($output); } } elsif ($reject) { $msgsrej *= $factor; $msgsdis *= $factor; if (!$snmp) { MRTGOutput ($msgsrej, $msgsdis, $start, "$host ($mailer)"); } else { SNMPOutput (-1); } } else { $msgsfr *= $factor; $msgsto *= $factor; if (!$snmp) { MRTGOutput ($msgsfr, $msgsto, $start, "$host ($mailer)"); } else { my ($output); ($snmp =~ /^from$/i) && ($output = $msgsfr); ($snmp =~ /^to$/i) && ($output = $msgsto); ($snmp =~ /^rej/i) && ($output = $msgsrej); ($snmp =~ /^dis/i) && ($output = $msgsdis); ($snmp =~ /^total$/i) && ($output = $msgsfr + $msgsto + $msgsrej + $msgsdis); SNMPOutput ($output); } } } sub MRTGOutput { my ($msgsfr, $msgsto, $start, $host) = @_; # changed by teslina, da mailstat nicht das aktuelle datum anzeigt. $start = `date`; #print "$msgsfr\n$msgsto\n$start\n$host\n"; print "$msgsfr\n$msgsto\n$start$host\n"; } sub SNMPOutput { my ($n) = @_; print "$n\n"; } sub ErrDie { my ($errstr, $host, $snmp) = @_; if (!$snmp) { MRTGOutput ("0", "0", $errstr, $host); } else { SNMPOutput (0); } exit (1); } =pod =head1 NAME mrtg-mailstats - collect Sendmail mailstats data and format it for MRTG =head1 SYNOPSIS B [C<-hvbr>] [C<-S from|to|rej|dis|total>] [C<-f factor>] [C<-m mailer>] [C<-s host> [C<-p port>]] [C<-C sendmail.cf>] =head1 DESCRIPTION B collects Sendmail statistics data from the mailstats(1) program and formats it so that B (Multi Router Traffic Grapher) can include it in its collected data. The mailstats program may be run either on the local host (mrtg-mailstats will run it directly) or on a remote host, in which case mrtg-mailstats will connect via TCP to a port where mailstats is run. The following options are recognized by mrtg-mailstats: =over =item C<-b> record bytes rather than message counts (the default is messages). =item C<-f factor> specify a factor by which to multiply the output of the mailstats(1) program. This may be helpful for monitoring servers which process a small number of messages. A factor of 60 will effectively count bytes or messages per minute; 1200 would be equal to per hour counts. The default factor is 1 (no multiplication). This option will get around the B limitation that data are stored as integers and previous data are stored as averages over the time period. This will appear in low traffic servers as a flat graph, even if there are messages being sent. This option is of less use if B is used as the data storage mechanism. =item C<-h> generate a help synopsis. =item C<-m mailer> specify the mailer from which to collect data. This may be specified either by number or by name (the "M" or "Mailer" columns in mailstats(1) output). The default is "T" or total counts. =item C<-p port> specify the port for connecting to a remote server. Default is 7256. =item C<-r> get rejected and discarded message counts for the specified mailer (or total). Byte count (C<-b>) is incompatible with this option. =item C<-s server> specify the server host name or IP address. This option puts mrtg-mailstats into remote mode where it collects statistics from a remote mailserver rather than running mailstats(1) on the local host. =item C<-S from|to|rej|dis|total> print the requested value by itself on a single line to STDOUT. This makes B easier to use in the B MIB of UCD SNMP. See snmpd.conf(5) for more details. =item C<-C sendmail.cf> specify a particular sendmail configuration file. This is useful if you have two instances of sendmail (e.g., a gateway mail router) and wish to run B on each. B will verify that the file exists and is readable and then pass this file as a parameter to the mailstats(1) program with the C<-C> flag. =item C<-v> print version number and exit. =back 4 =head2 Remote Operation The default mode for mrtg-mailstats is to run mailstats(1) on the local host and parse its output. In remote operation, mrtg-mailstats will connect to a remote server on a specified port number (default 7256). It is expected that immediately upon connection, a mailstats(1) process on the remote host will be started with its output directed to the the connection socket. The easiest way to do this is through inetd(8). Add a line in the F file with the service name for the desired port and a program specification of /usr/sbin/mailstats: mailstats stream tcp nowait root \ /usr/sbin/mailstats mailstats You may also need to add the appropriate entry to F (or your local equivalent) to be able to use the service name in F. Similar methods can be used if you are instead using xinetd(8). For the security conscious, some form of access control (e.g., TCP Wrappers) is suggested for limiting access to the mailstats(1) port. =head2 MRTG Target Specification Mailstats targets are easily included with the standard targets in the F file: Target[mail]: `/usr/local/bin/mrtg-mailstats` Title[mail]: Mailstats MaxBytes[mail]: 1048576 # 1 MB / sec PageTop[mail]:

Sendmail traffic

This will count total mail messages for the local host. To count bytes transferred on a remote host, the following might be appropriate: Target[mail_remote]: `/usr/local/bin/mrtg-mailstats -b -s \ smtp.domain.tld` Title[mail_remote]: Mailstats for smtp.domain.tld MaxBytes[mail_remote]: 1048576 PageTop[mail_remote]:

Sendmail traffic for smtp.domain.tld

You may wish to use the B, B, B, B, and B directives to change the graph captions. =head2 Low Traffic Servers MRTG uses its accompanying B program to store historic values in the log file for the specified target. Since B only records integers and historic data are stored as averages, it will be the case for small increments that the average will be less than one, and thus recorded as zero. For example, if a server has processed 12 messages during a five minute interval, the average for that time is 0.04 messages per second. If this is consistent behavior, MRTG will never notice that the server has any traffic whatsoever. To get around this problem, the B<-f> switch was added to multiply the data reported to MRTG by a constant factor. This factor may be anything you choose, so long as it consistently produces numbers that the rateup program will record. In the previous example, if a factor of 60 is specified, the number of messages reported to MRTG will be 720, and the five minute average will be 2.4, which will be recorded as 2. Note that a factor of 60 will be equivalent to recording messages per minute, and a factor of 3600 will be equivalent to messages per hour. The graphs produced from mrtg-mailstats using the B<-f> option may appear jumpy since the granularity of data recorded in this mode can be quite coarse. A more elegant solution to this problem is to use B to store the data rather than rateup. Currently, MRTG has good support for B, although it is not yet part of the standard distribution. =head2 Use with the UCD-SNMP exec MIB If the UCD-SNMP agent is already running on the sendmail server, mrtg-mailstats can be called via the F MIB. The proper configuration for the UCD-SNMP agent will be similar to the following: exec mrtg-mailstats -S total The output of mrtg-mailstats will be a single line with the number corresponding to the requested value for the specified mailer (the sendmail totals if not specified). This conforms to the F MIB specification; the values will appear in F (.1.3.6.1.4.1.2021.8). Typically, one will want to have at least two SNMP entries: one for "from" and one for "to". Together, these will give the same effect as the standard form of the script and both can be queried in the target in the MRTG configuration file. The target should be specified in this case as Target[mail]: OID_1&OID_2:public@mymailserver where OID_1 and OID_2 are the numeric or symbolic names corresponding to the appropriate MIB variables (see the UCD-SNMP documentation for further details). =head1 BUGS On Sendmail 8.12.0 and higher servers that include message submission statistics (MSP), this is ignored until the MTA statistics start. If your Sendmail includes this, then the F will look something like this: MSP statistics... Statistics from Sun Aug 11 20:28:59 2002 M msgsfr bytes_from msgsto bytes_to msgsrej msgsdis Mailer ============================================================= ... MTA statistics... Statistics from Tue Aug 6 06:29:08 2002 M msgsfr bytes_from msgsto bytes_to msgsrej msgsdis Mailer ... =head1 SEE ALSO mailstats(1), sendmail(8), inetd(8), snmpd(1), snmpd.conf(5) =head1 AUTHOR John "Rowan" Littell Sendmail config file selection code added by Alexandre Roberto Zia . Sendmail 8.12.x processing code provided by Derek J. Balling. Rejected and discarded message count option suggested by Antonio Casado Rodriguez. This version was inspired by previous versions from the following authors: rachel polanskis , Petter Reinholdsten , Rick Horowitz =cut