#!/usr/bin/perl # # Author by Rick Horowitz 1998-12-16 # Inspired by, and may show remnants of code from: # "mailstats" by Petter Reinholdtsen # dated 1997-07-09 and part of mrtg2.54c dist # Petter credits Rachel Polanskis for # the original. # Type "perldoc mrtg-mailstat" from a shell prompt for more information # on this script and its use. # use strict; use Socket; use Getopt::Long; # IMPORTANT: THIS NEXT LINE ENSURE THAT CASE MATTERS ON OPTIONS!!! $Getopt::Long::ignorecase = 0; use vars qw($datafile $server $sourceport $oldfrm $oldto $help $newfrm $newto $uptime $mailtype $logbase); # Assign options. ("Illegal" options will be possible, # but we'll warn about them via Usage().) # h = runs the perldoc program to show the mrtg-mailstat manual # m = specifies the type of mail we're checking for # p = specifies the port for reaching the mailstats on the mailserver # s = specifies the location of the mailserver # See the mrtg-mailstat man page for details. GetOptions("help" => \$help, "logbase=s" => \$logbase, "mailtype=s" => \$mailtype, "port=i" => \$sourceport, "server=s" => \$server, "<>" => \&Usage); # HARDCODING SECTION -- MODIFY THESE ONLY IF YOU KNOW WHAT YOU ARE DOING!!! # Set these ONLY if you don't wish to use command line options. For more # information, type 'mrtg-mailstat -h' or 'perldoc mrtg-mailstat' from a # shell prompt. $logbase = '/tmp' unless $logbase; $mailtype = '' unless $mailtype; $server = '' unless $server; $sourceport = '' unless $sourceport; # Location for keeping mail statistics. The numbers stored here are # compared to the new numbers at each run of this script. $datafile = "$logbase/${mailtype}-mailstat.old"; # Core portions of the program. Most of retained code from original # comes into play here. GetHelp() if $help; Usage() unless ($mailtype && $server && $sourceport); ($oldfrm, $oldto) = GetOldStats($datafile); ($newfrm, $newto, $uptime) = GetNewStats($server, $sourceport); PutOldStats($datafile, $newfrm, $newto) || warn "$0: Unable to save stats to $datafile"; print $newfrm-$oldfrm,"\n",$newto-$oldto,"\n","$uptime\nthe mail server\n" if ($oldfrm); ## # Returns first line of file given as param split on (a single) space sub GetOldStats { my($datafile) = @_; if (-e $datafile) { open(OLD, $datafile) || warn "$0: Unable to open $datafile for reading"; my($line) = ; close(OLD); return split(/ /, $line); } else { return(0, 0); } } sub GetNewStats { my($server, $sourceport) = @_; my(@output, $port, $proto, $iaddr, $paddr, $tempuptime, $curfrm, $curto, $uptime); if ( $server eq "localhost" ) { @output = `/usr/sbin/mailstats`; chomp(@output); } else { if ($sourceport =~ /\D/) { $port = getservbyname ($sourceport, 'tcp'); } else { $port = $sourceport; } die "$0: Bad port \"$sourceport\"" unless ($port); $proto = getprotobyname ('tcp') || die "$0: Bad prototype tcp"; $iaddr = inet_aton($server) or die "$0: no host \"$server\""; $paddr = sockaddr_in($port, $iaddr); socket (SOCK, PF_INET, SOCK_STREAM, $proto) or die "$0: socket error $!"; connect (SOCK, $paddr) or die "$0: connect error $!"; while () { push(@output, $_); } close(SOCK) || warn "$0: socket close error $!"; } # Parse only the interesting lines. foreach (@output) { ($curfrm, $curto) = (split(/ +/))[2,4] if (/$mailtype/); if (/UPTIME.*?up\s*(.*?), [0-9]* user/) { $uptime = $1; $uptime =~ s/:([0-9]*)/ hours, $1 minutes/; } } # This line ensures no negative numbers. See "perldoc mrtg-mailstat". ($curfrm, $curto) = noNeg($curfrm, $curto); return ($curfrm, $curto, $uptime); } # End GetNewStats(). # Saves the information to the log file. (Note: Overwrites old contents # which aren't needed anymore, anyway.) sub PutOldStats { my($datafile, $frm, $to) = @_; open(STAT, ">$datafile") || return ""; print STAT "$frm $to\n"; close(STAT); return "1"; } # End PutOldStats(). # Negative values should only occur the first time the script is ever run. # These must be converted to positives so subsequent runs of the script # correctly compute new values. sub noNeg { my ($frm, $to) = @_; $frm = -($frm) if ($frm < 0); $to = -($to) if ($to < 0); return($frm, $to); } # End noNeg(). # Usage() simply checks to see that no "illegal" options # have been entered. If the program is thus improperly # called, it issues a notice explaining how to read the # man page for this program. sub Usage { my $option = shift; print STDERR "You may wish to read the documentation \n"; print STDERR "(type \"perldoc mrtg-mailstat\" or \"mrtg-mailstat -h\")\n"; exit(5); } # End Usage(). sub GetHelp { system("perldoc mrtg-mailstat"); exit(0); } # End GetHelp(). __END__ =head1 NAME mrtg-mailstat - Mail Statistics Collector for MRTG =head1 SYNOPSIS mrtg-mailstat S<[B<-h>]> S<[B<-m> I]> S<[B<-p> I]> S<[B<-s> I]> =head1 DESCRIPTION The B program retrieves statistical information from the mail server. It does not matter whether the script is run on the mail server itself, or on a machine remote from the mail server. =head1 S< Running mrtg-mailstat > S< > The proper way to run B is to call it from an MRTG configuration file. The following provides an example of a valid configuration file entry: =over 2 =item C S S S SH1ETechStop SMTP Mail Statistics E/H1E> S S S S S S S > =back This makes certain assumptions which are outlined below under I. =head1 S< Switches > S< > =head2 Hardcoding versus Using Switches It is not necessary to provide switches to the program, if you prefer to "hardcode" the information in the script itself. "Hardcoding" allows you to call the script with less (or no) flags; information needed by the script, and not provided by a flag, must be "hardcoded" by you before this will work. (This was provided for diehards who hate the flexibility of switches. I provided enough flexibility for them to be inflexible. ;)) To "hardcode" your information into the script, look for the section near the top of the script labeled "HARDCODING SECTION". Change the "null" entries represented by the double double quotes ("") so that they contain the appropriate values. Example: =over 4 =item C<$server = "" unless $server; > =back becomes =over 4 =item C<$server = "mail.techstop.com" unless $server; > =back As stated, this is not necessary; it is not even preferred. You may, and probably should, use the switches in the next subsection. =head2 Invoking Switches B switches adhere to the POSIX syntax for command-line options. This means options can take a variety of forms. For example, to specify running B against port C<7258> of the mail server to obtain reports on smtp mail (see "I under I below) you could use any of the following forms: =over 5 =item C<% mrtg-mailstat -m smtp -p 7258> =item C<% mrtg-mailstat -m=smtp -p=7258> =item C<% mrtg-mailstat -mailtype smtp -port 7258> =item C<% mrtg-mailstat -mailtype=smtp -port=7258> =item C<% mrtg-mailstat --m smtp --p 7258> =item C<% mrtg-mailstat --m=smtp --p=7258> =item C<% mrtg-mailstat --mailtype smtp --port 7258> =item C<% mrtg-mailstat --mailtype=smtp --port=7258> =back Any of the last four forms are preferred. One way of doing options which is I possible is: =over 5 =item C<% mrtg-mailstat --msmtp --p7258> =back Use of that last form may cause results other than you expected. (Important Note: The above examples would also require that the server be "hardcoded" into the script as noted in the subsection on I above.) =head2 Available Switches The following switches are available: =over 2 =item B<--h[elp]> The B<-h> flag causes B to print this manual to standard output. For obvious reasons, this is not normally included in an MRTG configuration file, nor invoked by cron. =item B<--l[ogbase]> The B<-l> flag can be used to specify the directory in which to store the file that holds the values from each run. By default, this file is created in I, but if you need to store it in another directory, this is the flag for you. By the way, if the file doesn't exist, one is created. A side-effect of this is that if you try to use one "logbase" one time, and another the next, the program has no way of warning you. It simply creates a new file, and uses zero values to begin its calculations. This can have strange consequences for your mail statistics. Try to remember where you store your log, or use the hardcoding option (see I above). =item B<--m[ailtype]> The B<-m> flag is used to specify the mail type. The two types reported by I are "esmtp" and "local". The "esmtp" line reports traffic between the mail server and other systems. The "local" line reports traffic between users on the mail server. For most smaller networks, this likely means that "esmtp" reports mail between their network and the Internet, and "local" reports mail between users within the small network's organization. Possible values for this flag are "smtp" and "local". B =item B<--p[ort]> The B<-p> flag specifies the port whence the statistics are obtained. See the I section below. =item B<--s[erver]> The B<-s> flag specifies the mail server. This can be either a DNS name, or an IP number. =back =head1 DEPENDENCIES The B program makes certain assumptions. The following is quoted with a few changes from the documentation for the original B script upon which B is based. In particular, it is important to note that the original instructions were Solaris-centric. Your system may have different commands for starting and stopping the mail server, and your paths may be different (i.e., may use /usr/local instead of /opt, or whatever). You are assumed to be able to figure that out yourself. =over 2 =item 1: Ensure you have I (or I)on your system. =item 2: Enable I To do this you can just make sure the entry for "sendmail.st" in "sendmail.cf" is uncommented. On the BSD/OS 3.1 system mrtg-mailstat was tested on, the /etc/sendmail.cf entries look like this: OS/var/log/sendmail.st O StatusFile=/var/log/sendmail.st Then do the command: # touch /var/log/sendmail.st And restart sendmail: # /etc/init.d/sendmail stop # /etc/init.d/sendmail start =item 3: Create the following shell-script (B This section is different than the original version!): #!/bin/sh # # smtp-stats: invoke mailstats from a wrapper for use in inetd. # PATH=/bin:/sbin if [ -x "/usr/sbin/mailstats" ] then temp1=`/usr/sbin/mailstats -f/var/log/sendmail.st` fi if [ -x "/usr/bin/uptime" ] then temp2=`/usr/bin/uptime` fi echo -e "$temp1\nUPTIME: $temp2" I run this out of /usr/local/bin, and call it "smtp-stats" =item 4: Now add the following to /etc/services: # /etc/services # smtp-stats 7256/tcp # smtp-stats I used port 7256 as it was undefined. You might like to select something else. =item 5: Add the following to /etc/inetd.conf: # /etc/inetd.conf # smtp-stats stream tcp nowait root \ /usr/local/bin/smtp-stats smtp-stats ***Ensure that the above is all on *one* line. The sample above is broken on 2 lines for legibility!! =item 6: Restart inetd =item 7: Test the port. you should see the following: S< > S S S S S S< M msgsfr bytes_from msgsto bytes_to Mailer > S< 3 79372 3623347K 143989 6954437K local > S< 5 9941 483230K 6669 652586K esmtp > S<======================================== > S< T 89313 4106577K 150658 7607023K > S S =back =head1 FILES Strictly speaking, you needn't worry about the files. If you've created the smtp-stats script noted above, and made the appropriate changes, the only other I you need to be concerned with is B itself. For completeness sake, I list all the files with which B may depend upon, utilize, create, or use. =over 5 =item I =item I =item I =item I =item I =item I =item I<$logbase/${mailtype}-mailstat.old> =back =head1 AUTHOR This B program was written by Rick Horowitz , tested, and released to the MRTG mailing list on December 16, 1998. It was inspired by, and contains remnants of code from: =over 10 =item "mailstats" by Petter Reinholdtsen dated 1997-07-09 and part of mrtg2.54c dist =back Petter credits Rachel Polanskis for the original. =cut