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              
43             use Mail::SpamAssassin::Plugin;
44 20     20   145 use Mail::SpamAssassin::Logger;
  20         41  
  20         587  
45 20     20   123 use strict;
  20         48  
  20         1005  
46 20     20   107 use warnings;
  20         64  
  20         479  
47 20     20   102 # use bytes;
  20         51  
  20         619  
48             use re 'taint';
49 20     20   123  
  20         47  
  20         18752  
50             our @ISA = qw(Mail::SpamAssassin::Plugin);
51              
52             my $class = shift;
53             my $mailsaobject = shift;
54 61     61 1 183  
55 61         146 $class = ref($class) || $class;
56             my $self = $class->SUPER::new($mailsaobject);
57 61   33     363 bless ($self, $class);
58 61         282  
59 61         181 $self->register_eval_rule("check_shortcircuit");
60             $self->set_config($mailsaobject->{conf});
61 61         264  
62 61         303 return $self;
63             }
64 61         545  
65              
66             my($self, $conf) = @_;
67 81     81 0 1133 my @cmds;
68              
69             =head1 CONFIGURATION SETTINGS
70 61     61 0 153  
71 61         126 The following configuration settings are used to control shortcircuiting:
72              
73             =over 4
74              
75             =item shortcircuit SYMBOLIC_TEST_NAME {ham|spam|on|off}
76              
77             Shortcircuiting a test will force all other pending rules to be skipped, if
78             that test is hit.
79              
80             Recommended usage is to use C<priority> to set rules with strong S/O values (ie.
81             1.0) to be run first, and make instant spam or ham classification based on
82             that.
83              
84             To override a test that uses shortcircuiting, you can set the classification
85             type to C<off>.
86              
87             =over 4
88              
89             =item on
90              
91             Shortcircuits the rest of the tests, but does not make a strict classification
92             of spam or ham. Rather, it uses the default score for the rule being
93             shortcircuited. This would allow you, for example, to define a rule such as
94              
95             body TEST /test/
96             describe TEST test rule that scores barely over spam threshold
97             score TEST 5.5
98             priority TEST -100
99             shortcircuit TEST on
100              
101             The result of a message hitting the above rule would be a final score of 5.5,
102             as opposed to 100 (default) if it were classified as spam.
103              
104             =item off
105              
106             Disables shortcircuiting on said rule.
107              
108             =item spam
109              
110             Shortcircuit the rule using a set of defaults; override the default score of
111             this rule with the score from C<shortcircuit_spam_score>, set the
112             C<noautolearn> tflag, and set priority to C<-100>. In other words,
113             equivalent to:
114              
115             shortcircuit TEST on
116             priority TEST -100
117             score TEST 100
118             tflags TEST noautolearn
119              
120             =item ham
121              
122             Shortcircuit the rule using a set of defaults; override the default score of
123             this rule with the score from C<shortcircuit_ham_score>, set the C<noautolearn>
124             and C<nice> tflags, and set priority to C<-100>. In other words, equivalent
125             to:
126              
127             shortcircuit TEST on
128             priority TEST -100
129             score TEST -100
130             tflags TEST noautolearn nice
131              
132             =back
133              
134             =cut
135              
136             push (@cmds, {
137             setting => 'shortcircuit',
138             code => sub {
139             my ($self, $key, $value, $line) = @_;
140             my ($rule,$type);
141             unless (defined $value && $value !~ /^$/) {
142             return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
143 5     5   16 }
144 5         11 if ($value =~ /^(\S+)\s+(\S+)$/) {
145 5 50 33     34 $rule=$1;
146 0         0 $type=$2;
147             } else {
148 5 50       20 return $Mail::SpamAssassin::Conf::INVALID_VALUE;
149 5         12 }
150 5         12  
151             if ($type =~ m/^(?:spam|ham)$/) {
152 0         0 dbg("shortcircuit: adding $rule using abbreviation $type");
153              
154             # set the defaults:
155 5 50       17 $self->{shortcircuit}->{$rule} = $type;
    0          
    0          
156 5         24 $self->{priority}->{$rule} = -100;
157              
158             my $tf = $self->{tflags}->{$rule};
159 5         15 $self->{tflags}->{$rule} = ($tf ? $tf." " : "") .
160 5         9 ($type eq 'ham' ? "nice " : "") .
161             "noautolearn";
162 5         12 }
163 5 50       31 elsif ($type eq "on") {
    50          
164             $self->{shortcircuit}->{$rule} = "on";
165             }
166             elsif ($type eq "off") {
167             delete $self->{shortcircuit}->{$rule};
168 0         0 }
169             else {
170             return $Mail::SpamAssassin::Conf::INVALID_VALUE;
171 0         0 }
172             }
173             });
174 0         0  
175             =item shortcircuit_spam_score n.nn (default: 100)
176              
177 61         547 When shortcircuit is used on a rule, and the shortcircuit classification type
178             is set to C<spam>, this value should be applied in place of the default score
179             for that rule.
180              
181             =cut
182              
183             push (@cmds, {
184             setting => 'shortcircuit_spam_score',
185             default => 100,
186             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
187 61         309 });
188              
189             =item shortcircuit_ham_score n.nn (default: -100)
190              
191             When shortcircuit is used on a rule, and the shortcircuit classification type
192             is set to C<ham>, this value should be applied in place of the default score
193             for that rule.
194              
195             =cut
196              
197             push (@cmds, {
198             setting => 'shortcircuit_ham_score',
199             default => -100,
200             type => $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC
201 61         261 });
202              
203             $conf->{parser}->register_commands(\@cmds);
204             }
205              
206             =back
207 61         308  
208             =head1 TAGS
209              
210             The following tags are added to the set available for use in reports, headers
211             etc.:
212              
213             _SC_ shortcircuit status (classification and rule name)
214             _SCRULE_ rulename that caused the shortcircuit
215             _SCTYPE_ shortcircuit classification ("spam", "ham", "default", "none")
216              
217             =cut
218              
219             my ($self, $params) = @_;
220              
221             my $scan = $params->{permsgstatus};
222             my $rule = $params->{rulename};
223              
224 178     178 1 365 # don't s/c if we're linting
225             return if ($scan->{lint_rules});
226 178         309  
227 178         269 # don't s/c if we're in compile_now()
228             return if ($self->{am_compiling});
229              
230 178 50       404 my $sctype = $scan->{conf}->{shortcircuit}->{$rule};
231             return unless $sctype;
232              
233 178 100       382 my $conf = $scan->{conf};
234             my $score = $params->{score};
235 171         336  
236 171 50       515 $scan->{shortcircuit_rule} = $rule;
237             my $scscore;
238 0         0 if ($sctype eq 'on') { # guess by rule score
239 0         0 dbg("shortcircuit: s/c due to $rule, using score of $score");
240             $scan->{shortcircuit_type} = ($score < 0 ? 'ham' : 'spam');
241 0         0 $scscore = ($score < 0) ? -0.0001 : 0.0001;
242 0         0 }
243 0 0       0 else {
244 0         0 $scan->{shortcircuit_type} = $sctype;
245 0 0       0 if ($sctype eq 'ham') {
246 0 0       0 $score = $conf->{shortcircuit_ham_score};
247             } else {
248             $score = $conf->{shortcircuit_spam_score};
249 0         0 }
250 0 0       0 dbg("shortcircuit: s/c $sctype due to $rule, using score of $score");
251 0         0 $scscore = $score;
252             }
253 0         0  
254             # bug 5256: if we short-circuit, don't do auto-learning
255 0         0 $scan->{disable_auto_learning} = 1;
256 0         0 $scan->got_hit('SHORTCIRCUIT', '', score => $scscore);
257             }
258              
259             my ($self, $params) = @_;
260 0         0 my $scan = $params->{permsgstatus};
261 0         0  
262             $scan->set_tag ('SC', sub {
263             my $rule = $scan->{shortcircuit_rule};
264             my $type = $scan->{shortcircuit_type};
265 81     81 1 201 return "$rule ($type)" if ($rule);
266 81         172 return "no";
267             });
268              
269 0     0   0 $scan->set_tag ('SCRULE', sub {
270 0         0 my $rule = $scan->{shortcircuit_rule};
271 0 0       0 return ($rule || "none");
272 0         0 });
273 81         674  
274             $scan->set_tag ('SCTYPE', sub {
275             my $type = $scan->{shortcircuit_type};
276 0     0   0 return ($type || "no");
277 0   0     0 });
278 81         447  
279             $scan->set_spamd_result_item (sub {
280             "shortcircuit=".$scan->get_tag("SCTYPE");
281 0     0   0 });
282 0   0     0 }
283 81         428  
284             my ($self, $params) = @_;
285             return (exists $params->{permsgstatus}->{shortcircuit_type}) ? 1 : 0;
286 0     0   0 }
287 81         526  
288             my ($self, $params) = @_;
289             $self->{am_compiling} = 1;
290             }
291 2106     2106 1 3726  
292 2106 50       5170 my ($self, $params) = @_;
293             delete $self->{am_compiling};
294             }
295              
296 2     2 1 6 1;
297 2         6  
298             =head1 SEE ALSO
299              
300             C<http://issues.apache.org/SpamAssassin/show_bug.cgi?id=3109>
301 2     2 1 7  
302 2         6 =cut