d644c24c83
refactored into small subs with unit tests. parse SA header with split instead of regexp (more reliable) store SA results in a 'spamassassin' transaction note add strict and warnings pragma renamed reject_threshold -> reject (backwards compatible) added relayclient skip option and POD. Skips SA processing when relayclient is set added MULTIPLE RECIPIENT BEHAVIOR topic to POD
203 lines
6.9 KiB
Perl
203 lines
6.9 KiB
Perl
#!perl -w
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Mail::Header;
|
|
use Qpsmtpd::Address;
|
|
use Qpsmtpd::Constants;
|
|
|
|
my @sample_headers = (
|
|
'No, score=-5.4 required=4.0 autolearn=ham',
|
|
'No, score=-8.2 required=4.0 autolearn=ham',
|
|
'No, score=-102.3 required=4.0 autolearn=disabled',
|
|
'No, score=-0.1 required=5.0 tests=AWL,BAYES_00,FREEMAIL_FROM,HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RDNS_NONE autolearn=no version=3.3.2',
|
|
'No, score=4.4 required=5.0 autolearn=no',
|
|
'Yes, score=14.3 required=5.0 autolearn=no',
|
|
'Yes, score=18.3 required=5.0 autolearn=spam',
|
|
'Yes, score=26.6 required=4.0 autolearn=unavailable',
|
|
'No, score=-1.7 required=4.0 autolearn=unavailable version=3.3.2',
|
|
'No, hits=-1.0 required=4.0 autolearn=unavailable version=3.3.2',
|
|
);
|
|
|
|
sub register_tests {
|
|
my $self = shift;
|
|
|
|
$self->register_test('test_connect_to_spamd', 4);
|
|
$self->register_test('test_parse_spam_header', 10);
|
|
$self->register_test('test_get_spam_results', 19);
|
|
$self->register_test('test_check_spam_munge_subject', 4);
|
|
$self->register_test('test_check_spam_reject', 2);
|
|
}
|
|
|
|
sub test_connect_to_spamd {
|
|
my $self = shift;
|
|
|
|
my $transaction = $self->qp->transaction;
|
|
$transaction->add_recipient( Qpsmtpd::Address->new( '<user@example.com>' ) );
|
|
my $username = $self->select_spamd_username( $transaction );
|
|
my $message = $self->test_message();
|
|
my $length = length $message;
|
|
|
|
# Try a unix socket
|
|
$self->{_args}{spamd_socket} = '/var/run/spamd/spamd.socket';
|
|
my $SPAMD = $self->connect_to_spamd();
|
|
if ( $SPAMD ) {
|
|
ok( $SPAMD, "connect_to_spamd, socket");
|
|
|
|
$self->print_to_spamd( $SPAMD, $message, $length, $username );
|
|
shutdown($SPAMD, 1); # close our side of the socket (tell spamd we're done)
|
|
my $headers = $self->parse_spamd_response( $SPAMD );
|
|
#warn Data::Dumper::Dumper($headers);
|
|
ok( $headers, "connect_to_spamd, socket response\n");
|
|
}
|
|
else {
|
|
ok( 1 == 1, "connect_to_spamd, socket connect FAILED");
|
|
ok( 1 == 1, "connect_to_spamd, socket response FAILED");
|
|
};
|
|
|
|
# Try a TCP/IP connection
|
|
$self->{_args}{spamd_socket} = '127.0.0.1:783';
|
|
$SPAMD = $self->connect_to_spamd();
|
|
if ( $SPAMD ) {
|
|
ok( $SPAMD, "connect_to_spamd, tcp/ip");
|
|
#warn Data::Dumper::Dumper($SPAMD);
|
|
$self->print_to_spamd( $SPAMD, $message, $length, $username );
|
|
shutdown($SPAMD, 1); # close our side of the socket (tell spamd we're done)
|
|
my $headers = $self->parse_spamd_response( $SPAMD );
|
|
#warn Data::Dumper::Dumper($headers);
|
|
ok( $headers, "connect_to_spamd, tcp/ip response\n");
|
|
}
|
|
else {
|
|
ok( 1 == 1, "connect_to_spamd, tcp/ip connect FAILED");
|
|
ok( 1 == 1, "connect_to_spamd, tcp/ip response FAILED");
|
|
};
|
|
};
|
|
|
|
sub test_check_spam_reject {
|
|
my $self = shift;
|
|
|
|
my $transaction = $self->qp->transaction;
|
|
$self->setup_headers();
|
|
|
|
# message scored a 10, should pass
|
|
$self->{_args}{reject} = 12;
|
|
$transaction->notes('spamassassin', { score => 10 } );
|
|
my $r = $self->check_spam_reject($transaction);
|
|
cmp_ok( DECLINED, '==', $r, "check_spam_reject, $r");
|
|
|
|
# message scored a 15, should fail
|
|
$self->{_args}{reject} = 12;
|
|
$transaction->notes('spamassassin', { score => 15 } );
|
|
($r) = $self->check_spam_reject($transaction);
|
|
cmp_ok( DENY, '==', $r, "check_spam_reject, $r");
|
|
};
|
|
|
|
sub test_check_spam_munge_subject {
|
|
my $self = shift;
|
|
|
|
my $transaction = $self->qp->transaction;
|
|
$self->setup_headers();
|
|
my $subject = 'DSPAM smells better than SpamAssassin';
|
|
|
|
$self->{_args}{munge_subject_threshold} = 5;
|
|
$transaction->notes('spamassassin', { score => 6 } );
|
|
$transaction->header->add('Subject', $subject);
|
|
$self->check_spam_munge_subject($transaction);
|
|
my $r = $transaction->header->get('Subject'); chomp $r;
|
|
cmp_ok($r, 'eq', "*** SPAM *** $subject", "check_spam_munge_subject +");
|
|
|
|
$transaction->header->delete('Subject'); # cleanup
|
|
$self->{_args}{munge_subject_threshold} = 5;
|
|
$transaction->notes('spamassassin', { score => 3 } );
|
|
$transaction->header->add('Subject', $subject);
|
|
$self->check_spam_munge_subject($transaction);
|
|
$r = $transaction->header->get('Subject'); chomp $r;
|
|
cmp_ok($r, 'eq', $subject, "check_spam_munge_subject -");
|
|
|
|
$transaction->header->delete('Subject'); # cleanup
|
|
$transaction->notes('spamassassin', { score => 3, required => 4 } );
|
|
$transaction->header->add('Subject', $subject);
|
|
$self->check_spam_munge_subject($transaction);
|
|
$r = $transaction->header->get('Subject'); chomp $r;
|
|
cmp_ok($r, 'eq', $subject, "check_spam_munge_subject -");
|
|
|
|
$transaction->header->delete('Subject'); # cleanup
|
|
$transaction->notes('spamassassin', { score => 5, required => 4 } );
|
|
$transaction->header->add('Subject', $subject);
|
|
$self->check_spam_munge_subject($transaction);
|
|
$r = $transaction->header->get('Subject'); chomp $r;
|
|
cmp_ok($r, 'eq', "*** SPAM *** $subject", "check_spam_munge_subject +");
|
|
};
|
|
|
|
sub test_get_spam_results {
|
|
my $self = shift;
|
|
|
|
my $transaction = $self->qp->transaction;
|
|
$self->setup_headers();
|
|
|
|
foreach my $h ( @sample_headers ) {
|
|
$transaction->notes('spamassassin', undef); # empty cache
|
|
$transaction->header->delete('X-Spam-Status'); # delete previous header
|
|
$transaction->header->add('X-Spam-Status', $h);
|
|
my $r_ref = $self->get_spam_results($transaction);
|
|
if ( $h =~ /hits=/ ) {
|
|
$r_ref->{hits} = delete $r_ref->{score}; # SA v2 compat
|
|
};
|
|
my $r2 = _reassemble_header($r_ref);
|
|
cmp_ok( $h, 'eq', $r2, "get_spam_results ($h)" );
|
|
|
|
# this time it should be cached
|
|
$r_ref = $self->get_spam_results($transaction);
|
|
next if $h =~ /hits=/; # caching is broken for SA v2 headers
|
|
$r2 = _reassemble_header($r_ref);
|
|
cmp_ok( $h, 'eq', $r2, "get_spam_results ($h)" );
|
|
};
|
|
|
|
};
|
|
|
|
sub test_parse_spam_header {
|
|
my $self = shift;
|
|
|
|
foreach my $h ( @sample_headers ) {
|
|
my $r_ref = $self->parse_spam_header($h);
|
|
if ( $h =~ /hits=/ ) {
|
|
$r_ref->{hits} = delete $r_ref->{score}; # SA v2 compat
|
|
};
|
|
my $r2 = _reassemble_header($r_ref);
|
|
cmp_ok( $h, 'eq', $r2, "parse_spam_header ($h)" );
|
|
};
|
|
};
|
|
|
|
sub setup_headers {
|
|
my $self = shift;
|
|
|
|
my $transaction = $self->qp->transaction;
|
|
my $header = Mail::Header->new(Modify => 0, MailFrom => "COERCE");
|
|
$transaction->header( $header );
|
|
};
|
|
|
|
sub test_message {
|
|
return <<'EO_MESSAGE'
|
|
To: Fictitious User <fict@example.com>
|
|
From: No Such <such@example.com>
|
|
Subject: jose can you see, by the dawns early light?
|
|
|
|
What so proudly we.
|
|
EO_MESSAGE
|
|
|
|
|
|
};
|
|
|
|
sub _reassemble_header {
|
|
my $info_ref = shift;
|
|
my $string = $info_ref->{'is_spam'};
|
|
$string .= ",";
|
|
foreach ( qw/ hits score required tests autolearn version / ) {
|
|
next if ! defined $info_ref->{$_};
|
|
$string .= " $_=$info_ref->{$_}";
|
|
};
|
|
return $string;
|
|
};
|
|
|