Mail::SpamAssassin::SimpleClient - easy client to SpamAssassin's spamd


Mail-SpamAssassin-SimpleClient documentation Contained in the Mail-SpamAssassin-SimpleClient distribution.

Index


Code Index:

NAME

Top

Mail::SpamAssassin::SimpleClient - easy client to SpamAssassin's spamd

VERSION

Top

version 0.101610

WARNING

Top

Achtung!

This module is still in its infancy. Its interface will probably change somewhat, especially if some changes are made to the spamd protocol to make this module awesomer. Please don't rely on it. Just play with it and try to help figure out how to make it great.

SYNOPSIS

Top

  my $spamc = Mail::SpamAssassin::Simpleclient->new;

  die "It's horrible, horrible spam!" if $spamc->check($message)->is_spam;

DESCRIPTION

Top

Mail::SpamAssassin is a great, free tool for identifying spam messages. It is generally accessed via the spamc or spamassassin programs. Despite the fact that SpamAssassin in implemented in Perl, it is often difficult to check a message against SpamAssassin from within a Perl program.

This module provides a very simple (but also limited) interface to check mail against SpamAssassin.

METHODS

Top

new

  my $client = Mail::SpamAssassin::SimpleClient->new(\%arg);

This method returns a new SimpleClient object.

Valid arguments are:

  host     - the host on which to look for spamd (default: localhost)
  port     - the port on which to look for spamd (default: 783)
  username - username to pass to spamd
  timeout  - how long (in seconds) to allow SpamAssassin to consider the
             message before an exception is raised; default 120;  set to 0 to
             wait forever

check

  my $result = $spamc->check($message);

This method passes a message to SpamAssassin to be spam-checked. It returns a Mail::SpamAssassin::SimpleClient::Result object. If SpamAssassin does not respond within the SimpleClient's timeout period, an exception is raised.

TODO

Top

Support spamd-less operation.

Get the protocol to support "always rewrite" or another way to always get scores.

AUTHOR

Top

  Ricardo Signes <rjbs@cpan.org>

COPYRIGHT AND LICENSE

Top


Mail-SpamAssassin-SimpleClient documentation Contained in the Mail-SpamAssassin-SimpleClient distribution.

use strict;
use warnings;
use 5.006;
package Mail::SpamAssassin::SimpleClient;
BEGIN {
  $Mail::SpamAssassin::SimpleClient::VERSION = '0.101610';
}
# ABSTRACT: easy client to SpamAssassin's spamd

use Carp ();
use Email::MIME;
use Mail::SpamAssassin::Client;
use Mail::SpamAssassin::SimpleClient::Result;


sub new {
  my ($class, $arg) = @_;
  $arg ||= {};

  $arg->{host} = 'localhost' unless defined $arg->{host};
  $arg->{port} = 783 unless defined $arg->{port};
  $arg->{timeout} = 120 unless defined $arg->{timeout};

  bless $arg => $class;
}


sub check {
  my ($self, $message) = @_;

  local $SIG{ALRM} = sub {
    Carp::croak "SpamAssassin failed to respond within $self->{timeout}s";
  };

  alarm $self->{timeout};

  # Maybe we should keep one spamc and ping each check.  Whatever, I'll deal
  # with that when it turns out to matter. -- rjbs, 2007-03-21
  my $spamc = Mail::SpamAssassin::Client->new({
    host => $self->{host},
    port => $self->{port},
    ($self->{username} ? (username => $self->{username}) : ()),
  });

  my $response = $spamc->process($message->as_string);

  alarm 0;
# X-Spam-Flag: YES
# X-Spam-Checker-Version: SpamAssassin 3.1.0 (2005-09-13) on emerald.pobox.com
# X-Spam-Status: Yes, score=1000.8 required=5.0 tests=ENGLISH_UCE_SUBJECT,GTUBE,
#   NO_REAL_NAME,NO_RECEIVED,NO_RELAYS autolearn=no version=3.1.0
# X-Spam-Level: **************************************************
# X-Spam-Report: *  1.4 NO_DNS_FOR_FROM DNS: Envelope sender has no MX or A DNS records *  0.0 MISSING_MID Missing Message-Id: header * -0.0 NO_RELAYS Informational: message was not relayed via SMTP

  # use Data::Dumper;
  # warn Dumper($response);

  my $response_email = Email::MIME->new($response->{message});

  # We can't just look for tests=\S because when the tests wrap and are
  # unfolded, a space is introduced betwen tests.
  my $status = $response_email->header('X-Spam-Status');

  $status =~ s/,\s([A-Z])/,$1/g;
  my ($tests) = $status =~ /tests=(.+?)(?:\s[a-z]|$)/;
  my ($version) = $status =~ /version=(\S+)/;

  my @tests = split /,/, $tests;

  # isspam will be True or False
  # score
  # threshold

  my (%test_score, %test_desc);

  if ($response->{isspam} eq 'True') {
    # prefer the X-Spam-Report header before checking the body
    my $report = $response_email->header('X-Spam-Report');
    if( $report ) {
        foreach my $report_part (split(/\s*\*\s*/, $report)) {
            next unless $report_part;
            my ($score, $name, $desc) = $report_part =~ /^(-?[\d.]+)\s+(\S+)\s+(.*)$/;
            $test_score{ $name } = $score;
            $test_desc{ $name } = $desc;
        }
    } else {
      ($report) = ($response_email->parts)[0];

      my $past_header;
      LINE: for my $line (split /\n/, $report->body) {
        next if not($past_header) and not($line =~ /^\s*---- -/);
        $past_header = 1, next if not $past_header;
  
        my ($score, $name) = $line =~ /\s*(-?[\d.]+)\s+(\S+)/;
        next LINE unless defined $name;
        $test_score{ $name } = $score;
      }
    }
  }

  return Mail::SpamAssassin::SimpleClient::Result->new({
    is_spam    => $response->{score} > $response->{threshold},
    score      => $response->{score},
    threshold  => $response->{threshold},
    version    => $version,
    email      => $response_email,

    tests      => %test_score
                ? \%test_score
                : { map { $_ => undef } @tests },
    test_desc  => %test_desc
                ? \%test_desc
                : { map { $_ => undef } @tests },
  });
}



1;

__END__