Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Dspam #26

Closed
wants to merge 3 commits into from

1 participant

Matt Simerson
Matt Simerson
Owner

dspam: work around DESTROY bug

worked around qpsmtpd bug that DESTROYs the parent when a forked child exits
expanded learning support. Now learns from spamassassin, naughty, and karma (both good and bad).
better logging for 'reject agree'
improved POD
abstracted new subs: log_and_return, attach_headers

msimerson added some commits
Matt Simerson msimerson reject and reject_type handling for plugins cbd1be7
Matt Simerson msimerson dspam: work around DESTROY bug
worked around qpsmtpd bug that DESTROYs the parent when a forked child exits
expanded learning support. Now learns from spamassassin, naughty, and karma (both good and bad).
better logging for 'reject agree'
improved POD
abstracted new subs: log_and_return, attach_headers
4f00da4
Matt Simerson msimerson dspam: replaced header->replace with ->delete & ->add
the replace feature doesn't insert the header in the correct position.

added POD note for dspam maintenance script
d156262
Matt Simerson msimerson closed this
Matt Simerson msimerson deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 4, 2012
  1. Matt Simerson
  2. Matt Simerson

    dspam: work around DESTROY bug

    msimerson authored
    worked around qpsmtpd bug that DESTROYs the parent when a forked child exits
    expanded learning support. Now learns from spamassassin, naughty, and karma (both good and bad).
    better logging for 'reject agree'
    improved POD
    abstracted new subs: log_and_return, attach_headers
Commits on Jun 7, 2012
  1. Matt Simerson

    dspam: replaced header->replace with ->delete & ->add

    msimerson authored
    the replace feature doesn't insert the header in the correct position.
    
    added POD note for dspam maintenance script
