File Coverage

blib/lib/Mail/SpamAssassin/Plugin/Shortcircuit.pm
Criterion Covered Total %
statement 61 91 67.0
branch 10 32 31.2
condition 2 10 20.0
subroutine 14 18 77.7
pod 6 8 75.0
total 93 159 58.4


line stmt bran cond sub pod time code
1             # <@LICENSE>
2             # Licensed to the Apache Software Foundation (ASF) under one or more
3             # contributor license agreements. See the NOTICE file distributed with
4             # this work for additional information regarding copyright ownership.
5             # The ASF licenses this file to you under the Apache License, Version 2.0
6             # (the "License"); you may not use this file except in compliance with
7             # the License. You may obtain a copy of the License at:
8             #
9             # http://www.apache.org/licenses/LICENSE-2.0
10             #
11             # Unless required by applicable law or agreed to in writing, software
12             # distributed under the License is distributed on an "AS IS" BASIS,
13             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14             # See the License for the specific language governing permissions and
15             # limitations under the License.
16             # </@LICENSE>
17              
18             =head1 NAME
19              
20             Mail::SpamAssassin::Plugin::Shortcircuit - short-circuit evaluation for certain rules
21              
22             =head1 SYNOPSIS
23              
24             loadplugin Mail::SpamAssassin::Plugin::Shortcircuit
25              
26             report Content analysis details: (_SCORE_ points, _REQD_ required, s/c _SCTYPE_)
27              
28             add_header all Status "_YESNO_, score=_SCORE_ required=_REQD_ tests=_TESTS_ shortcircuit=_SCTYPE_ autolearn=_AUTOLEARN_ version=_VERSION_"
29              
30             =head1 DESCRIPTION
31              
32             This plugin implements simple, test-based shortcircuiting. Shortcircuiting a
33             test will force all other pending rules to be skipped, if that test is hit.
34             In addition, a symbolic rule, C<SHORTCIRCUIT>, will fire.
35              
36             Recommended usage is to use C<priority> to set rules with strong S/O values (ie.
37             1.0) to be run first, and make instant spam or ham classification based on
38             that.
39              
40             =cut
41              
42             package Mail::SpamAssassin::Plugin::Shortcircuit;
43              
44 19     19   154 use Mail::SpamAssassin::Plugin;
  19         42  
  19         693  
45 19     19   111 use Mail::SpamAssassin::Logger;
  19         41  
  19         1138  
46 19     19   133 use strict;
  19         62  
  19         439  
47 19     19   110 use warnings;
  19         37  
  19         749  
48             # use bytes;
49 19     19   138 use re 'taint';
  19         51  
  19         20173  
