Ken A wrote:
Hi,

Nice plugin.

In localmailusers.pm:

current <sourcefiles> are /etc/aliases and /etc/passwd

What about virtualusertable?

Yeah, should be easy enough to add.


In validlocaluser.pm:

#    Note that the local mail domain can be a perl RegEx,
#    i.e. local_mail_domain (:?foo\.com|bar\.org)


But the more you expand your regex to include more than one domain, the more invalid addresses you actually will accept, right?

Yes, if [EMAIL PROTECTED] is a valid user, but [EMAIL PROTECTED] is not


Seems like this would be suited to a location with a single domain. Otherwise, it might be better to build a list of valid full email addresses, rather than a list of users with the appended regex for the domain.

I thought about that. That's for version 2 I guess. My main priority was to get it to work for my own domain.



The other thing that would get FPs is mail like this list that is sent To: or cc: users@spamassassin.apache.org
Not sure how you work around that one. Probably would need to lower the scoring a bit, since you'd have a lot of FPs on this one.

Not sure what you mean here, since I'm only looking for invalid users at my domain. The actual contents of To: and Cc: could have any number of entries, but if they aren't in the domains I'm interested in, they will be ignored.



Ken A. Pacific.Net



Brian R. Jones wrote:

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


------------------------------------------------------------------------

#!/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;




--
Brian R. Jones
Ob.  tagline:
        "I never follow the herd.
                Even when it's going the right direction."

Reply via email to