package PublicCheck::To; # # # use lib qw(/home/mail/perl/sa); use Mail::SpamAssassin::Plugin; use strict; our @ISA = qw(Mail::SpamAssassin::Plugin); # Our area in the conf hash. our($CONF_KEY) = "PublicCheck"; sub new { my ($this, $mailsa) = @_; $this = ref($this) || $this; my $self = $this->SUPER::new($mailsa); bless ($self, $this); $self->register_eval_rule("public_check_to_address"); $self->register_eval_rule("public_check_references"); delete($self->{ADDR}); $self; } sub parse_config { my($self,$opt) = @_; # fill storage in on anything interesting. if($self->{STORE}){ if($self->{STORE}->parse_config($self,$opt)){ return(1); } } # conf:line:value:key:user_config my($key) = $opt->{key}; my($val) = $opt->{value}; if($key eq 'public_address'){ $opt->{conf}{$CONF_KEY}{public_address}{$val} = 1; $self->debug("address: $val is considered public"); $self->inhibit_further_callbacks(); return(1); } # Storage impl. not settable by users. if(! $opt->{'user_config'}){ if($key eq 'public_store_module'){ my($pack,$file) = split(/\s+/,$val); # Storage impl. eval { if($file){ require($file); }else{ eval "use $pack;"; if($@){ die $@; } } $self->{STORE} = $pack->new($self); }; if($@){ # Let us know about the problem. $self->debug($@); } $self->inhibit_further_callbacks(); return(1); } } return(0); } # Get our storage impl. figured out here. sub check_start { my($self,$opt) = (shift,shift); if($self->{STORE}){ $self->{STORE}->check_start($self,$opt); } } # Disconnect from DB, etc.. sub check_end { my($self,$opt) = (shift,shift); if($self->{STORE}){ $self->{STORE}->check_end($self,$opt); } } sub debug { shift; &Mail::SpamAssassin::Plugin::dbg($CONF_KEY . ':' . shift()); } sub per_msg_finish { my($self,$opt) = (shift,shift); if($self->{STORE}){ $self->{STORE}->per_msg_finish($self,$opt); } } sub DESTROY { my($self) = (shift); delete($self->{STORE}); if($self->SUPER::can('DESTROY')){ $self->SUPER::DESTROY; } } ################### actual checkers ######################### sub public_check_to_address { my($self,$stat) = (shift,shift); my $conf = $stat->{conf}{$CONF_KEY}; my $pub = $conf->{public_address}; # $pub now has the address->key values. my(@to) = $self->_get_to($stat); foreach $_ (@to){ if($pub->{$_}){ return(1); # Yep, it's a 'public' address. One likely to recieve spam. } } return(0); } # 'public' is kind of a bad name for it, because it doesn't exclude non-public. # Also, doesn't (yet) look at a References: header, from usenet. (TODO) sub public_check_references { my($self,$stat,$level) = (shift,shift,shift); $level ||=''; my($store) = $self->{STORE}; if(! $store){ $self->debug("No storage implementation, set public_store_module?"); return(0); } my($ref) = $stat->get("In-Reply-To"); # See if in response to something we sent. $ref =~ s/\s//g; if(! $ref){ $self->debug("Message contains no In-Reply-To: why bother checking?"); return(0); } $self->debug("Checking: [$ref] against [" . ref($self->{STORE}) . "]"); my($found) = 0; if($level ne 'strict'){ $self->debug("Not using strict checks"); $found = $store->exists($self,$stat,$ref); }else{ # we're being really picky, the message ID has to be sent FROM this email # address. my(@to) = $self->_get_to($stat); foreach my $to (@to) { $self->debug("Using strict checks: [$to]"); if($store->exists($self,$stat,$ref,$to)){ $self->debug("exists($self,$stat,$ref,$to) returned T"); $found = 1; last; } } } return($found); } sub _get_to { my($self,$stat) = @_; my($to) = $stat->get("ToCc"); $to =~ s/\(.*?\)//g; my(@to) = ($to =~ m/([\w.=-]+\@\w+(?:[\w.-]+\.)+\w+)/g); return(@to); } 1; __END__ =pod =head1 NAME PublicCheck::To Check the To address against In-Reply-To. Also checks known public addresses, such as those used for mailing lists. =head1 SYNOPSIS loadplugin PublicCheck::To public_store_module StorageImpl /path/to/StorageImpl.pm =head1 TESTS =over 4 =item public_check_to_address Tests to see if the To: or Cc: line contains an address we've publicly used. # Let it know that these addresses are potential spam. public_address info@example.com public_address nospam@example.com head PUBLIC_ADDRESS eval:public_check_to_address() score PUBLIC_ADDRESS 2 describe PUBLIC_ADDRESS It was sent to a public email address. =item public_check_references Test to see if it has an In-Reply-To header matchine a message ID we've used before. You typically want to give this a negative score, as it indicates someone replied. head REFERENCE_MINE eval:public_check_references('[strict]') score REFERENCE_MINE -5 describe REFERENCE_MINE Contains known In-Reply-To field. If 'strict' is given as an argument the underlying storage module will need to verify the message ID was sent from that email address. =back =head1 STORAGE It needs a storage implementation of some sort in order to check references, this is a perl package that subclasses PublicCheck::MidStore package. The critical method is: exists($plugin,$pmstatus,$message_id,[$email]) Where $email is only used when the level is 'strict' The public_check_to_address does NOT need any storage, but you need to set public_address (perhaps multiple times) to addresses you've used in public. =head1 BUILDING DATABASE Good luck! :-) This is a serious downside of this system, in order to be effective, something has to track each generated message ID. One approach is a site-wide SMTP out-bound filter, but that is a local issue and isn't dealt with here. =head1 COPYWRIGHT Copywright (C) 2005 Jamie Hoglund. You're free to use it, same as SpamAssassin. I'm always available for custom programming, see http://www.geniegate.com/ for contact information. =cut