Sign In/My Account | View Cart  
advertisement


Listen Print

Mail Filtering with Mail::Audit

by Simon Cozens
July 17, 2001

Let's face it. procmail is horrid. But for most of us, it's the only sensible way to handle mail filtering. I used to tolerate procmail, with its grotesque syntax and its less-than-helpful error messages, because it was the only way I knew to separate out my mail. One day, however, I decided that I'd been told "delivery failed, couldn't get lock" or similar garbage for the very last time, and decided to sit down and write a procmail replacement.

That's when it dawned on me that what I really disliked about procmail was the recipe format. I didn't want to handle my mail with a collection of colons, zeroes, and single-letter commands that made sendmail.cf look like a Shakespearean sonnet; I wanted to program my mail routing in a nice, high-level language. Something like Perl, for instance.

The result is the astonishingly simple Mail::Audit module. In this article, we'll examine what we can do with Mail::Audit and how we can use it to create mail filters. We'll also look at the News::Gateway module for turning mailing lists into newsgroups and back again.

What Is It?

Mail::Audit itself isn't a mail filter - it's a toolkit that makes it very easy for you to build mail filters. You write a program that describes what should happen to your mail, and this replaces your procmail command in your .forward or .qmail file.

Mail::Audit provides the functionality for extracting mail headers, bouncing, accepting, rejecting, forwarding, and filtering incoming mail.

A Very Simple Mail Filter

Here's the simplest filter program we can make with Mail::Audit.

    use Mail::Audit;
    my $incoming = Mail::Audit->new;
    $incoming->reject;

If you save this as ~/bin/chuckmail, then you can put the following in a .forward file:

    |~/bin/chuckmail

or in a .qmail file:

    preline ~/bin/chuckmail

Every mail message you receive will now pass through this program. The mail comes into the program via standard input, and the new() method takes it from there and turns it into a Mail::Audit object:

    my $incoming = Mail::Audit->new;

Next, we bounce it as undeliverable:

    $incoming->reject;

We could even get fancy, and supply a reason with the bounce:

$incoming->reject(<<EOF);
    The local user was silly enough to leave chuckmail as his
    mail filter.  Too bad you can't mail him to let him know.
EOF

This reason will be relayed back to the sender as part of the bounce message.

Separating Mail Into Folders

The one thing most people use procmail for is to separate mail out into several mail folders. Here's an example of how we'd do this:

    use Mail::Audit;
    my $item = Mail::Audit->new;

    if ($item->from =~ /perl5-porters/) {
        $item->accept("/home/simon/mail/p5p")
    }

    $item->accept;

Now any mail with perl5-porters in the From: line will be added to the file mail/p5p under my home directory. Any other mail will be accepted into my inbox as normal.

Two things to note here:

  • Once the mail has been filed to mail/p5p via accept(), it leaves the program. Game over, end of story. The same goes for the other methods such as reject(), pipe(), and bounce().
  • The last line in the program should probably be an accept() call; mail that reaches the end of the program without being deposited in a mailbox or rejected will be silently ignored. (This may change to an implicit accept() in a later version, to be more procmail-like.)

If you've got a few mailing lists or people you want to filter, you could do this:

  use Mail::Audit;
  my $item    = Mail::Audit->new;
  my $maildir = "/home/simon/mail/";

  my %lists = (
      perl5-porters    => "p5p",
      helixcode        => "gnome",
      uclinux          => "uclinux",
      'infobot\.org'   => "infobot",
      '@dion\.ne\.jp'  => "yamachan"
  );

  for my $pattern (keys %lists) {
      $item->accept($maildir.$lists{$pattern})
          if $item->from =~ /$pattern/ 
             or $item->to =~ /$pattern/;
  }

  $item->accept;

This time, we perform a regular expression match to see if either the From: line or the To: line match any of the patterns in our hash keys, and if they do, direct the mail to the corresponding folder. Since we're using ordinary Perl regular expressions, we can do this sort of thing:

  '\bxxx.*\.com$'  => "spam"

(And you'd be surprised at quite how much junk mail that one traps.)

Here's another simple but remarkably effective spamtrap recipe:

  $item->accept("questionable")
      if $item->from !~ /simon/i and $item->cc !~ /simon/i;

We check the From: and CC: headers for my name, and if it's not in either - the mail probably isn't to me. This one only makes sense after we've filtered out mailing list messages, which could validly be sent from a subscriber to a generic list address.

Pages: 1, 2, 3, 4

Next Pagearrow