50              
51             our @ISA = qw(Mail::SpamAssassin::Plugin);
52              
53             sub new {
54 60     60 1 259 my $class = shift;
55 60         156 my $mailsaobject = shift;
56              
57 60   33     440 $class = ref($class) || $class;
58 60         360 my $self = $class->SUPER::new($mailsaobject);
59 60         182 bless ($self, $class);
60              
61 60         319 $self->register_eval_rule("check_shortcircuit");
62 60         353 $self->set_config($mailsaobject->{conf});
63              
64 60         617 return $self;
65             }
66              
67 81     81 0 1269 sub check_shortcircuit { return 0; } # never used
68              
69             sub set_config {
70 60     60 0 172 my($self, $conf) = @_;
71 60         6590 my @cmds;
72              
73             =head1 CONFIGURATION SETTINGS
74              
75             The following configuration settings are used to control shortcircuiting:
76              
77             =over 4
78              
79             =item shortcircuit SYMBOLIC_TEST_NAME {ham|spam|on|off}
80              
81             Shortcircuiting a test will force all other pending rules to be skipped, if
82             that test is hit.
83              
84             Recommended usage is to use C<priority> to set rules with strong S/O values (ie.
85             1.0) to be run first, and make instant spam or ham classification based on
86             that.
87              
88             To override a test that uses shortcircuiting, you can set the classification
89             type to C<off>.
90              
91             =over 4
92              
93             =item on
94              
95             Shortcircuits the rest of the tests, but does not make a strict classification
96             of spam or ham. Rather, it uses the default score for the rule being
97             shortcircuited. This would allow you, for example, to define a rule such as
98              
99             body TEST /test/
100             describe TEST test rule that scores barely over spam threshold
101             score TEST 5.5
102             priority TEST -100
103             shortcircuit TEST on
104              
105             The result of a message hitting the above rule would be a final score of 5.5,
106             as opposed to 100 (default) if it were classified as spam.
107              
108             =item off
109              
110             Disables shortcircuiting on said rule.
111              
112             =item spam
113              
114             Shortcircuit the rule using a set of defaults; override the default score of
115             this rule with the score from C<shortcircuit_spam_score>, set the
116             C<noautolearn> tflag, and set priority to C<-100>. In other words,
117             equivalent to:
118              
119             shortcircuit TEST on
120             priority TEST -100
121             score TEST 100
122             tflags TEST noautolearn
123              
124             =item ham
125              
126             Shortcircuit the rule using a set of defaults; override the default score of
127             this rule with the score from C<shortcircuit_ham_score>, set the C<noautolearn>
128             and C<nice> tflags, and set priority to C<-100>. In other words, equivalent
129             to:
130              
131             shortcircuit TEST on
132             priority TEST -100
133             score TEST -100
134             tflags TEST noautolearn nice
135              
136             =back
137              
138             =cut
139              
140             push (@cmds, {
141             setting => 'shortcircuit',
142             code => sub {
143 5     5   17 my ($self, $key, $value, $line) = @_;
144 5         8 my ($rule,$type);
145 5 50 33     33 unless (defined $value && $value !~ /^$/) {
146 0         0 return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
147             }
148 5 50       22 if ($value =~ /^(\S+)\s+(\S+)$/) {
149 5         16 $rule=$1;
150 5         13 $type=$2;
151             } else {
152 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
153             }
154              
155 5 50       19 if ($type =~ m/^(?:spam|ham)$/) {
    0          
    0          
156 5         29 dbg("shortcircuit: adding $rule using abbreviation $type");
157              
158             # set the defaults:
159 5         16 $self->{shortcircuit}->{$rule} = $type;
160 5         13 $self->{priority}->{$rule} = -100;
161              
162 5         10 my $tf = $self->{tflags}->{$rule};
163 5 50       41 $self->{tflags}->{$rule} = ($tf ? $tf." " : "") .
    50          
164             ($type eq 'ham' ? "nice " : "") .
165             "noautolearn";
166             }
167             elsif ($type eq "on") {
168 0         0 $self->{shortcircuit}->{$rule} = "on";
169             }
170             elsif ($type eq "off") {
171 0         0 delete $self->{shortcircuit}->{$rule};
172             }
173             else {
174 0         0 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
175             }
176             }
177 60         735 });
178              
179             =item shortcircuit_spam_score n.nn (default: 100)
180              
181             When shortcircuit is used on a rule, and the shortcircuit classification type
182             is set to C<spam>, this value should be applied in place of the default score
183             for that rule.
184              
185             =cut
186              
187 60         346 push (@cmds, {
188             setting => 'shortcircuit_spam_score',
189             default => 100,
190             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
191             });
192              
193             =item shortcircuit_ham_score n.nn (default: -100)
194              
195             When shortcircuit is used on a rule, and the shortcircuit classification type
196             is set to C<ham>, this value should be applied in place of the default score
197             for that rule.
198              
199             =cut
200              
201 60         322 push (@cmds, {
202             setting => 'shortcircuit_ham_score',
203             default => -100,
204             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
205             });
206              
207 60         307 $conf->{parser}->register_commands(\@cmds);
208             }
209              
210             =back
211              
212             =head1 TAGS
213              
214             The following tags are added to the set available for use in reports, headers
215             etc.:
216              
217             _SC_ shortcircuit status (classification and rule name)
218             _SCRULE_ rulename that caused the shortcircuit
219             _SCTYPE_ shortcircuit classification ("spam", "ham", "default", "none")
220              
221             =cut
222              
223             sub hit_rule {
224 178     178 1 479 my ($self, $params) = @_;
225              
226 178         339 my $scan = $params->{permsgstatus};
227 178         327 my $rule = $params->{rulename};
228              
229             # don't s/c if we're linting
230 178 50       472 return if ($scan->{lint_rules});
231              
232             # don't s/c if we're in compile_now()
233 178 100       464 return if ($self->{am_compiling});
234              
235 171         378 my $sctype = $scan->{conf}->{shortcircuit}->{$rule};
236 171 50       564 return unless $sctype;
237              
238 0         0 my $conf = $scan->{conf};
239 0         0 my $score = $params->{score};
240              
241 0         0 $scan->{shortcircuit_rule} = $rule;
242 0         0 my $scscore;
243 0 0       0 if ($sctype eq 'on') { # guess by rule score
244 0         0 dbg("shortcircuit: s/c due to $rule, using score of $score");
245 0 0       0 $scan->{shortcircuit_type} = ($score < 0 ? 'ham' : 'spam');
246 0 0       0 $scscore = ($score < 0) ? -0.0001 : 0.0001;
247             }
248             else {
249 0         0 $scan->{shortcircuit_type} = $sctype;
250 0 0       0 if ($sctype eq 'ham') {
251 0         0 $score = $conf->{shortcircuit_ham_score};
252             } else {
253 0         0 $score = $conf->{shortcircuit_spam_score};
254             }
255 0         0 dbg("shortcircuit: s/c $sctype due to $rule, using score of $score");
256 0         0 $scscore = $score;
257             }
258              
259             # bug 5256: if we short-circuit, don't do auto-learning
260 0         0 $scan->{disable_auto_learning} = 1;
261 0         0 $scan->got_hit('SHORTCIRCUIT', '', score => $scscore);
262             }
263              
264             sub parsed_metadata {
265 81     81 1 297 my ($self, $params) = @_;
266 81         215 my $scan = $params->{permsgstatus};
267              
268             $scan->set_tag ('SC', sub {
269 0     0   0 my $rule = $scan->{shortcircuit_rule};
270 0         0 my $type = $scan->{shortcircuit_type};
271 0 0       0 return "$rule ($type)" if ($rule);
272 0         0 return "no";
273 81         728 });
274              
275             $scan->set_tag ('SCRULE', sub {
276 0     0   0 my $rule = $scan->{shortcircuit_rule};
277 0   0     0 return ($rule || "none");
278 81         535 });
279              
280             $scan->set_tag ('SCTYPE', sub {
281 0     0   0 my $type = $scan->{shortcircuit_type};
282 0   0     0 return ($type || "no");
283 81         578 });
284              
285             $scan->set_spamd_result_item (sub {
286 0     0   0 "shortcircuit=".$scan->get_tag("SCTYPE");
287 81         692 });
288             }
289              
290             sub have_shortcircuited {
291 2106     2106 1 4228 my ($self, $params) = @_;
292 2106 50       6243 return (exists $params->{permsgstatus}->{shortcircuit_type}) ? 1 : 0;
293             }
294              
295             sub compile_now_start {
296 2     2 1 8 my ($self, $params) = @_;
297 2         7 $self->{am_compiling} = 1;
298             }
299              
300             sub compile_now_finish {
301 2     2 1 7 my ($self, $params) = @_;
302 2         8 delete $self->{am_compiling};
303             }
304              
305             1;
306              
307             =head1 SEE ALSO
308              
309             C<http://issues.apache.org/SpamAssassin/show_bug.cgi?id=3109>
310              
311             =cut