[comp.lang.perl] A script to do accounting from sendmail logs

dem@meaddata.com (David Myers) (03/11/91)

   Here's a perl script I cooked up to do accounting from sendmail
logs.

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	syslog.perl
#
if test -f 'syslog.perl'
then
	echo shar: will not over-write existing file "'syslog.perl'"
else
	echo x - 'syslog.perl'
	sed 's/^X//' >'syslog.perl' << 'SHAR_EOF'
X#!/usr/local/bin/perl
X#
X# Extracts mail accounting information from sendmail logs.
X#
X# usage: <whatever you call this> <sendmail log file>
X#
X# The idea here is to look at each message logged by sendmail, and to
X# try to determine which local user is responsible for it.  That user
X# is then "charged" for it.  Only sucessfully delivered mail is
X# accounted for.
X#
X# If your sendmail produces logging information in a different
X# format than that of the sendmail shipped with SunOS 4.[01], you're
X# SOL.
X#
X# The basic algorithm is to first look at the sender, and if the
X# address is local, charge to that user.  If not, look at the
X# recipient, and if local, charge.  If neither the sender nor
X# recipient are local, charge to the fictional user "external".
X#
X# The actual routines that try to determine whether or not a user is
X# local are very simplistic, and likely not to be accurate for every
X# situation.  Also, you might want to change the field widths of the
X# printed fields to be more appropriate for your site.  I run this
X# script once a week, and for a small- to medium-sized UUCP site like
X# us, the output is just what I want.
X#
X# Like many, I'm a perl novice, and the Camel book has not made it to
X# the local bookstore yet.  So I'm sure this isn't the most efficient
X# way to do things.  But since this is run in the early morning hours
X# from cron, what do I care how slow it is?
X#
X# "Just another perl hacker" (where have I heard that before? :-)
X# dem@meaddata.com (David Myers)
X
X
X# The local domain name - CHANGE THIS.
X$domain = "meaddata.com";
X
X# The name of the sendmail log - CHANGE THE DEFAULT.
X$syslog = (shift || "/var/log/syslog");
X
X
X# OK, boys - lets do it.
X
X# Open sesame.
Xopen(syslog) || die "Just where is this file $syslog?\n";
X
X# Read in all user names for use in the report.
Xwhile (($login, $p, $u, $g, $q, $c, $name, $h, $s) = getpwent) {
X    $name{$login} = $name;
X}
Xendpwent;
X
X# Read in all host names and *assume they are all local*.
X# This is a big leap of faith, but it works for me.
Xwhile (($host, @misc) = gethostent) { 
X    $localhost{$host} = 1;
X}
Xendhostent;
X
X# Scan the sendmail log.
Xwhile (<syslog>) {
X    ($1, $2, $3, $4, $5, $id, $tofrom, $size, $stat) = split;
X    chop ($id, $tofrom, $size);
X    if ($tofrom =~ /from=/) {	# Found a sender.
X	$from{$id} = substr($tofrom, 5);
X	$size{$id} = substr($size, 5);
X    }
X    elsif (($tofrom) =~ /to=/ && ($stat =~ /Sent/)) { # Found a recipient.
X	if ($from{$id}) {
X
X	    if    ($user = &islocal($from{$id})) {}
X	    elsif ($user = &islocal(substr($tofrom, 3))) {}
X	    else {
X		$user = "external";
X	    }
X
X	    $mesgs{$user}++;
X	    $bytes{$user} += $size{$id};
X
X	    $mesgstotal++;
X	    $bytestotal += $size{$id};
X	}
X    }
X}
Xclose(syslog);
X
X# Sort and print in one shot.
Xforeach $user (sort compare keys %bytes) {
X    $bytespercent = $bytes{$user} / $bytestotal * 100;
X    $mesgspercent = $mesgs{$user} / $mesgstotal * 100;
X    write;
X}
X
X$~ = "total";
Xwrite;
X
X
X# Done.  Subroutines follow.
X
X# Compares the total bytes attributed to two users.
Xsub compare {
X    $bytes{$b} - $bytes{$a};
X}
X
X# Extracts the user component of an address.
X# I suppose this and the next subroutine could have been implemented
X# with regexps.  Would that have been a better idea?
Xsub extractuser {
X    local($addr) = @_;
X    $addr = substr($addr, rindex($addr, '!') + 1);
X    if (($len = index($addr , '@')) > 0) {
X	$addr = substr($addr , 0, $len);
X    }
X    $addr;
X}
X
X# Extracts the host component of an address.
Xsub extracthost {
X    local($addr) = @_;
X    $addr = substr($addr, rindex($addr, '@') + 1);
X    if (($len = index($addr , '!')) > 0) {
X	$addr = substr($addr , 0, $len);
X    }
X    $addr;
X}
X
X# Returns a "billable" user name if the address is local.
Xsub islocal {
X    local($_) = @_;
X    local($host, $user);
X    s/[<>]//g;
X    s/%/@/g;
X    $host = &extracthost($_);
X    $user = &extractuser($_);
X   
X    # If the host is not in our host table, and it's not in our domain,
X    # and extracthost didn't simply return the user name, then the
X    # host is not local.
X    if (!($localhost{$host}) && !($host =~ $domain) && !($host eq $user)) {
X	$user = 0;
X    }
X    $user;
X}
X
X
X
Xformat =
X@>>>>>>>> @##.##%   @>>>> @##.##%   @<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<
X$bytes{$user}, $bytespercent, $mesgs{$user}, $mesgspercent, $user, $name{$user}
X.
X
Xformat total =
X -------- -------   ----- -------   -------------  -------------------------
X@>>>>>>>> @##.##%   @>>>> @##.##%   @<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<
X$bytestotal, 100, $mesgstotal, 100, "TOTAL", ""
X.
Xformat top =
X
X
X
X
X       Bytes          Messages
X   Total  Percent   Total Percent   Login          Full Name
X -------- -------   ----- -------   -------------  -------------------------
X.
SHAR_EOF
if test 4732 -ne "`wc -c < 'syslog.perl'`"
then
	echo shar: error transmitting "'syslog.perl'" '(should have been 4732 characters)'
fi
fi
echo Done
exit 0
--
David Myers                                             (513) 865-1343   
Mead Data Central          Friends don't           Interactive Systems
P.O. Box 933                let friends               dem@meaddata.com
Dayton, Ohio  45401           use DOS.          ...!uunet!meaddata!dem