So I wrote a plugin for spamassassin, and I'd like a few volunteers to try/abuse/critique it before I donate it fully to the public domain.

The plugin is ValidLocalUser.pm, and the reason I wrote it is because I get a lot of spam to my domain that has the following signature:

Received: ... for [EMAIL PROTECTED]

To: or Cc: [EMAIL PROTECTED]

Since, counting aliases, I only have a couple of hundred valid users, I figured it would be pretty easy to write a plugin to filter on these non-existent users. Well, it wasn't, but that's just because I had never written OO perl before. :)

Anyway, the plugin consists of three pieces:

LocalMailUsers.pm:
A perl module that supplies a reference to a hash who's keys are all the valid email recipients on my mail server. It is specific to a *nix environment. It seems not unreasonable that a different could be written for other environments.


ValidLocalUser.pm:
        This is the plugin.

validlocaluser.cf:
        The config for the pm

To use:
On my system, I put LocalMailUsers.pm in /usr/local/lib/perl5. If you chose to place it elsewhere, you will need to edit the 'use lib' line in ValidLocalUser.pm.
ValidLocalUser.pm and validlocaluser.cf go in your config directory. Since I'm running sitewide, mine is /etc/mail/spamassassin.
You will need to edit validlocaluser.cf and change 'local_mail_domain mycompany.com' to reflect you local domain.



TIA

--
Brian R. Jones
Ob.  tagline:
        "I never follow the herd.
                Even when it's going the right direction."
#!/usr/bin/perl
#
#       Package to provide lists of current and former mail users and valid
#       mail domains 
#
#       Routines:
#
#       local_users() returns a reference to a hash containing valid users
#               current <sourcefiles> are /etc/aliases and /etc/passwd.
#               %{ local_users } =: (
#                       "user1" => <sourcefile>,
#                       "user2" => <sourcefile>,
#                       "user3" => <sourcefile>,
#                               ...
#               );
#
#       old_users() same as local, but uses site specific practices to identify
#               former users.
#
#
#       Brian R. Jones          04/28/2005
#
#       Should probably be Mail::LocalMailUsers, but oh well...
#
package LocalMailUsers;
require 5.6.0;

use strict;
our( @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION );
use Carp;

BEGIN {
     use  Exporter;

     @ISA      = qw(Exporter);
        @EXPORT = qw( old_users local_users );
        @EXPORT_OK      = ();
     %EXPORT_TAGS   = ();
     $VERSION  = "0.34";
}

my $aliasfile   = "/etc/aliases";

#
#       read the aliases file
#
sub read_aliases {
        my $aliasref    = shift;
        my $key;
        open(ALIAS, "<$aliasfile") or do {
                carp "Can't open aliases file $aliasfile: $!";
                return $aliasref;
        };
        while(<ALIAS>) {
                chomp;
                /^#/ and next;
                /^$/ and next;
                /^([\w\.-]+):\s+(\S+)$/ and do {
                        $key = lc($1);
                        ${$aliasref}{$key}  = "aliases" unless 
${$aliasref}{$key};
                };
        }
        close(ALIAS);
        return $aliasref;
}

#
#       Don't use the /etc/passwd, use getpwent instead.
#
sub pw_users {
        my $usersref    = shift;
        my ( $user, $pw, $uid );
        setpwent;
     while ( ( $user, $pw, $uid ) = getpwent ) {
          if ( ( $uid == 0 || $uid > 99 ) && ! ( $user =~ m/^gone-/ ) ) {
                        ${$usersref}{$user}  = "password";
          }
     }
        return $usersref;
}

#
#       former users are still in /etc/passwd, but with "gone-" prepended
#       to their username.
#
sub old_users {
        my %users;
        my ( $user, $pw, $uid );
        setpwent;
     while ( ( $user, $pw, $uid ) = getpwent ) {
          if ( $user =~ m/^gone-(.*)/ ) {
                        $users{$1}  = "password";
          }
     }
        return \%users;
}

sub local_users {
        my %users;
        pw_users(\%users);
        read_aliases(\%users);
        return \%users;
}

1;
loadplugin ValidLocalUser ValidLocalUser.pm

local_mail_domain MyCompany.com

header BADLOCALRCPT1 eval:bad_local_rcpt_1()
header BADLOCALRCPTFEW eval:bad_local_rcpt_few()
header BADLOCALRCPTMANY eval:bad_local_rcpt_many()
header OLDLOCALRCPT1 eval:old_local_rcpt_1()
header OLDLOCALRCPTFEW eval:old_local_rcpt_few()
header OLDLOCALRCPTMANY eval:old_local_rcpt_many()

describe BADLOCALRCPT1 invalid recipient @local address
describe BADLOCALRCPTFEW 2-3 invalid recipients @local address
describe BADLOCALRCPTMANY many invalid recipients @local address
describe OLDLOCALRCPT1 no longer valid recipient @local address
describe OLDLOCALRCPTFEW 2-3 no longer valid recipients @local address
describe OLDLOCALRCPTMANY many no longer valid recipients @local address

