[comp.lang.perl] Yet another mail filter

kaul@icarus.eng.ohio-state.edu (Rich Kaul) (11/15/90)

Some time ago people got to posting scripts that did mail actions, so
I thought I`d throw in my entry.  I get a fair quantity of mail, much
of it from mailing lists, the various computers I deal with and other
users.  I prefer that such mail be placed in a format that I can read
with my favorite news reader, GNUS.  Here's what I came up to do my
mail delivery.  It's a bit more than twice as fast as the old shell
script I used to do this.  The scheme I use is to try and decode the
"From " line (for well behaved lists and personal mail) and check the
"To:" and "CC:" lines (for less well behaved mailing lists).  If it's
from a mailing list, drop it in a private news-like directory
structure in a file that's appropriately numbered.  If it's from
someone on a short list, drop it in the mailbox, otherwise drop it in
a generic directory and number it correctly.

Comments are certainly welcome, especially as to style and efficiency.
I haven't been writing perl very long, as may be obvious from some of
the hacks.  I'd love to see someone turn it into a one-liner ;-)

#!/usr/bin/perl

# A crude attempt at a perl mail delivery agent.  The basic premise is
# that mail from certain people demands attention immediately, so it
# gets put in the normal system mailbox.  Mail from mailing lists is
# to be placed in various subdirectories in a format that GNUS can
# handle for reading.  Mail from the system, random users, etc, gets
# placed in a third directory ($Root).  This is, of course, easily
# customizable.  Delivering into a directory requires a .last file to
# keep track of article numbers.
#
# Pardon the perl style; I'm just learning but this works!  Comments
# welcome.
#
# Author:	Rich Kaul (kaul@icarus.eng.ohio-state.edu)
# Date:		11/13//90
# The credit where credit is due department:  the core of the message
# parsing is based on Larry Wall's mailagent script, with much of the
# functionality removed.
#
# Usage:  Make a .forward file like:
# "| /usr/1/kaul/bin/pmd /usr/1/kaul kaul >> /usr/1/kaul/.maillog 2>&1"

($HOME, $USER) = @ARGV;

$Root = "$HOME/Memos/personal";		# root of personal news tree.
$dest = $Root;
$box = "/usr/spool/mail/$USER";		# default mail box.
$GIVE_UP_BOX = "$HOME/mail_dump";	# emergency dumping box.

$LOCK_SH = 1;				# Values for flock() calls.
$LOCK_EX = 2;
$LOCK_NB = 4;
$LOCK_UN = 8;

umask(077);				# Get a little privacy.

# Now run and get the headers.  We have to parse the headers before we
# can figure out delivery.
while (<stdin>) {
    # Do this on the From_ line only.
    if (1 .. 1) {
	if (/^From\s+(\S+)\s+[^\n]*(\w{3}\s+\d+\s+\d+:\d+)/) { $from = $1; }
	$from =~ s/@.*//;		# remove trailing @machine
	$from =~ s/%.*//;		# remove trailing %machine
	$from = $1 if $from =~ /.*!([^\n]+)/; # remove leading ! paths
    }

    # This section operates on the header of the message.  We create
    # an array of header keys to work on later.
    if (1 .. /^\s*$/) {
	s/^From: ([^<]*)\s+<(.*)>$/From: $2 ($1)/;	# rewrite ugly header
	$header .= $_;
	chop;
	if (/^\s*$/) {
	    foreach $key (keys(header)) {
		eval "\$H$key = \$header{'$key'}";
	    }
	}
	else {
	    if (s/^([-\w]+):\s*//) {
		($headline = $1) =~ y/A-Z-/a-z_/;
		$header{$headline} .= "\n" if $header{$headline} ne '';
	    }
	    else {
		s/^\s+/ /;
	    }
	    $header{$headline} .= $_;
	}
    }
    # And here we make the body of the message.
    else {
	$body .= $_;
	}
}

# Ok, now figure out where to deliver the message.  The first case is
# mail from well behaved mailing lists (i.e. ones that advertise that
# they are mailing lists).
if($from =~ /firearms/) {$dest = "$Root/firearms";}
elsif($from =~ /Info-VM/ || $from =~ /Bug-VM/) {$dest="$Root/bug-vm";}
elsif($from =~ /freemacs/) {$dest = "$Root/freemacs";}

# Sometimes we have dumb mailing lists that can't get their act
# together.  The interviews, oct and some freemacs stuff are prime examples.
if ($Hto =~ /gif/ || $Hcc =~ /gif/) {$dest = "$Root/gif";}
elsif($Hto =~ /gnuplot/ || $Hcc =~ /gnuplot/) {$dest = "$Root/gnuplot";}
elsif ($Hto =~ /freemacs/ || $Hcc =~ /freemacs/) {$dest = "$Root/freemacs";}
elsif ($Hto =~ /interviews/i || $Hcc =~ /interviews/i ) { 
    $dest = "$Root/interviews";}
elsif ($Hto =~ /vem/i || $Hto =~ /oct/i || $Hcc =~ /vem/i || $Hcc =~ /oct/i) {
    $dest = "$Root/vem";}
elsif($Hto =~ /xviewbug-trackers/ || $Hcc =~ /xviewbug-trackers/) {
    $dest = "$Root/xview";}

# Now we list the people who can crash into the mailbox.
if($from =~ /karl_kl/ || $from =~ /alden/ || $from =~ /monty/) {$dest = $box;}
elsif($from=~/pnelson/ || $from=~/wilson/i || $from =~ /gratz/i) {$dest=$box;}
elsif($from =~ /bibyk/ || $from =~ /adkins/ || $from =~ /zaka/) {$dest = $box;}
elsif($from =~ /kaul/) {$dest = $box;}

# Do the delivery.  There are two cases.  If we are delivering
# to a directory find and update the .last file there.
if ( -d $dest ) {
    # It's going to be a news article, so change the From_ line
    $header =~ s/^From /Unix-From: /;
    $all = $header . $body;

    $count_file = "$dest/\.last";
    open(COUNTER,"+<$count_file")|| do gag("Can't open $dest/.last ($< $>): $!");
    while (<COUNTER>) {
	chop;
	$count = $_;
	$count++;
    }
    do flocker(COUNTER,$LOCK_EX);	# Lock the file, just in case.
    seek(COUNTER,0,0);
    print COUNTER $count,"\n";
    do flocker(COUNTER,$LOCK_UN);

    # We now have the article number we need.
    open(ART,">>$dest/$count") || do gag("Can't open $dest/$count ($< $>): $!");
    print ART $all,"\n";
} else {
    # Here we're delivering to a file, which is easier.  Build and deliver.
    $all = $header . $body;
    open(BOX, ">>$dest") || do gag ("Can't open $dest ($< $>): $!");

    do flocker(BOX,$LOCK_EX);
    print BOX $all,"\n\n";
    do flocker(BOX,$LOCK_UN);
}

# File locking subroutine.
sub flocker {
    local ($file, $mode) = @_;
    eval 'flock($file,$mode);';
    seek($file, 0, 2);		# in case it was appended while we were waiting
}

# For some reason mail couldn't get delivered.  Dump the message in the
# emergency mailbox and exit.
sub gag {
    open(ERR_BOX, ">>$GIVE_UP_BOX") || die "I'm really hosed.  I can't even open the emergency box $GIVE_UP_BOX\n";

    print @_;
    do flocker(ERR_BOX,$LOCK_EX);
    print ERR_BOX $all,"\n\n";
    do flocker(ERR_BOX,$LOCK_UN);
    exit 1;
}