This page is out of date. Refresh to see the latest.
45 docs/config.pod
View
@@ -89,7 +89,7 @@ connection before any auth succeeds, defaults to C<0>.
=back
-=head2 Plugin settings
+=head2 Plugin settings files
=over 4
@@ -153,5 +153,48 @@ only currenlty.
=back
+=head2 Plugin settings arguments
+
+These are arguments that can be set on the config/plugins line, after the name
+of the plugin. These config options are available to all plugins.
+
+=over 4
+
+=item loglevel
+
+Adjust the quantity of logging for the plugin. See docs/logging.pod
+
+=item reject
+
+ plugin reject [ 0 | 1 | naughty ]
+
+Should the plugin reject mail?
+
+The special 'naughty' case will mark the connection as a naughty. Most plugins
+skip processing naughty connections. Filtering plugins can learn from them.
+Naughty connections are terminated up by the B<naughty> plugin.
+
+Plugins that use $self->get_reject() or $self->get_reject_type() will
+automatically honor this setting.
+
+=item reject_type
+
+ plugin reject_type [ perm | temp | disconnect | temp_disconnect ]
+
+Default: perm
+
+Values with temp in the name return a 4xx code and the others return a 5xx
+code.
+
+The I<reject_type> argument and the corresponding get_reject_type() method
+provides a standard way for plugins to automatically return the selected
+rejection type, as chosen by the config setting, the plugin author, or the
+get_reject_type() method.
+
+Plugins that are updated to use the $self->get_reject() or
+$self->get_reject_type() methods will automatically honor this setting.
+
+=back
+
=cut
34 lib/Qpsmtpd/Plugin.pm
View
@@ -210,6 +210,40 @@ sub compile {
die "eval $@" if $@;
}
+sub get_reject {
+ my $self = shift;
+ my $message = shift || "why didn't you pass an error message?";
+
+ my $reject = $self->{_args}{reject};
+ if ( defined $reject && ! $reject ) {
+ $self->log(LOGINFO, 'fail, reject disabled');
+ return DECLINED;
+ };
+
+ # the naughty plugin will reject later
+ if ( $reject eq 'naughty' ) {
+ $self->log(LOGINFO, 'fail, NAUGHTY');
+ $self->connection->notes('naughty', $message);
+ return (DECLINED);
+ };
+
+ # they asked for reject, we give them reject
+ $self->log(LOGINFO, 'fail');
+ return ( $self->get_reject_type(), $message);
+};
+
+sub get_reject_type {
+ my $self = shift;
+ my $default = shift || DENY;
+ my $deny = $self->{_args}{reject_type} or return $default;
+
+ return $deny =~ /^(temp|soft)$/i ? DENYSOFT
+ : $deny =~ /^(perm|hard)$/i ? DENY
+ : $deny eq 'disconnect' ? DENY_DISCONNECT
+ : $deny eq 'temp_disconnect' ? DENYSOFT_DISCONNECT
+ : $default;
+};
+
sub _register_standard_hooks {
my ($plugin, $qp) = @_;
335 plugins/dspam
View
@@ -6,15 +6,15 @@ dspam - dspam integration for qpsmtpd
=head1 DESCRIPTION
-qpsmtpd plugin that uses dspam to classify messages. Can use SpamAssassin to
-train dspam.
+Uses dspam to classify messages. Use B<spamassassin>, B<karma>, and B<naughty>
+to train dspam.
Adds the X-DSPAM-Result and X-DSPAM-Signature headers to messages. The latter is essential for
training dspam and the former is useful to MDAs, MUAs, and humans.
-Adds a transaction note to the qpsmtpd transaction. The notes is a hashref
+Adds a transaction note to the qpsmtpd transaction. The note is a hashref
with at least the 'class' field (Spam,Innocent,Whitelisted). It will normally
-contain a probability and confidence ratings as well.
+contain a probability and confidence rating.
=head1 TRAINING DSPAM
@@ -30,7 +30,7 @@ dspam as follows:
=item learn from SpamAssassin
-See the docs on the learn_from_sa feature in the CONFIG section.
+See the SPAMASSASSIN section.
=item periodic training
@@ -54,41 +54,58 @@ messages are moved to/from the Spam folder.
=head2 dspam_bin
The path to the dspam binary. If yours is installed somewhere other
-than /usr/local/bin/dspam, you'll need to set this.
+than /usr/local/bin/dspam, set this.
-=head2 learn_from_sa
-
-Dspam can be trained by SpamAssassin. This relationship between them requires
-attention to several important details:
+=head2 autolearn [ naughty | karma | spamassassin | any ]
=over 4
-=item 1
+=item naughty
-dspam must be listed B<after> spamassassin in the config/plugins file.
-Because SA runs first, I crank the SA reject_threshold up above 100 so that
-all spam messages will be used to train dspam.
+learn naughty messages as spam (see plugins/naughty)
-Once dspam is trained and errors are rare, I plan to run dspam first and
-reduce the SA load.
+=item karma
-=item 2
+learn messages with negative karma as spam (see plugins/karma)
+
+=item spamassassin
+
+learn from spamassassins messages with autolearn=(ham|spam)
+
+=item any
+
+all of the above, and any future tests too!
+
+=back
+
+=head2 reject
+
+Set to a floating point value between 0 and 1.00 where 0 is no confidence
+and 1.0 is 100% confidence.
+
+If dspam's confidence is greater than or equal to this threshold, the
+message will be rejected. The default is 1.00.
+
+ dspam reject .95
+
+To only reject mail if dspam and spamassassin both think the message is spam,
+set I<reject agree>.
+
+=head2 reject_type
+
+ reject_type [ perm | temp | disconnect ]
-Autolearn must be enabled and configured in SpamAssassin. SA autolearn
-preferences will determine whether a message is learned as spam or innocent
-by dspam. The settings to pay careful attention to in your SA local.cf file
-are bayes_auto_learn_threshold_spam and bayes_auto_learn_threshold_nonspam.
-Make sure they are both set to conservative values that are certain to
-yield no false positives.
+By default, rejects are permanent (5xx). Set I<reject_type temp> to
+defer mail instead of rejecting it.
-If you are using learn_from_sa and reject, then messages that exceed the SA
-threshholds will cause dspam to reject them. Again I say, make sure them SA
-autolearn threshholds are set high enough to avoid false positives.
+Set I<reject_type disconnect> if you'd prefer to immediately disconnect
+the connection when a spam is encountered. This prevents the remote server
+from issuing a reset and attempting numerous times in a single connection.
-=item 3
+=head1 dspam.conf
-dspam must be configured and working properly. I have modified the following
-dspam values on my system:
+dspam must be configured and working properly. I had to modify the following
+settings on my system:
=over 4
@@ -117,26 +134,47 @@ only supports storing the signature in the headers. If you want to train dspam
after delivery (ie, users moving messages to/from spam folders), then the
dspam signature must be in the headers.
-When using the dspam MySQL backend, use InnoDB tables. Dspam training
+When using the dspam MySQL backend, use InnoDB tables. DSPAM training
is dramatically slowed by MyISAM table locks and dspam requires lots
of training. InnoDB has row level locking and updates are much faster.
-=back
+=head1 DSPAM periodic maintenance
-=head2 reject
+Install this cron job to clean up your DSPAM database.
-Set to a floating point value between 0 and 1.00 where 0 is no confidence
-and 1.0 is 100% confidence.
+http://dspam.git.sourceforge.net/git/gitweb.cgi?p=dspam/dspam;a=tree;f=contrib/dspam_maintenance;hb=HEAD
-If dspam's confidence is greater than or equal to this threshold, the
-message will be rejected. The default is 1.00.
-=head2 reject_type
- reject_type [ temp | perm ]
+=head1 SPAMASSASSIN
-By default, rejects are permanent (5xx). Set this to temp if you want to
-defer mail instead of rejecting it with dspam.
+DSPAM can be trained by SpamAssassin. This relationship between them requires
+attention to several important details:
+
+=over 4
+
+=item 1
+
+dspam must be listed B<after> spamassassin in the config/plugins file.
+Because SA runs first, I set the SA reject_threshold up above 100 so that
+all spam messages will be used to train dspam.
+
+Once dspam is trained and errors are rare, I plan to run dspam first and
+reduce the SA load.
+
+=item 2
+
+Autolearn must be enabled and configured in SpamAssassin. SA autolearn will
+determine if a message is learned by dspam. The settings to pay careful
+attention to in your SA local.cf file are I<bayes_auto_learn_threshold_spam>
+and I<bayes_auto_learn_threshold_nonspam>. Make sure they are set to
+conservative values that will yield no false positives.
+
+If you are using I<autolearn spamassassin> and reject, messages that exceed
+the SA threshholds will cause dspam to reject them. Again I say, make sure
+the SA autolearn threshholds are set high enough to avoid false positives.
+
+=back
=head1 MULTIPLE RECIPIENT BEHAVIOR
@@ -151,9 +189,12 @@ ie, (Trust smtpd).
=head1 CHANGES
+2012-06 - Matt Simerson - added karma & naughty learning support
+ - worked around the DESTROY bug in dspam_process
+
=head1 AUTHOR
- Matt Simerson - 2012
+2012 - Matt Simerson
=cut
@@ -166,49 +207,42 @@ use IO::Handle;
use Socket qw(:DEFAULT :crlf);
sub register {
- my ($self, $qp, %args) = @_;
+ my ($self, $qp) = shift, shift;
$self->log(LOGERROR, "Bad parameters for the dspam plugin") if @_ % 2;
- $self->{_args} = { %args };
- $self->{_args}{reject} = defined $args{reject} ? $args{reject} : 1;
- $self->{_args}{reject_type} = $args{reject_type} || 'perm';
+ $self->{_args} = { @_ };
+ $self->{_args}{reject} = 1 if ! defined $self->{_args}{reject};
+ $self->{_args}{reject_type} ||= 'perm';
- $self->register_hook('data_post', 'dspam_reject');
+ $self->register_hook('data_post', 'data_post_handler');
}
-sub hook_data_post {
- my ($self, $transaction) = @_;
+sub data_post_handler {
+ my $self = shift;
+ my $transaction = shift || $self->qp->transaction;
+
+ $self->autolearn( $transaction );
+ return (DECLINED) if $self->is_immune();
- $self->log(LOGDEBUG, "check_dspam");
if ( $transaction->data_size > 500_000 ) {
- $self->log(LOGINFO, "skip: message too large (" . $transaction->data_size . ")" );
+ $self->log(LOGINFO, "skip, too big (" . $transaction->data_size . ")" );
return (DECLINED);
};
my $username = $self->select_username( $transaction );
- my $message = $self->assemble_message($transaction);
my $filtercmd = $self->get_filter_cmd( $transaction, $username );
$self->log(LOGDEBUG, $filtercmd);
- my $response = $self->dspam_process( $filtercmd, $message );
+ my $response = $self->dspam_process( $filtercmd, $transaction );
if ( ! $response ) {
- $self->log(LOGWARN, "skip: no response from dspam. Check logs for errors.");
+ $self->log(LOGWARN, "skip, no dspam response. Check logs for errors.");
return (DECLINED);
};
- # X-DSPAM-Result: user@example.com; result="Spam"; class="Spam"; probability=1.0000; confidence=1.00; signature=N/A
- # X-DSPAM-Result: smtpd; result="Innocent"; class="Innocent"; probability=0.0023; confidence=1.00; signature=4f8dae6a446008399211546
- my ($result,$prob,$conf,$sig) = $response =~ /result=\"(Spam|Innocent)\";.*?probability=([\d\.]+); confidence=([\d\.]+); signature=(.*)/;
- my $header_str = "$result, probability=$prob, confidence=$conf";
- $self->log(LOGDEBUG, $header_str);
- $transaction->header->replace('X-DSPAM-Result', $header_str, 0);
-
- # the signature header is required if you intend to train dspam later.
- # In dspam.conf, set: Preference "signatureLocation=headers"
- $transaction->header->add('X-DSPAM-Signature', $sig, 0);
+ $self->attach_headers( $response, $transaction );
- return (DECLINED);
+ return $self->log_and_return( $transaction );
};
sub select_username {
@@ -243,18 +277,23 @@ sub assemble_message {
};
sub dspam_process {
- my ( $self, $filtercmd, $message ) = @_;
-
- #return $self->dspam_process_open2( $filtercmd, $message );
-
- my ($in_fh, $out_fh);
- if (! open($in_fh, '-|')) {
- open($out_fh, "|$filtercmd") or die "Can't run $filtercmd: $!\n";
+ my ( $self, $filtercmd, $transaction ) = @_;
+
+ return $self->dspam_process_backticks( $filtercmd );
+ #return $self->dspam_process_open2( $filtercmd, $transaction );
+
+ # yucky. This method (which forks) exercises a bug in qpsmtpd. When the
+ # child exits, the Transaction::DESTROY method is called, which deletes
+ # the spooled file from disk. The contents of $self->qp->transaction
+ # needed to spool it again are also destroyed. Don't use this.
+ my $message = $self->assemble_message( $transaction );
+ my $in_fh;
+ if (! open($in_fh, '-|')) { # forks child for writing
+ open(my $out_fh, "|$filtercmd") or die "Can't run $filtercmd: $!\n";
print $out_fh $message;
close $out_fh;
exit(0);
};
- #my $response = join('', <$in_fh>);
my $response = <$in_fh>;
close $in_fh;
chomp $response;
@@ -262,8 +301,20 @@ sub dspam_process {
return $response;
};
+sub dspam_process_backticks {
+ my ( $self, $filtercmd ) = @_;
+
+ my $filename = $self->qp->transaction->body_filename;
+ #my $response = `cat $filename | $filtercmd`; chomp $response;
+ my $response = `$filtercmd < $filename`; chomp $response;
+ $self->log(LOGDEBUG, $response);
+ return $response;
+};
+
sub dspam_process_open2 {
- my ( $self, $filtercmd, $message ) = @_;
+ my ( $self, $filtercmd, $transaction ) = @_;
+
+ my $message = $self->assemble_message( $transaction );
# not sure why, but this is not as reliable as I'd like. What's a dspam
# error -5 mean anyway?
@@ -281,31 +332,33 @@ sub dspam_process_open2 {
return $response;
};
-sub dspam_reject {
- my ($self, $transaction) = @_;
+sub log_and_return {
+ my $self = shift;
+ my $transaction = shift || $self->qp->transaction;
my $d = $self->get_dspam_results( $transaction ) or return DECLINED;
if ( ! $d->{class} ) {
- $self->log(LOGWARN, "skip: no dspam class detected");
+ $self->log(LOGWARN, "skip, no dspam class detected");
return DECLINED;
};
my $status = "$d->{class}, $d->{confidence} c.";
my $reject = $self->{_args}{reject} or do {
- $self->log(LOGINFO, "skip: reject disabled ($status)");
+ $self->log(LOGINFO, "skip, reject disabled ($status)");
return DECLINED;
};
if ( $reject eq 'agree' ) {
- return $self->dspam_reject_agree( $transaction, $d );
+ return $self->reject_agree( $transaction, $d );
};
+
if ( $d->{class} eq 'Innocent' ) {
- $self->log(LOGINFO, "pass: $status");
+ $self->log(LOGINFO, "pass, $status");
return DECLINED;
};
if ( $self->qp->connection->relay_client ) {
- $self->log(LOGINFO, "skip: allowing spam, user authenticated ($status)");
+ $self->log(LOGINFO, "skip, allowing spam, user authenticated ($status)");
return DECLINED;
};
if ( $d->{probability} <= $reject ) {
@@ -313,17 +366,17 @@ sub dspam_reject {
return DECLINED;
};
if ( $d->{confidence} != 1 ) {
- $self->log(LOGINFO, "pass: $d->{class} confidence is too low ($d->{confidence})");
+ $self->log(LOGINFO, "pass, $d->{class} confidence is too low ($d->{confidence})");
return DECLINED;
};
# dspam is more than $reject percent sure this message is spam
- $self->log(LOGINFO, "fail: $d->{class}, ($d->{confidence} confident)");
- my $deny = $self->{_args}{reject_type} eq 'temp' ? DENYSOFT : DENY;
- return Qpsmtpd::DSN->media_unsupported($deny,'dspam says, no spam please');
+ $self->log(LOGINFO, "fail, $d->{class}, ($d->{confidence} confident)");
+ my $deny = $self->get_reject_type();
+ return Qpsmtpd::DSN->media_unsupported($deny, 'dspam says, no spam please');
}
-sub dspam_reject_agree {
+sub reject_agree {
my ($self, $transaction, $d ) = @_;
my $sa = $transaction->notes('spamassassin' );
@@ -331,21 +384,44 @@ sub dspam_reject_agree {
my $status = "$d->{class}, $d->{confidence} c";
if ( ! $sa->{is_spam} ) {
- $self->log(LOGINFO, "pass: cannot agree, SA results missing ($status)");
+ $self->log(LOGINFO, "pass, cannot agree, SA results missing ($status)");
return DECLINED;
};
- if ( $d->{class} eq 'Spam' && $sa->{is_spam} eq 'Yes' ) {
- $self->log(LOGINFO, "fail: agree, $status");
- return Qpsmtpd::DSN->media_unsupported(DENY,'we agree, no spam please');
+ if ( $d->{class} eq 'Spam' ) {
+ if ( $sa->{is_spam} eq 'Yes' ) {
+ if ( defined $self->connection->notes('karma') ) {
+ $self->connection->notes('karma', $self->connection->notes('karma') - 2);
+ };
+ $self->log(LOGINFO, "fail, agree, $status");
+ my $reject = $self->get_reject_type();
+ return ($reject, 'we agree, no spam please');
+ };
+
+ $self->log(LOGINFO, "fail, disagree, $status");
+ return DECLINED;
};
- $self->log(LOGINFO, "pass: agree, $status");
+ if ( $d->{class} eq 'Innocent' ) {
+ if ( $sa->{is_spam} eq 'No' ) {
+ if ( $d->{confidence} > .9 ) {
+ if ( defined $self->connection->notes('karma') ) {
+ $self->connection->notes('karma', $self->connection->notes('karma') + 2);
+ };
+ };
+ $self->log(LOGINFO, "pass, agree, $status");
+ return DECLINED;
+ };
+ $self->log(LOGINFO, "pass, disagree, $status");
+ };
+
+ $self->log(LOGINFO, "pass, other $status");
return DECLINED;
};
sub get_dspam_results {
- my ( $self, $transaction ) = @_;
+ my $self = shift;
+ my $transaction = shift || $self->qp->transaction;
if ( $transaction->notes('dspam') ) {
return $transaction->notes('dspam');
@@ -379,19 +455,22 @@ sub get_filter_cmd {
my $dspam_bin = $self->{_args}{dspam_bin} || '/usr/local/bin/dspam';
my $default = "$dspam_bin --user $user --mode=tum --process --deliver=summary --stdout";
- my $min_score = $self->{_args}{learn_from_sa} or return $default;
- #$self->log(LOGDEBUG, "attempting to learn from SA");
+ my $learn = $self->{_args}{autolearn} or return $default;
+ return $default if ( $learn ne 'spamassassin' && $learn ne 'any' );
- my $sa = $transaction->notes('spamassassin' );
- return $default if ! $sa || ! $sa->{is_spam};
+ $self->log(LOGDEBUG, "attempting to learn from SA");
- if ( $sa->{is_spam} eq 'Yes' && $sa->{score} < $min_score ) {
- $self->log(LOGNOTICE, "SA score $sa->{score} < $min_score, skip autolearn");
+ my $sa = $transaction->notes('spamassassin' );
+ if ( ! $sa || ! $sa->{is_spam} ) {
+ $self->log(LOGERROR, "SA results missing");
return $default;
};
- return $default if ! $sa->{autolearn};
+ if ( ! $sa->{autolearn} ) {
+ $self->log(LOGERROR, "SA autolearn unset");
+ return $default;
+ };
if ( $sa->{is_spam} eq 'Yes' && $sa->{autolearn} eq 'spam' ) {
return "$dspam_bin --user $user --mode=tum --source=corpus --class=spam --deliver=summary --stdout";
@@ -403,4 +482,64 @@ sub get_filter_cmd {
return $default;
};
+sub attach_headers {
+ my ($self, $response, $transaction) = @_;
+ $transaction ||= $self->qp->transaction;
+
+ # X-DSPAM-Result: user@example.com; result="Spam"; class="Spam"; probability=1.0000; confidence=1.00; signature=N/A
+ # X-DSPAM-Result: smtpd; result="Innocent"; class="Innocent"; probability=0.0023; confidence=1.00; signature=4f8dae6a446008399211546
+ my ($result,$prob,$conf,$sig) = $response =~ /result=\"(Spam|Innocent)\";.*?probability=([\d\.]+); confidence=([\d\.]+); signature=(.*)/;
+ my $header_str = "$result, probability=$prob, confidence=$conf";
+ $self->log(LOGDEBUG, $header_str);
+ my $name = 'X-DSPAM-Result';
+ $transaction->header->delete($name) if $transaction->header->get($name);
+ $transaction->header->add($name, $header_str, 0);
+
+ # the signature header is required if you intend to train dspam later.
+ # In dspam.conf, set: Preference "signatureLocation=headers"
+ $transaction->header->add('X-DSPAM-Signature', $sig, 0);
+};
+
+sub learn_as_ham {
+ my $self = shift;
+ my $transaction = shift;
+ my $user = $self->select_username( $transaction );
+ my $dspam_bin = $self->{_args}{dspam_bin} || '/usr/local/bin/dspam';
+ my $cmd = "$dspam_bin --user $user --mode=tum --source=corpus --class=innocent --deliver=summary --stdout";
+ $self->dspam_process( $cmd, $transaction );
+};
+
+sub learn_as_spam {
+ my $self = shift;
+ my $transaction = shift;
+
+ my $user = $self->select_username( $transaction );
+ my $dspam_bin = $self->{_args}{dspam_bin} || '/usr/local/bin/dspam';
+ my $cmd = "$dspam_bin --user $user --mode=tum --source=corpus --class=spam --deliver=summary --stdout";
+ $self->dspam_process( $cmd, $transaction );
+};
+
+sub autolearn {
+ my ( $self, $transaction ) = @_;
+
+ my $learn = $self->{_args}{autolearn} or return;
+
+ if ( $learn eq 'naughty' || $learn eq 'any' ) {
+ if ( $self->connection->notes('naughty') ) {
+ $self->log(LOGINFO, "training naughty as spam");
+ $self->learn_as_spam( $transaction );
+ };
+ };
+ if ( $learn eq 'karma' || $learn eq 'any' ) {
+ my $karma = $self->connection->notes('karma');
+ if ( defined $karma && $karma <= -1 ) {
+ $self->log(LOGINFO, "training poor karma as spam");
+ $self->learn_as_spam( $transaction );
+ };
+ if ( defined $karma && $karma >= 1 ) {
+ $self->log(LOGINFO, "training good karma as ham");
+ $self->learn_as_ham( $transaction );
+ };
+ };
+};
56 t/plugin_tests/dspam
View
@@ -13,48 +13,49 @@ sub register_tests {
$self->register_test('test_get_filter_cmd', 5);
$self->register_test('test_get_dspam_results', 6);
- $self->register_test('test_dspam_reject', 6);
+ $self->register_test('test_log_and_return', 6);
+ $self->register_test('test_reject_type', 3);
}
-sub test_dspam_reject {
+sub test_log_and_return {
my $self = shift;
my $transaction = $self->qp->transaction;
# reject not set
$transaction->notes('dspam', { class=> 'Spam', probability => .99, confidence=>1 } );
- ($r) = $self->dspam_reject( $transaction );
- cmp_ok( $r, '==', DECLINED, "dspam_reject ($r)");
+ ($r) = $self->log_and_return( $transaction );
+ cmp_ok( $r, '==', DECLINED, "($r)");
# reject exceeded
- $self->{_args}->{reject} = .95;
+ $self->{_args}{reject} = .95;
$transaction->notes('dspam', { class=> 'Spam', probability => .99, confidence=>1 } );
- ($r) = $self->dspam_reject( $transaction );
- cmp_ok( $r, '==', DENY, "dspam_reject ($r)");
+ ($r) = $self->log_and_return( $transaction );
+ cmp_ok( $r, '==', DENY, "($r)");
# below reject threshold
$transaction->notes('dspam', { class=> 'Spam', probability => .94, confidence=>1 } );
- ($r) = $self->dspam_reject( $transaction );
- cmp_ok( $r, '==', DECLINED, "dspam_reject ($r)");
+ ($r) = $self->log_and_return( $transaction );
+ cmp_ok( $r, '==', DECLINED, "($r)");
# requires agreement
- $self->{_args}->{reject} = 'agree';
+ $self->{_args}{reject} = 'agree';
$transaction->notes('spamassassin', { is_spam => 'Yes', score => 25 } );
$transaction->notes('dspam', { class=> 'Spam', probability => .90, confidence=>1 } );
- ($r) = $self->dspam_reject( $transaction );
- cmp_ok( $r, '==', DENY, "dspam_reject ($r)");
+ ($r) = $self->log_and_return( $transaction );
+ cmp_ok( $r, '==', DENY, "($r)");
# requires agreement
$transaction->notes('spamassassin', { is_spam => 'No', score => 15 } );
$transaction->notes('dspam', { class=> 'Spam', probability => .96, confidence=>1 } );
- ($r) = $self->dspam_reject( $transaction );
- cmp_ok( $r, '==', DECLINED, "dspam_reject ($r)");
+ ($r) = $self->log_and_return( $transaction );
+ cmp_ok( $r, '==', DECLINED, "($r)");
# requires agreement
$transaction->notes('spamassassin', { is_spam => 'Yes', score => 10 } );
$transaction->notes('dspam', { class=> 'Innocent', probability => .96, confidence=>1 } );
- ($r) = $self->dspam_reject( $transaction );
- cmp_ok( $r, '==', DECLINED, "dspam_reject ($r)");
+ ($r) = $self->log_and_return( $transaction );
+ cmp_ok( $r, '==', DECLINED, "($r)");
};
sub test_get_dspam_results {
@@ -77,7 +78,7 @@ sub test_get_dspam_results {
$transaction->header->delete('X-DSPAM-Result');
$transaction->header->add('X-DSPAM-Result', $header);
my $r = $self->get_dspam_results($transaction);
- ok( ref $r, "get_dspam_results ($header)" );
+ ok( ref $r, "r: ($header)" );
#warn Data::Dumper::Dumper($r);
};
};
@@ -88,26 +89,39 @@ sub test_get_filter_cmd {
my $transaction = $self->qp->transaction;
my $dspam = "/usr/local/bin/dspam";
$self->{_args}{dspam_bin} = $dspam;
+ $self->{_args}{autolearn} = 'spamassassin';
foreach my $user ( qw/ smtpd matt@example.com / ) {
my $answer = "$dspam --user smtpd --mode=tum --process --deliver=summary --stdout";
my $r = $self->get_filter_cmd($transaction, 'smtpd');
- cmp_ok( $r, 'eq', $answer, "get_filter_cmd $user" );
+ cmp_ok( $r, 'eq', $answer, "$user" );
};
$transaction->notes('spamassassin', { is_spam => 'No', autolearn => 'ham' } );
my $r = $self->get_filter_cmd($transaction, 'smtpd');
cmp_ok( $r, 'eq', "$dspam --user smtpd --mode=tum --source=corpus --class=innocent --deliver=summary --stdout",
- "get_filter_cmd smtpd, ham" );
+ "smtpd, ham" );
$transaction->notes('spamassassin', { is_spam => 'Yes', autolearn => 'spam', score => 110 } );
$r = $self->get_filter_cmd($transaction, 'smtpd');
cmp_ok( $r, 'eq', "$dspam --user smtpd --mode=tum --source=corpus --class=spam --deliver=summary --stdout",
- "get_filter_cmd smtpd, spam" );
+ "smtpd, spam" );
$transaction->notes('spamassassin', { is_spam => 'No', autolearn => 'spam' } );
$r = $self->get_filter_cmd($transaction, 'smtpd');
cmp_ok( $r, 'eq', "$dspam --user smtpd --mode=tum --process --deliver=summary --stdout",
- "get_filter_cmd smtpd, spam" );
+ "smtpd, spam" );
};
+sub test_reject_type {
+ my $self = shift;
+
+ $self->{_args}{reject_type} = undef;
+ cmp_ok( $self->get_reject_type(), '==', DENY, "default");
+
+ $self->{_args}{reject_type} = 'temp';
+ cmp_ok( $self->get_reject_type(), '==', DENYSOFT, "defer");
+
+ $self->{_args}{reject_type} = 'disconnect';
+ cmp_ok( $self->get_reject_type(), '==', DENY_DISCONNECT, "disconnect");
+};
Something went wrong with that request. Please try again.