score BADLOCALRCPT1 1.0
score BADLOCALRCPTFEW 1.0
score BADLOCALRCPTMANY 2.5
score OLDLOCALRCPT1 0.5
score OLDLOCALRCPTFEW 1.0
score OLDLOCALRCPTMANY 1.0

package ValidLocalUser;
use strict;
use lib '/usr/local/lib/perl5';
use Mail::SpamAssassin;
use Mail::SpamAssassin::Plugin;
use LocalMailUsers qw( old_users local_users );
our @ISA = qw(Mail::SpamAssassin::Plugin);

sub new {
  my ($class, $mailsaobject) = @_;
  $class = ref($class) || $class;
  my $self = $class->SUPER::new($mailsaobject);
  bless ($self, $class);
  $self->register_eval_rule ("bad_local_rcpt_1");
  $self->register_eval_rule ("bad_local_rcpt_few");
  $self->register_eval_rule ("bad_local_rcpt_many");
  $self->register_eval_rule ("old_local_rcpt_1");
  $self->register_eval_rule ("old_local_rcpt_few");
  $self->register_eval_rule ("old_local_rcpt_many");
  $self->{"valid_users"}        = local_users();
  $self->{"old_users"}  = old_users();
  return $self;
}

#
#       Look for the local_mail_domain tag in the config file
#       Note that the local mail domain can be a perl RegEx,
#       i.e. local_mail_domain (:?foo\.com|bar\.org)
#
sub parse_config {
        my ($self, $opts) = @_;
        my $key = $opts->{key};

        if ($key eq 'local_mail_domain') {
                $self->{main}->{conf}->{local_mail_domain} = $opts->{value};
                $self->inhibit_further_callbacks();
                return 1;
        }
        return 0;
}

#
#       Pull the list of local recipients out of the To: and Cc: headers
#       bail out with a null list if local_mail_domain isn't defined.
#
sub _get_local_rcpt_list {
        my ( $self, $permsgstatus ) = @_;
        my ( $addrs, $maildomain );
        my (@addressees);

        return \() unless $self->{main}->{conf}->{local_mail_domain};

        $maildomain = $self->{main}->{conf}->{local_mail_domain};
        $addrs = lc( $permsgstatus->get('ToCc', 0) );
        @addressees =   map { m/\b<?(\S+)[EMAIL PROTECTED]>?\b/oi ? $1 : () } 
split(/,\s?/, $addrs);
        return [EMAIL PROTECTED];
}

#
#       count the number of invalid or old local recipients
#
sub _count_local_rcpts {
        my ( $self, $permsgstatus ) = @_;
        my $dbg = 0;
        for ( @{ $self->_get_local_rcpt_list($permsgstatus) } ) {
                if ( $self->{"valid_users"}->{$_} ) {
                        print STDERR "$_ is a valid local user\n" if $dbg;
                } elsif ( $self->{"old_users"}->{$_} ) {
                        print STDERR "$_ don\'t work here no more.\n" if $dbg;
                        $permsgstatus->{'old_count'}++;
                } else {
                        print STDERR "$_ ain\'t from around these parts.\n" if 
$dbg;
                        $permsgstatus->{'invalid_count'}++;
                }
        }
        return 0;
}
        
#
#       Here are the actual tests
#
        
sub bad_local_rcpt_1 {
        my ( $self, $permsgstatus ) = @_;
        $self->_count_local_rcpts($permsgstatus) unless 
$permsgstatus->{'invalid_count'};
        return $permsgstatus->{'invalid_count'};
}

sub bad_local_rcpt_few {
        my ( $self, $permsgstatus ) = @_;
        $self->_count_local_rcpts($permsgstatus) unless 
$permsgstatus->{'invalid_count'};
        if ( $permsgstatus->{'invalid_count'} > 1 ) {
                return $permsgstatus->{'invalid_count'};
        } else {
                return 0;
        }
}

sub bad_local_rcpt_many {
        my ( $self, $permsgstatus ) = @_;
        $self->_count_local_rcpts($permsgstatus) unless 
$permsgstatus->{'invalid_count'};
        if ( $permsgstatus->{'invalid_count'} > 3 ) {
                return $permsgstatus->{'invalid_count'};
        } else {
                return 0;
        }
}

sub old_local_rcpt_1 {
        my ( $self, $permsgstatus ) = @_;
        $self->_count_local_rcpts($permsgstatus) unless 
$permsgstatus->{'old_count'};
        return $permsgstatus->{'old_count'};
}

sub old_local_rcpt_few {
        my ( $self, $permsgstatus ) = @_;
        $self->_count_local_rcpts($permsgstatus) unless 
$permsgstatus->{'old_count'};
        if ( $permsgstatus->{'old_count'} > 1 ) {
                return $permsgstatus->{'old_count'};
        } else {
                return 0;
        }
}

sub old_local_rcpt_many {
        my ( $self, $permsgstatus ) = @_;
        $self->_count_local_rcpts($permsgstatus) unless 
$permsgstatus->{'old_count'};
        if ( $permsgstatus->{'old_count'} > 3 ) {
                return $permsgstatus->{'old_count'};
        } else {
                return 0;
        }
}

1;

Reply via email to