File Coverage

blib/lib/Mail/SpamAssassin/Plugin/Check.pm
Criterion Covered Total %
statement 961 1249 76.9
branch 196 460 42.6
condition 44 110 40.0
subroutine 95 100 95.0
pod 3 32 9.3
total 1299 1951 66.5


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Mail::SpamAssassin::Plugin::Check - primary message check functionality
4              
5             =head1 SYNOPSIS
6              
7             loadplugin Mail::SpamAssassin::Plugin::Check
8              
9             =head1 DESCRIPTION
10              
11             This plugin provides the primary message check functionality.
12              
13             =cut
14              
15              
16             use strict;
17 23     23   209 use warnings;
  23         44  
  23         684  
18 23     23   128 use re 'taint';
  23         40  
  23         729  
19 23     23   116  
  23         60  
  23         937  
20             use Time::HiRes qw(time);
21 23     23   172  
  23         48  
  23         376  
22             use Mail::SpamAssassin::Plugin;
23 23     23   3552 use Mail::SpamAssassin::Logger;
  23         42  
  23         575  
24 23     23   120 use Mail::SpamAssassin::Util qw(untaint_var);
  23         35  
  23         1456  
25 23     23   143 use Mail::SpamAssassin::Timeout;
  23         50  
  23         1109  
26 23     23   133 use Mail::SpamAssassin::Constants qw(:sa);
  23         56  
  23         747  
27 23     23   141  
  23         67  
  23         44582  
28             our @ISA = qw(Mail::SpamAssassin::Plugin);
29              
30             my $ARITH_EXPRESSION_LEXER = ARITH_EXPRESSION_LEXER;
31             my $META_RULES_MATCHING_RE = META_RULES_MATCHING_RE;
32              
33             # methods defined by the compiled ruleset; deleted in finish_tests()
34             our @TEMPORARY_METHODS;
35              
36             # constructor
37             my $class = shift;
38             my $mailsaobject = shift;
39 59     59 1 226  
40 59         132 $class = ref($class) || $class;
41             my $self = $class->SUPER::new($mailsaobject);
42 59   33     421 bless ($self, $class);
43 59         458  
44 59         136 return $self;
45             }
46 59         459  
47             ###########################################################################
48              
49             my ($self, $args) = @_;
50              
51             my $pms = $args->{permsgstatus};
52 96     96 1 311  
53             my $suppl_attrib = $pms->{msg}->{suppl_attrib};
54 96         206 if (ref $suppl_attrib && ref $suppl_attrib->{rule_hits}) {
55             my @caller_rule_hits = @{$suppl_attrib->{rule_hits}};
56 96         221 dbg("check: adding caller rule hits, %d rules", scalar(@caller_rule_hits));
57 96 50 66     546 for my $caller_rule_hit (@caller_rule_hits) {
58 0         0 next if ref $caller_rule_hit ne 'HASH';
  0         0  
59 0         0 my($rulename, $area, $score, $defscore, $value,
60 0         0 $ruletype, $tflags, $description) =
61 0 0       0 @$caller_rule_hit{qw(rule area score defscore value
62             ruletype tflags descr)};
63             $pms->got_hit($rulename, $area,
64 0         0 !defined $score ? () : (score => $score),
65             !defined $defscore ? () : (defscore => $defscore),
66 0 0       0 !defined $value ? () : (value => $value),
    0          
    0          
    0          
    0          
67             !defined $tflags ? () : (tflags => $tflags),
68             !defined $description ? () : (description => $description),
69             ruletype => $ruletype);
70             }
71             }
72              
73             # bug 4353:
74             # Do this before the RBL tests are kicked off. The metadata parsing
75             # will figure out the (un)trusted relays and such, which are used in the
76             # rbl calls.
77             $pms->extract_message_metadata();
78              
79             # Here, we launch all the DNS RBL queries and let them run while we
80 96         623 # inspect the message
81             $self->run_rbl_eval_tests($pms);
82             my $needs_dnsbl_harvest_p = 1; # harvest needs to be run
83              
84 96         587 my $decoded = $pms->get_decoded_stripped_body_text_array();
85 96         224 my $bodytext = $pms->get_decoded_body_text_array();
86             my $fulltext = $pms->{msg}->get_pristine();
87 96         330 my $master_deadline = $pms->{master_deadline};
88 96         384 dbg("check: check_main, time limit in %.3f s",
89 96         443 $master_deadline - time) if $master_deadline;
90 96         244  
91 96 50       677 my @uris = $pms->get_uri_list();
92              
93             foreach my $priority (sort { $a <=> $b } keys %{$pms->{conf}->{priorities}}) {
94 96         471 # no need to run if there are no priorities at this level. This can
95             # happen in Conf.pm when we switch a rule from one priority to another
96 96         179 next unless ($pms->{conf}->{priorities}->{$priority} > 0);
  224         633  
  96         1143  
97              
98             if ($pms->{deadline_exceeded}) {
99 258 50       993 last;
100             } elsif ($master_deadline && time > $master_deadline) {
101 258 50 33     2454 info("check: exceeded time limit, skipping further tests");
    50          
    50          
102 0         0 $pms->{deadline_exceeded} = 1;
103             last;
104 0         0 } elsif ($self->{main}->call_plugins("have_shortcircuited",
105 0         0 { permsgstatus => $pms })) {
106 0         0 # if shortcircuiting is hit, we skip all other priorities...
107             last;
108             }
109              
110 0         0 my $timer = $self->{main}->time_method("tests_pri_".$priority);
111             dbg("check: running tests for priority: $priority");
112              
113 258         1236 # only harvest the dnsbl queries once priority HARVEST_DNSBL_PRIORITY
114 258         1102 # has been reached and then only run once
115             #
116             # TODO: is this block still needed here? is HARVEST_DNSBL_PRIORITY used?
117             #
118             if ($priority >= HARVEST_DNSBL_PRIORITY
119             && $needs_dnsbl_harvest_p
120             && !$self->{main}->call_plugins("have_shortcircuited",
121 258 100 100     1470 { permsgstatus => $pms }))
      66        
122             {
123             # harvest the DNS results
124             $pms->harvest_dnsbl_queries();
125             $needs_dnsbl_harvest_p = 0;
126              
127 81         344 # finish the DNS results
128 81         127 $pms->rbl_finish();
129             $self->{main}->call_plugins("check_post_dnsbl", { permsgstatus => $pms });
130             $pms->{resolver}->finish_socket() if $pms->{resolver};
131 81         319 }
132 81         396  
133 81 100       372 $pms->harvest_completed_queries();
134             # allow other, plugin-defined rule types to be called here
135             $self->{main}->call_plugins ("check_rules_at_priority",
136 258         1540 { permsgstatus => $pms, priority => $priority, checkobj => $self });
137              
138 258         1543 # do head tests
139             $self->do_head_tests($pms, $priority);
140             $pms->harvest_completed_queries();
141             last if $pms->{deadline_exceeded};
142 258         1274  
143 258         5112 $self->do_head_eval_tests($pms, $priority);
144 258 50       746 $pms->harvest_completed_queries();
145             last if $pms->{deadline_exceeded};
146 258         1068  
147 258         939 $self->do_body_tests($pms, $priority, $decoded);
148 258 50       773 $pms->harvest_completed_queries();
149             last if $pms->{deadline_exceeded};
150 258         917  
151 258         3546 $self->do_uri_tests($pms, $priority, @uris);
152 258 50       732 $pms->harvest_completed_queries();
153             last if $pms->{deadline_exceeded};
154 258         958  
155 258         2883 $self->do_body_eval_tests($pms, $priority, $decoded);
156 258 50       811 $pms->harvest_completed_queries();
157             last if $pms->{deadline_exceeded};
158 258         837
159 258         691 $self->do_rawbody_tests($pms, $priority, $bodytext);
160 258 50       618 $pms->harvest_completed_queries();
161             last if $pms->{deadline_exceeded};
162 258         843  
163 258         2883 $self->do_rawbody_eval_tests($pms, $priority, $bodytext);
164 258 50       759 $pms->harvest_completed_queries();
165             last if $pms->{deadline_exceeded};
166 258         816
167 258         599 $self->do_full_tests($pms, $priority, \$fulltext);
168 258 50       729 $pms->harvest_completed_queries();
169             last if $pms->{deadline_exceeded};
170 258         980  
171 258         2473 $self->do_full_eval_tests($pms, $priority, \$fulltext);
172 258 50       730 $pms->harvest_completed_queries();
173             last if $pms->{deadline_exceeded};
174 258         919  
175 258         758 $self->do_meta_tests($pms, $priority);
176 258 50       625 $pms->harvest_completed_queries();
177             last if $pms->{deadline_exceeded};
178 258         801  
179 258         5870 # we may need to call this more often than once through the loop, but
180 258 50       731 # it needs to be done at least once, either at the beginning or the end.
181             $self->{main}->call_plugins ("check_tick", { permsgstatus => $pms });
182             $pms->harvest_completed_queries();
183             last if $pms->{deadline_exceeded};
184 258         1238 }
185 258         823  
186 258 50       1101 # sanity check, it is possible that no rules >= HARVEST_DNSBL_PRIORITY ran so the harvest
187             # may not have run yet. Check, and if so, go ahead and harvest here.
188             if ($needs_dnsbl_harvest_p) {
189             if (!$self->{main}->call_plugins("have_shortcircuited",
190             { permsgstatus => $pms }))
191 96 100       404 {
192 15 50       63 # harvest the DNS results
193             $pms->harvest_dnsbl_queries();
194             }
195              
196 15         61 # finish the DNS results
197             $pms->rbl_finish();
198             $self->{main}->call_plugins ("check_post_dnsbl", { permsgstatus => $pms });
199             $pms->{resolver}->finish_socket() if $pms->{resolver};
200 15         95 }
201 15         83  
202 15 50       53 if ($pms->{deadline_exceeded}) {
203             $pms->got_hit('TIME_LIMIT_EXCEEDED', '', defscore => 0.001,
204             description => 'Exceeded time limit / deadline');
205 96 50       267 }
206 0         0  
207             # finished running rules
208             delete $pms->{current_rule_name};
209             undef $decoded;
210             undef $bodytext;
211 96         228 undef $fulltext;
212 96         162  
213 96         155 if ($pms->{deadline_exceeded}) {
214 96         196 # dbg("check: exceeded time limit, skipping auto-learning");
215             } elsif ($master_deadline && time > $master_deadline) {
216 96 50 33     742 info("check: exceeded time limit, skipping auto-learning");
    50          
217             $pms->{deadline_exceeded} = 1;
218             } else {
219 0         0 # auto-learning
220 0         0 $pms->learn();
221             $self->{main}->call_plugins ("check_post_learn", { permsgstatus => $pms });
222             }
223 96         543  
224 102         588 # track user_rules recompilations; each scanned message is 1 tick on this counter
225             if ($self->{done_user_rules}) {
226             my $counters = $pms->{conf}->{want_rebuild_for_type};
227             foreach my $type (keys %{$self->{done_user_rules}}) {
228 102 50       404 if ($counters->{$type} > 0) {
229 0         0 $counters->{$type}--;
230 0         0 }
  6         23  
231 0 0       0 dbg("rules: user rules done; ticking want_rebuild counter for type $type to ".
232 0         0 $counters->{$type});
233             }
234             }
235 0         0  
236             return 1;
237             }
238              
239 96         353 my ($self, $params) = @_;
240              
241             foreach my $method (@TEMPORARY_METHODS) {
242             undef &{$method};
243 34     34 1 115 }
244             @TEMPORARY_METHODS = (); # clear for next time
245 34         77 }
246 103         192  
  103         10646  
247             ###########################################################################
248 34         155  
249             my ($self, $pms) = @_;
250             my ($rulename, $pat, @args);
251              
252             # XXX - possible speed up, moving this check out of the subroutine into Check->new()
253             if ($self->{main}->{local_tests_only}) {
254 96     96 0 275 dbg("rules: local tests only, ignoring RBL eval");
255 96         179 return 0;
256             }
257              
258 96 100       326 while (my ($rulename, $test) = each %{$pms->{conf}->{rbl_evals}}) {
259 92         306 my $score = $pms->{conf}->{scores}->{$rulename};
260 92         186 next unless $score;
261              
262             %{$pms->{test_log_msgs}} = (); # clear test state
263 4         6  
  4         21  
264 0         0 my $function = $test->[0];
265 0 0       0 if (!exists $pms->{conf}->{eval_plugins}->{$function}) {
266             warn("rules: unknown eval '$function' for $rulename, ignoring RBL eval\n");
267 0         0 return 0;
  0         0  
268             }
269 0         0  
270 0 0       0 my $result;
271 0         0 eval {
272 0         0 $result = $pms->$function($rulename, @{$test->[1]}); 1;
273             } or do {
274             my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
275 0         0 die "rules: $eval_stat\n" if $eval_stat =~ /__alarm__ignore__/;
276             warn "rules: failed to run $rulename RBL test, skipping:\n".
277 0         0 "\t($eval_stat)\n";
  0         0  
  0         0  
278 0 0       0 $pms->{rule_errors}++;
279 0 0       0 next;
  0         0  
280 0 0       0 };
281 0         0 }
282             }
283 0         0  
284 0         0 ###########################################################################
285              
286             my ($self, $pms, $priority, %opts) = @_;
287              
288             my $master_deadline = $pms->{master_deadline};
289             if ($pms->{deadline_exceeded}) {
290             return;
291             } elsif ($master_deadline && time > $master_deadline) {
292 1548     1548 0 8446 info("check: (run_generic) exceeded time limit, skipping further tests");
293             $pms->{deadline_exceeded} = 1;
294 1548         2667 return;
295 1548 50 33     10793 } elsif ($self->{main}->call_plugins("have_shortcircuited",
    50          
    50          
296 0         0 { permsgstatus => $pms })) {
297             return;
298 0         0 }
299 0         0  
300 0         0 my $ruletype = $opts{type};
301             dbg("rules: running $ruletype tests; score so far=".$pms->{score});
302             %{$pms->{test_log_msgs}} = (); # clear test state
303 0         0  
304             my $conf = $pms->{conf};
305             my $doing_user_rules = $conf->{want_rebuild_for_type}->{$opts{consttype}};
306 1548         3051 if ($doing_user_rules) { $self->{done_user_rules}->{$opts{consttype}}++; }
307 1548         7638  
308 1548         2245 # clean up priority value so it can be used in a subroutine name
  1548         2849  
309             my $clean_priority;
310 1548         2117 ($clean_priority = $priority) =~ s/-/neg/;
311 1548         3091 my $package_name = __PACKAGE__;
312 1548 50       2605 my $methodname = $package_name."::_".$ruletype."_tests_".$clean_priority;
  0         0  
313              
314             if (!defined &{$methodname} || $doing_user_rules) {
315 1548         1679  
316 1548         3044 # use %nopts for named parameter-passing; it's more friendly
317 1548         1736 # to future-proof subclassing, since new parameters can be added without
318 1548         2637 # breaking third-party subclassed implementations of this plugin.
319             my %nopts = (
320 1548 100 66     1730 ruletype => $ruletype,
  1548         9880  
321             doing_user_rules => $doing_user_rules,
322             priority => $priority,
323             clean_priority => $clean_priority
324             );
325 150         537  
326             # build up the eval string...
327             $self->{evalstr_methodname} = $methodname;
328             $self->{evalstr_chunk_current_methodname} = undef;
329             $self->{evalstr_chunk_methodnames} = [];
330             $self->{evalstr_chunk_prefix} = []; # stack (array) of source code sections
331             $self->{evalstr} = ''; $self->{evalstr_l} = 0;
332             $self->{evalstr2} = '';
333 150         290 $self->begin_evalstr_chunk($pms);
334 150         210  
335 150         284 $self->push_evalstr_prefix($pms, '
336 150         242 # start_rules_plugin_code '.$ruletype.' '.$priority.'
337 150         228 my $scoresptr = $self->{conf}->{scores};
  150         216  
338 150         215 my $qrptr = $self->{conf}->{test_qrs};
339 150         357 ');
340             if (defined $opts{pre_loop_body}) {
341 150         478 $opts{pre_loop_body}->($self, $pms, $conf, %nopts);
342             }
343             $self->add_evalstr($pms,
344             $self->start_rules_plugin_code($ruletype, $priority) );
345             while (my($rulename, $test) = each %{$opts{testhash}->{$priority}}) {
346 150 100       317 $opts{loop_body}->($self, $pms, $conf, $rulename, $test, %nopts);
347 75         271 }
348             if (defined $opts{post_loop_body}) {
349 150         361 $opts{post_loop_body}->($self, $pms, $conf, %nopts);
350             }
351 150         199  
  274         1132  
352 124         316 # dbg("rules: generated matching code:\n".$self->{evalstr});
353              
354 150 100       347 $self->flush_evalstr($pms, 'run_generic_tests');
355 50         150 $self->free_ruleset_source($pms, $ruletype, $priority);
356              
357             # clear out a previous version of this method
358             undef &{$methodname};
359              
360 150         383 # generate the loop that goes through each line...
361 150         382 my $evalstr = <<"EOT";
362             {
363             package $package_name;
364 150         222  
  150         635  
365             $self->{evalstr2}
366              
367 150         613 sub $methodname {
368             EOT
369              
370             for my $chunk_methodname (@{$self->{evalstr_chunk_methodnames}}) {
371             $evalstr .= " $chunk_methodname(\@_);\n";
372             }
373              
374             $evalstr .= <<"EOT";
375             }
376 150         195  
  150         273  
377 150         320 1;
378             }
379             EOT
380 150         216  
381             delete $self->{evalstr}; # free up some RAM before we eval()
382             delete $self->{evalstr2};
383             delete $self->{evalstr_methodname};
384             delete $self->{evalstr_chunk_current_methodname};
385             delete $self->{evalstr_chunk_methodnames};
386             delete $self->{evalstr_chunk_prefix};
387 150         233  
388 150         198 dbg("rules: run_generic_tests - compiling eval code: %s, priority %s",
389 150         180 $ruletype, $priority);
390 150         183 # dbg("rules: eval code to compile: %s", $evalstr);
391 150         211 my $eval_result;
392 150         195 { my $timer = $self->{main}->time_method('compile_gen');
393             $eval_result = eval($evalstr);
394 150         439 }
395             if (!$eval_result) {
396             my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
397 150         183 warn "rules: failed to compile $ruletype tests, skipping:\n".
398 150         171 "\t($eval_stat)\n";
  150         362  
399 150     96   10670 $pms->{rule_errors}++;
  96     81   1693  
  81     81   1255  
  81     96   1203  
  96     81   1526  
  81     81   1283  
  81     96   1321  
  96     81   1647  
  81     81   1242  
  81     96   1257  
  96     81   1598  
  81     81   1213  
  81     96   1208  
  96     81   1588  
  81     81   1175  
  81     96   1304  
  96     81   1608  
  81     81   1230  
  81         1266  
400             return;
401 150 50       416 }
402 0 0       0 dbg("rules: compiled $ruletype tests");
  0         0  
403 0         0 }
404              
405 0         0 #run_compiled_method:
406 0         0 # dbg("rules: run_generic_tests - calling %s", $methodname);
407             my $t = Mail::SpamAssassin::Timeout->new({ deadline => $master_deadline });
408 150         543 my $err = $t->run(sub {
409             no strict "refs";
410             $methodname->($pms, @{$opts{args}});
411             });
412             if ($t->timed_out() && $master_deadline && time > $master_deadline) {
413 1548         6477 info("check: exceeded time limit in $methodname, skipping further tests");
414             $pms->{deadline_exceeded} = 1;
415 23     23   223 }
  23         54  
  23         87661  
416 1548     1548   2094 }
  1548         39291  
417 1548         7603  
418 1548 0 33     6291 my ($self, $pms) = @_;
      33        
419 0         0 my $n = 0;
420 0         0 if ($self->{evalstr_chunk_methodnames}) {
421             $n = scalar(@{$self->{evalstr_chunk_methodnames}});
422             }
423             my $chunk_methodname = sprintf("%s_%d", $self->{evalstr_methodname}, $n+1);
424             # dbg("rules: begin_evalstr_chunk %s", $chunk_methodname);
425 300     300 0 453 undef &{$chunk_methodname};
426 300         356 my $package_name = __PACKAGE__;
427 300 50       566 my $evalstr = <<"EOT";
428 300         320 package $package_name;
  300         445  
429             sub $chunk_methodname {
430 300         1131 my \$self = shift;
431             my \$hits = 0;
432 300         366 EOT
  300         1741  
433 300         497 $evalstr .= ' '.$_ for @{$self->{evalstr_chunk_prefix}};
434 300         593 $self->{evalstr} = $evalstr;
435             $self->{evalstr_l} = length($evalstr);
436             $self->{evalstr_chunk_current_methodname} = $chunk_methodname;
437             }
438              
439             my ($self, $pms) = @_;
440 300         345 # dbg("rules: end_evalstr_chunk");
  300         1985  
441 300         540 my $evalstr = "}; 1;\n";
442 300         412 $self->{evalstr} .= $evalstr;
443 300         650 $self->{evalstr_l} += length($evalstr);
444             }
445              
446             my ($self, $pms, $caller_name) = @_;
447 150     150 0 219 my $chunk_methodname = $self->{evalstr_chunk_current_methodname};
448             $self->end_evalstr_chunk($pms);
449 150         188 dbg("rules: flush_evalstr (%s) compiling %d chars of %s",
450 150         270 $caller_name, $self->{evalstr_l}, $chunk_methodname);
451 150         227 # dbg("rules: eval code(2): %s", $self->{evalstr});
452             my $eval_result;
453             { my $timer = $self->{main}->time_method('compile_gen');
454             $eval_result = eval($self->{evalstr});
455 150     150 0 235 }
456 150         198 if (!$eval_result) {
457 150         308 my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
458             warn "rules: failed to compile $chunk_methodname, skipping:\n".
459 150         428 "\t($eval_stat)\n";
460             $pms->{rule_errors}++;
461 150         158 } else {
462 150         178 push(@{$self->{evalstr_chunk_methodnames}}, $chunk_methodname);
  150         344  
463 28 50 66 28   175 }
  28 50 66 8   80  
  28     14   5135  
  27     96   202  
  27     81   60  
  27     81   545  
  8     96   67  
  8     81   21  
  8     81   383  
  150     96   19968  
  96     81   256  
  96     81   167  
  96     96   283  
  96     81   1364  
  81     81   243  
  81     96   186  
  81     81   230  
  81     81   1091  
  81     96   232  
  81     81   126  
  81     81   191  
  81         1052  
  96         263  
  96         152  
  96         246  
  96         181  
  96         1268  
  81         183  
  81         121  
  81         201  
  81         146  
  81         1051  
  81         214  
  81         135  
  81         196  
  81         142  
  81         1066  
  96         251  
  96         169  
  96         266  
  96         226  
  96         312  
  81         308  
  81         196  
  81         123  
  81         226  
  81         166  
  81         1057  
  81         231  
  81         157  
  81         229  
  81         158  
  81         1062  
  96         255  
  96         193  
  96         229  
  96         183  
  96         183  
  96         1272  
  81         228  
  81         137  
  81         218  
  81         165  
  81         126  
  81         1120  
  81         241  
  81         140  
  81         224  
  81         146  
  81         134  
  81         155  
  81         516  
  81         212  
  0         0  
  81         351  
  81         1159  
  0         0  
  96         272  
  96         150  
  96         263  
  96         1290  
  81         246  
  81         146  
  81         200  
  81         1020  
  81         239  
  81         135  
  81         219  
  81         1060  
  96         293  
  96         152  
  96         236  
  96         437  
  81         216  
  81         125  
  81         188  
  81         1084  
  81         198  
  81         135  
  81         182  
  81         1099  
464             $self->{evalstr} = ''; $self->{evalstr_l} = 0;
465 150 50       661 $self->begin_evalstr_chunk($pms);
466 0 0       0 }
  0         0  
467 0         0  
468             my ($self, $pms, $str) = @_;
469 0         0 $self->add_evalstr_corked($pms, $str); # must not flush!
470             push(@{$self->{evalstr_chunk_prefix}}, $str);
471 150         196 # dbg("rules: push_evalstr_prefix (%d) - <%s>",
  150         467  
472             # scalar(@{$self->{evalstr_chunk_prefix}}), $str);
473 150         262 }
  150         207  
474 150         348  
475             my ($self) = @_;
476             pop(@{$self->{evalstr_chunk_prefix}});
477             # dbg("rules: pop_evalstr_prefix (%d)",
478 281     281 0 473 # scalar(@{$self->{evalstr_chunk_prefix}}));
479 281         564 }
480 281         297  
  281         618  
481             my ($self, $pms, $str) = @_;
482             if (defined $str && $str ne '') {
483             my $new_code_l = length($str);
484             # dbg("rules: add_evalstr %d - <%s>", $new_code_l, $str);
485             $self->{evalstr} .= $str;
486 56     56 0 82 $self->{evalstr_l} += $new_code_l;
487 56         60 if ($self->{evalstr_l} > 60000) {
  56         217  
488             $self->flush_evalstr($pms, 'add_evalstr');
489             }
490             }
491             }
492              
493 274     274 0 435 # similar to add_evalstr, but avoids flushing on size
494 274 100 66     908 my ($self, $pms, $str) = @_;
495 124         155 if (defined $str) {
496             my $new_code_l = length($str);
497 124         349 $self->{evalstr} .= $str;
498 124         163 $self->{evalstr_l} += $new_code_l;
499 124 50       406 }
500 0         0 }
501              
502             my ($self, $str) = @_;
503             $self->{evalstr2} .= $str;
504             }
505              
506             my ($self, $methodname, $methodbody) = @_;
507 281     281 0 372 $self->add_evalstr2 (' sub '.$methodname.' { '.$methodbody.' } ');
508 281 50       469 push (@TEMPORARY_METHODS, $methodname);
509 281         415 }
510 281         563  
511 281         447 ###########################################################################
512              
513             # Returns all rulenames matching glob (FOO_*)
514             my ($self, $ruleglob, $pms, $conf, $rulename) = @_;
515             my $expanded;
516 0     0 0 0 if (exists $pms->{ruleglob_cache}{$ruleglob}) {
517 0         0 $expanded = $pms->{ruleglob_cache}{$ruleglob};
518             } else {
519             my $reglob = $ruleglob;
520             $reglob =~ s/\?/./g;
521 0     0 0 0 $reglob =~ s/\*/.*?/g;
522 0         0 # Glob rules, but do not match ourselves..
523 0         0 my @rules = grep {/^${reglob}$/ && $_ ne $rulename} keys %{$conf->{scores}};
524             if (@rules) {
525             $expanded = join('+', sort @rules);
526             } else {
527             $expanded = '0';
528             }
529             }
530 0     0 0 0 my $logstr = $expanded eq '0' ? 'no matches' : $expanded;
531 0         0 dbg("rules: meta $rulename rules_matching($ruleglob) expanded: $logstr");
532 0 0       0 $pms->{ruleglob_cache}{$ruleglob} = $expanded;
533 0         0 return " ($expanded) ";
534             };
535 0         0  
536 0         0 my ($self, $pms, $priority) = @_;
537 0         0 my (%rule_deps, %meta, $rulename);
538              
539 0 0       0 $self->run_generic_tests ($pms, $priority,
  0         0  
  0         0  
540 0 0       0 consttype => $Mail::SpamAssassin::Conf::TYPE_META_TESTS,
541 0         0 type => 'meta',
542             testhash => $pms->{conf}->{meta_tests},
543 0         0 args => [ ],
544             loop_body => sub
545             {
546 0 0       0 my ($self, $pms, $conf, $rulename, $rule, %opts) = @_;
547 0         0  
548 0         0 # Expand meta rules_matching() before lexing
549 0         0 $rule =~ s/${META_RULES_MATCHING_RE}/$self->expand_ruleglob($1,$pms,$conf,$rulename)/ge;
550              
551             # Lex the rule into tokens using a rather simple RE method ...
552             my @tokens = ($rule =~ /$ARITH_EXPRESSION_LEXER/og);
553 258     258 0 538  
554 258         407 # Set the rule blank to start
555             $meta{$rulename} = "";
556              
557             # List dependencies that are meta tests in the same priority band
558             $rule_deps{$rulename} = [ ];
559              
560             # Go through each token in the meta rule
561             foreach my $token (@tokens) {
562              
563 16     16   50 # ... rulename?
564             if ($token =~ IS_RULENAME) {
565             # the " || 0" formulation is to avoid "use of uninitialized value"
566 16         63 # warnings; this is better than adding a 0 to a hash for every
  0         0  
567             # rule referred to in a meta...
568             $meta{$rulename} .= "(\$h->{'$token'}||0) ";
569 16         169
570             if (!exists $conf->{scores}->{$token}) {
571             dbg("rules: meta test $rulename has undefined dependency '$token'");
572 16         38 }
573             elsif ($conf->{scores}->{$token} == 0) {
574             # bug 5040: net rules in a non-net scoreset
575 16         38 # there are some cases where this is expected; don't warn
576             # in those cases.
577             unless ((($conf->get_score_set()) & 1) == 0 &&
578 16         33 ($conf->{tflags}->{$token}||'') =~ /\bnet\b/)
579             {
580             info("rules: meta test $rulename has dependency '$token' with a zero score");
581 96 100       224 }
582             }
583              
584             # If the token is another meta rule, add it as a dependency
585 40         96 push (@{ $rule_deps{$rulename} }, $token)
586             if (exists $conf->{meta_tests}->{$opts{priority}}->{$token});
587 40 50       156 } else {
    50          
588 0         0 # ... number or operator
589             $meta{$rulename} .= "$token ";
590             }
591             }
592             },
593             pre_loop_body => sub
594 0 0 0     0 {
      0        
595             my ($self, $pms, $conf, %opts) = @_;
596             $self->push_evalstr_prefix($pms, '
597 0         0 my $r;
598             my $h = $self->{tests_already_hit};
599             ');
600             },
601             post_loop_body => sub
602 0         0 {
603 40 50       133 my ($self, $pms, $conf, %opts) = @_;
604              
605             # Sort by length of dependencies list. It's more likely we'll get
606 56         112 # the dependencies worked out this way.
607             my @metas = sort { @{ $rule_deps{$a} } <=> @{ $rule_deps{$b} } }
608             keys %{$conf->{meta_tests}->{$opts{priority}}};
609              
610             my $count;
611             my $tflags = $conf->{tflags};
612 25     25   114  
613 25         66 # Now go ahead and setup the eval string
614             do {
615             $count = $#metas;
616             my %metas = map { $_ => 1 } @metas; # keep a small cache for fast lookups
617              
618             # Go through each meta rule we haven't done yet
619             for (my $i = 0 ; $i <= $#metas ; $i++) {
620 25     25   91  
621             # If we depend on meta rules that haven't run yet, skip it
622             next if (grep( $metas{$_}, @{ $rule_deps{ $metas[$i] } }));
623              
624 8         16 # If we depend on network tests, call ensure_rules_are_complete()
  8         18  
  8         43  
625 25         38 # to block until they are
  25         111  
626             if (!defined $conf->{meta_dependencies}->{ $metas[$i] }) {
627 25         42 warn "no meta_dependencies defined for $metas[$i]";
628 25         78 }
629             my $alldeps = join ' ', grep {
630             ($tflags->{$_}||'') =~ /\bnet\b/
631 25   66     38 } split (' ', $conf->{meta_dependencies}->{ $metas[$i] } );
632 25         41  
633 25         52 if ($alldeps ne '') {
  16         51  
634             $self->add_evalstr($pms, '
635             $self->ensure_rules_are_complete(q{'.$metas[$i].'}, qw{'.$alldeps.'});
636 25         154 ');
637             }
638              
639 16 50       27 # Add this meta rule to the eval line
  16         73  
640             $self->add_evalstr($pms, '
641             $r = '.$meta{$metas[$i]}.';
642             if ($r) { $self->got_hit(q#'.$metas[$i].'#, "", ruletype => "meta", value => $r); }
643 16 50       57 ');
644 0         0  
645             splice @metas, $i--, 1; # remove this rule from our list
646             }
647 40   50     159 } while ($#metas != $count && $#metas > -1); # run until we can't go anymore
648 16         51  
649             # If there are any rules left, we can't solve the dependencies so complain
650 16 50       58 my %metas = map { $_ => 1 } @metas; # keep a small cache for fast lookups
651 0         0 foreach my $rulename_t (@metas) {
652             $pms->{rule_errors}++; # flag to --lint that there was an error ...
653             my $msg =
654             "rules: excluding meta test $rulename_t, unsolved meta dependencies: " .
655             join(", ", grep($metas{$_}, @{ $rule_deps{$rulename_t} }));
656             if ($self->{main}->{lint_rules}) {
657             warn $msg."\n";
658 16         76 }
659             else {
660             info($msg);
661             }
662 16         91 }
663             }
664             );
665             }
666              
667 25         56 ###########################################################################
  0         0  
668 25         80  
669 0         0 my ($self, $pms, $priority) = @_;
670             # hash to hold the rules, "header\tdefault value" => rulename
671             my %ordered;
672 0         0 my %testcode; # tuples: [op_type, op, arg]
  0         0  
673 0 0       0 # op_type: 1=infix, 0:prefix/function
674 0         0 # op: operator, e.g. '=~', '!~', or a function like 'defined'
675             # arg: additional argument like a regexp for a patt matching op
676              
677 0         0 $self->run_generic_tests ($pms, $priority,
678             consttype => $Mail::SpamAssassin::Conf::TYPE_HEAD_TESTS,
679             type => 'head',
680             testhash => $pms->{conf}->{head_tests},
681 258         3328 args => [ ],
682             loop_body => sub
683             {
684             my ($self, $pms, $conf, $rulename, $pat, %opts) = @_;
685             my ($op, $op_infix);
686             my $hdrname = $conf->{test_opt_header}->{$rulename};
687 258     258 0 539 if (exists $conf->{test_opt_exists}->{$rulename}) {
688             $op_infix = 0;
689 258         468 if (exists $conf->{test_opt_neg}->{$rulename}) {
690             $op = '!defined';
691             } else {
692             $op = 'defined';
693             }
694             }
695             else {
696             $op_infix = 1;
697             $op = $conf->{test_opt_neg}->{$rulename} ? '!~' : '=~';
698             }
699              
700             my $def = $conf->{test_opt_unset}->{$rulename};
701             push(@{ $ordered{$hdrname . (!defined $def ? '' : "\t$rulename")} },
702 72     72   198 $rulename);
703 72         110  
704 72         171 return if ($opts{doing_user_rules} &&
705 72 100       141 !$self->is_user_rule_sub($rulename.'_head_test'));
706 8         22  
707 8 50       28 $testcode{$rulename} = [$op_infix, $op, $pat];
708 0         0 },
709             pre_loop_body => sub
710 8         17 {
711             my ($self, $pms, $conf, %opts) = @_;
712             $self->push_evalstr_prefix($pms, '
713             no warnings q(uninitialized);
714 64         70 my $hval;
715 64 100       112 ');
716             },
717             post_loop_body => sub
718 72         104 {
719 72 100       88 my ($self, $pms, $conf, %opts) = @_;
  72         301  
720             # setup the function to run the rules
721             while(my($k,$v) = each %ordered) {
722             my($hdrname, $def) = split(/\t/, $k, 2);
723 72 50 33     152 $self->push_evalstr_prefix($pms, '
724             $hval = $self->get(q{'.$hdrname.'}, ' .
725 72         301 (!defined($def) ? 'undef' :
726             '$self->{conf}->{test_opt_unset}->{q{'.$def.'}}') . ');
727             ');
728             foreach my $rulename (@{$v}) {
729 25     25   123 my $tc_ref = $testcode{$rulename};
730 25         68 my ($op_infix, $op, $pat);
731             ($op_infix, $op, $pat) = @$tc_ref if defined $tc_ref;
732              
733             my $posline = '';
734             my $ifwhile = 'if';
735             my $matchg = '';
736             my $whlast = '';
737 25     25   103  
738             my $matching_string_unavailable = 0;
739 25         124 my $expr;
740 56         149 if (!$op_infix) { # function or its negation
741 56 100       248 $expr = $op . '($hval)';
742             $matching_string_unavailable = 1;
743             }
744             else { # infix operator
745             if (($conf->{tflags}->{$rulename}||'') =~ /\bmultiple\b/) {
746 56         70 $posline = 'pos $hval = 0; $hits = 0;';
  56         86  
747 72         106 $ifwhile = 'while';
748 72         98 $matchg = 'g';
749 72 50       188 if ($conf->{tflags}->{$rulename} =~ /\bmaxhits=(\d+)\b/) {
750             $whlast = 'last if ++$hits >= '.untaint_var($1).';';
751 72         82 }
752 72         78 }
753 72         76 if ($matchg) {
754 72         92 $expr = '$hval '.$op.' /$qrptr->{q{'.$rulename.'}}/go';
755             } else {
756 72         83 $expr = '$hval '.$op.' /$qrptr->{q{'.$rulename.'}}/o';
757 72         74 }
758 72 100       121 }
759 8         20  
760 8         18 $self->add_evalstr($pms, '
761             if ($scoresptr->{q{'.$rulename.'}}) {
762             '.$posline.'
763 64 50 50     252 '.$self->hash_line_for_rule($pms, $rulename).'
764 0         0 '.$ifwhile.' ('.$expr.') {
765 0         0 $self->got_hit(q{'.$rulename.'}, "", ruletype => "header");
766 0         0 '.$self->hit_rule_plugin_code($pms, $rulename, "header", "",
767 0 0       0 $matching_string_unavailable).'
768 0         0 '.$whlast.'
769             }
770             '.$self->ran_rule_plugin_code($rulename, "header").'
771 64 50       98 }
772 0         0 ');
773             }
774 64         125 $self->pop_evalstr_prefix();
775             }
776             }
777             );
778 72         198 }
779              
780             ###########################################################################
781              
782             my ($self, $pms, $priority, $textary) = @_;
783             my $loopid = 0;
784              
785             $self->run_generic_tests ($pms, $priority,
786             consttype => $Mail::SpamAssassin::Conf::TYPE_BODY_TESTS,
787             type => 'body',
788             testhash => $pms->{conf}->{body_tests},
789             args => [ @$textary ],
790             loop_body => sub
791             {
792 56         106 my ($self, $pms, $conf, $rulename, $pat, %opts) = @_;
793             my $sub = '';
794             if (would_log('dbg', 'rules-all') == 2) {
795 258         3684 $sub .= '
796             dbg("rules-all: running body rule %s", q{'.$rulename.'});
797             ';
798             }
799             my $nosubject = ($conf->{tflags}->{$rulename}||'') =~ /\bnosubject\b/;
800             if ($nosubject) {
801 258     258 0 597 $sub .= '
802 258         402 my $nosubj = 1;
803             ';
804             }
805             if (($conf->{tflags}->{$rulename}||'') =~ /\bmultiple\b/)
806             {
807             # support multiple matches
808             $loopid++;
809             my ($max) = ($pms->{conf}->{tflags}->{$rulename}||'') =~ /\bmaxhits=(\d+)\b/;
810             $max = untaint_var($max);
811 28     28   159 $sub .= '
812 28         76 $hits = 0;
813 28 50       97 body_'.$loopid.': foreach my $l (@_) {
814 0         0 ';
815             if ($nosubject) {
816             $sub .= '
817             if ($nosubj) { $nosubj = 0; next; }
818 28   100     153 ';
819 28 50       87 }
820 0         0 $sub .= '
821             pos $l = 0;
822             '.$self->hash_line_for_rule($pms, $rulename).'
823             while ($l =~ /$qrptr->{q{'.$rulename.'}}/go) {
824 28 50 66     135 $self->got_hit(q{'.$rulename.'}, "BODY: ", ruletype => "body");
825             '. $self->hit_rule_plugin_code($pms, $rulename, "body", "") . '
826             '. ($max? 'last body_'.$loopid.' if ++$hits >= '.$max.';' : '') .'
827 0         0 }
828 0   0     0 }
829 0         0 ';
830 0         0 }
831             else {
832             # omitting the "pos" call, "body_loopid" label, use of while()
833             # instead of if() etc., shaves off 8 perl OPs.
834 0 0       0 $sub .= '
835 0         0 foreach my $l (@_) {
836             ';
837             if ($nosubject) {
838             $sub .= '
839 0 0       0 if ($nosubj) { $nosubj = 0; next; }
840             ';
841             }
842             $sub .= '
843             '.$self->hash_line_for_rule($pms, $rulename).'
844             if ($l =~ /$qrptr->{q{'.$rulename.'}}/o) {
845             $self->got_hit(q{'.$rulename.'}, "BODY: ", ruletype => "body");
846             '. $self->hit_rule_plugin_code($pms, $rulename, "body", "last") .'
847             }
848             }
849             ';
850             }
851              
852             $self->add_evalstr($pms, '
853 28         68 if ($scoresptr->{q{'.$rulename.'}}) {
854             '.$sub.'
855             '.$self->ran_rule_plugin_code($rulename, "body").'
856 28 50       64 }
857 0         0 ');
858              
859             return if ($opts{doing_user_rules} &&
860             !$self->is_user_rule_sub($rulename.'_body_test'));
861 28         82 }
862             );
863             }
864              
865             ###########################################################################
866              
867             my ($self, $pms, $priority, @uris) = @_;
868             my $loopid = 0;
869             $self->run_generic_tests ($pms, $priority,
870             consttype => $Mail::SpamAssassin::Conf::TYPE_URI_TESTS,
871 28         126 type => 'uri',
872             testhash => $pms->{conf}->{uri_tests},
873             args => [ @uris ],
874             loop_body => sub
875             {
876             my ($self, $pms, $conf, $rulename, $pat, %opts) = @_;
877             my $sub = '';
878             if (would_log('dbg', 'rules-all') == 2) {
879 28 50 33     137 $sub .= '
880             dbg("rules-all: running uri rule %s", q{'.$rulename.'});
881 258         2834 ';
882             }
883             if (($conf->{tflags}->{$rulename}||'') =~ /\bmultiple\b/) {
884             $loopid++;
885             my ($max) = ($pms->{conf}->{tflags}->{$rulename}||'') =~ /\bmaxhits=(\d+)\b/;
886             $max = untaint_var($max);
887 258     258 0 657 $sub .= '
888 258         378 $hits = 0;
889             uri_'.$loopid.': foreach my $l (@_) {
890             pos $l = 0;
891             '.$self->hash_line_for_rule($pms, $rulename).'
892             while ($l =~ /$qrptr->{q{'.$rulename.'}}/go) {
893             $self->got_hit(q{'.$rulename.'}, "URI: ", ruletype => "uri");
894             '. $self->hit_rule_plugin_code($pms, $rulename, "uri", "") . '
895             '. ($max? 'last uri_'.$loopid.' if ++$hits >= '.$max.';' : '') .'
896 8     8   51 }
897 8         24 }
898 8 50       34 ';
899 0         0 } else {
900             $sub .= '
901             foreach my $l (@_) {
902             '.$self->hash_line_for_rule($pms, $rulename).'
903 8 50 33     67 if ($l =~ /$qrptr->{q{'.$rulename.'}}/o) {
904 0         0 $self->got_hit(q{'.$rulename.'}, "URI: ", ruletype => "uri");
905 0   0     0 '. $self->hit_rule_plugin_code($pms, $rulename, "uri", "last") .'
906 0         0 }
907 0 0       0 }
908             ';
909             }
910              
911             $self->add_evalstr($pms, '
912             if ($scoresptr->{q{'.$rulename.'}}) {
913             '.$sub.'
914             '.$self->ran_rule_plugin_code($rulename, "uri").'
915             }
916             ');
917              
918             return if ($opts{doing_user_rules} &&
919             !$self->is_user_rule_sub($rulename.'_uri_test'));
920 8         35 }
921             );
922             }
923              
924             ###########################################################################
925              
926             my ($self, $pms, $priority, $textary) = @_;
927             my $loopid = 0;
928             $self->run_generic_tests ($pms, $priority,
929             consttype => $Mail::SpamAssassin::Conf::TYPE_RAWBODY_TESTS,
930             type => 'rawbody',
931 8         60 testhash => $pms->{conf}->{rawbody_tests},
932             args => [ @$textary ],
933             loop_body => sub
934             {
935             my ($self, $pms, $conf, $rulename, $pat, %opts) = @_;
936             my $sub = '';
937             if (would_log('dbg', 'rules-all') == 2) {
938             $sub .= '
939 8 50 33     61 dbg("rules-all: running rawbody rule %s", q{'.$rulename.'});
940             ';
941 258         2072 }
942             if (($pms->{conf}->{tflags}->{$rulename}||'') =~ /\bmultiple\b/)
943             {
944             # support multiple matches
945             $loopid++;
946             my ($max) = ($pms->{conf}->{tflags}->{$rulename}||'') =~ /\bmaxhits=(\d+)\b/;
947 258     258 0 527 $max = untaint_var($max);
948 258         348 $sub .= '
949             $hits = 0;
950             rawbody_'.$loopid.': foreach my $l (@_) {
951             pos $l = 0;
952             '.$self->hash_line_for_rule($pms, $rulename).'
953             while ($l =~ /$qrptr->{q{'.$rulename.'}}/go) {
954             $self->got_hit(q{'.$rulename.'}, "RAW: ", ruletype => "rawbody");
955             '. $self->hit_rule_plugin_code($pms, $rulename, "rawbody", "") . '
956 0     0   0 '. ($max? 'last rawbody_'.$loopid.' if ++$hits >= '.$max.';' : '') .'
957 0         0 }
958 0 0       0 }
959 0         0 ';
960             }
961             else {
962             $sub .= '
963 0 0 0     0 foreach my $l (@_) {
964             '.$self->hash_line_for_rule($pms, $rulename).'
965             if ($l =~ /$qrptr->{q{'.$rulename.'}}/o) {
966 0         0 $self->got_hit(q{'.$rulename.'}, "RAW: ", ruletype => "rawbody");
967 0   0     0 '. $self->hit_rule_plugin_code($pms, $rulename, "rawbody", "last") . '
968 0         0 }
969 0 0       0 }
970             ';
971             }
972              
973             $self->add_evalstr($pms, '
974             if ($scoresptr->{q{'.$rulename.'}}) {
975             '.$sub.'
976             '.$self->ran_rule_plugin_code($rulename, "rawbody").'
977             }
978             ');
979              
980             return if ($opts{doing_user_rules} &&
981             !$self->is_user_rule_sub($rulename.'_rawbody_test'));
982             }
983 0         0 );
984             }
985              
986             ###########################################################################
987              
988             my ($self, $pms, $priority, $fullmsgref) = @_;
989             my $loopid = 0;
990             $self->run_generic_tests ($pms, $priority,
991             consttype => $Mail::SpamAssassin::Conf::TYPE_FULL_TESTS,
992             type => 'full',
993             testhash => $pms->{conf}->{full_tests},
994 0         0 args => [ $fullmsgref ],
995             pre_loop_body => sub
996             {
997             my ($self, $pms, $conf, %opts) = @_;
998             $self->push_evalstr_prefix($pms, '
999             my $fullmsgref = shift;
1000             ');
1001             },
1002 0 0 0     0 loop_body => sub
1003             {
1004 258         2372 my ($self, $pms, $conf, $rulename, $pat, %opts) = @_;
1005             my ($max) = ($pms->{conf}->{tflags}->{$rulename}||'') =~ /\bmaxhits=(\d+)\b/;
1006             $max = untaint_var($max);
1007             $max ||= 0;
1008             $self->add_evalstr($pms, '
1009             if ($scoresptr->{q{'.$rulename.'}}) {
1010 258     258 0 564 pos $$fullmsgref = 0;
1011 258         364 '.$self->hash_line_for_rule($pms, $rulename).'
1012             dbg("rules-all: running full rule %s", q{'.$rulename.'});
1013             $hits = 0;
1014             while ($$fullmsgref =~ /$qrptr->{q{'.$rulename.'}}/g) {
1015             $self->got_hit(q{'.$rulename.'}, "FULL: ", ruletype => "full");
1016             '. $self->hit_rule_plugin_code($pms, $rulename, "full", "last") . '
1017             last if ++$hits >= '.$max.';
1018             }
1019 25     25   95 pos $$fullmsgref = 0;
1020 25         72 '.$self->ran_rule_plugin_code($rulename, "full").'
1021             }
1022             ');
1023             }
1024             );
1025             }
1026 0     0   0  
1027 0   0     0 ###########################################################################
1028 0         0  
1029 0   0     0 my ($self, $pms, $priority) = @_;
1030 0         0 return unless (defined($pms->{conf}->{head_evals}->{$priority}));
1031             dbg("rules: running head_eval tests; score so far=".$pms->{score});
1032             $self->run_eval_tests ($pms, $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS,
1033             'head_evals', '', $priority);
1034             }
1035              
1036             my ($self, $pms, $priority, $bodystring) = @_;
1037             return unless (defined($pms->{conf}->{body_evals}->{$priority}));
1038             dbg("rules: running body_eval tests; score so far=".$pms->{score});
1039             $self->run_eval_tests ($pms, $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS,
1040             'body_evals', 'BODY: ', $priority, $bodystring);
1041             }
1042              
1043             my ($self, $pms, $priority, $bodystring) = @_;
1044             return unless (defined($pms->{conf}->{rawbody_evals}->{$priority}));
1045             dbg("rules: running rawbody_eval tests; score so far=".$pms->{score});
1046 258         2210 $self->run_eval_tests ($pms, $Mail::SpamAssassin::Conf::TYPE_RAWBODY_EVALS,
1047             'rawbody_evals', 'RAW: ', $priority, $bodystring);
1048             }
1049              
1050             my ($self, $pms, $priority, $fullmsgref) = @_;
1051             return unless (defined($pms->{conf}->{full_evals}->{$priority}));
1052 258     258 0 595 dbg("rules: running full_eval tests; score so far=".$pms->{score});
1053 258 100       917 $self->run_eval_tests($pms, $Mail::SpamAssassin::Conf::TYPE_FULL_EVALS,
1054 162         941 'full_evals', '', $priority, $fullmsgref);
1055 162         699 }
1056              
1057             my ($self, $pms, $testtype, $evalname, $prepend2desc, $priority, @extraevalargs) = @_;
1058            
1059             my $master_deadline = $pms->{master_deadline};
1060 258     258 0 591 if ($pms->{deadline_exceeded}) {
1061 258 100       810 return;
1062 81         486 } elsif ($master_deadline && time > $master_deadline) {
1063 81         301 info("check: (run_eval) exceeded time limit, skipping further tests");
1064             $pms->{deadline_exceeded} = 1;
1065             return;
1066             } elsif ($self->{main}->call_plugins("have_shortcircuited",
1067             { permsgstatus => $pms })) {
1068 258     258 0 545 return;
1069 258 50       870 }
1070 0         0  
1071 0         0 my $conf = $pms->{conf};
1072             my $doing_user_rules = $conf->{want_rebuild_for_type}->{$testtype};
1073             if ($doing_user_rules) { $self->{done_user_rules}->{$testtype}++; }
1074              
1075             # clean up priority value so it can be used in a subroutine name
1076 258     258 0 621 my $clean_priority;
1077 258 100       867 ($clean_priority = $priority) =~ s/-/neg/;
1078 81         464 my $scoreset = $conf->get_score_set();
1079 81         283 my $package_name = __PACKAGE__;
1080              
1081             my $methodname = '_eval_tests'.
1082             '_type'.$testtype .
1083             '_pri'.$clean_priority .
1084 324     324 0 1133 '_set'.$scoreset;
1085              
1086 324         530 # Some of the rules are scoreset specific, so we need additional
1087 324 50 33     2591 # subroutines to handle those
    50          
    50          
1088 0         0 if (defined &{"${package_name}::${methodname}"}
1089             && !$doing_user_rules)
1090 0         0 {
1091 0         0 my $method = "${package_name}::${methodname}";
1092 0         0 #dbg("rules: run_eval_tests - calling previously compiled %s", $method);
1093             my $t = Mail::SpamAssassin::Timeout->new({ deadline => $master_deadline });
1094             my $err = $t->run(sub {
1095 0         0 no strict "refs";
1096             &{$method}($pms,@extraevalargs);
1097             });
1098 324         760 if ($t->timed_out() && $master_deadline && time > $master_deadline) {
1099 324         572 info("check: exceeded time limit in $method, skipping further tests");
1100 324 50       618 $pms->{deadline_exceeded} = 1;
  0         0  
1101             }
1102             return;
1103 324         369 }
1104 324         701  
1105 324         1042 # look these up once in advance to save repeated lookups in loop below
1106 324         479 my $evalhash = $conf->{$evalname}->{$priority};
1107             my $tflagsref = $conf->{tflags};
1108 324         880 my $scoresref = $conf->{scores};
1109             my $eval_pluginsref = $conf->{eval_plugins};
1110             my $have_start_rules = $self->{main}->have_plugin("start_rules");
1111             my $have_ran_rule = $self->{main}->have_plugin("ran_rule");
1112              
1113             # the buffer for the evaluated code
1114             my $evalstr = '';
1115 324 100 66     462  
  324         2260  
1116             # conditionally include the dbg in the eval str
1117             my $dbgstr = '';
1118 126         285 if (would_log('dbg')) {
1119             $dbgstr = 'dbg("rules: ran eval rule $rulename ======> got hit ($result)");';
1120 126         557 }
1121              
1122 23     23   244 while (my ($rulename, $test) = each %{$evalhash}) {
  23         46  
  23         11720  
1123 126     126   242 if ($tflagsref->{$rulename}) {
  126         3099  
1124 126         720 # If the rule is a net rule, and we are in a non-net scoreset, skip it.
1125 126 0 33     607 if ($tflagsref->{$rulename} =~ /\bnet\b/) {
      33        
1126 0         0 next if (($scoreset & 1) == 0);
1127 0         0 }
1128             # If the rule is a bayes rule, and we are in a non-bayes scoreset, skip it.
1129 126         673 if ($tflagsref->{$rulename} =~ /\blearn\b/) {
1130             next if (($scoreset & 2) == 0);
1131             }
1132             }
1133 198         450  
1134 198         340 # skip if score zeroed
1135 198         303 next if !$scoresref->{$rulename};
1136 198         312
1137 198         624 my $function = untaint_var($test->[0]); # was validated with \w+
1138 198         512 if (!$function) {
1139             warn "rules: error: no eval function defined for $rulename";
1140             next;
1141 198         389 }
1142              
1143             if (!exists $conf->{eval_plugins}->{$function}) {
1144 198         285 warn("rules: error: unknown eval '$function' for $rulename\n");
1145 198 50       581 next;
1146 0         0 }
1147              
1148             $evalstr .= '
1149 198         306 {
  2018         4968  
1150 1820 100       4156 $rulename = q#'.$rulename.'#;
1151             %{$self->{test_log_msgs}} = ();
1152 1297 100       3439 ';
1153 400 100       1037
1154             # only need to set current_rule_name for plugin evals
1155             if ($eval_pluginsref->{$function}) {
1156 907 100       2459 # let plugins get the name of the rule that is currently being run,
1157 78 50       364 # and ensure their eval functions exist
1158             $evalstr .= '
1159             $self->{current_rule_name} = $rulename;
1160             $self->register_plugin_eval_glue(q#'.$function.'#);
1161             ';
1162 1352 50       3061 }
1163              
1164 1352         2815 # this stuff is quite slow, and totally superfluous if
1165 1352 50       2288 # no plugin is loaded for those hooks
1166 0         0 if ($have_start_rules) {
1167 0         0 # XXX - should we use helper function here?
1168             $evalstr .= '
1169             $self->{main}->call_plugins("start_rules", {
1170 1352 50       2870 permsgstatus => $self,
1171 0         0 ruletype => "eval",
1172 0         0 priority => '.$priority.'
1173             });
1174              
1175 1352         2167 ';
1176             }
1177              
1178             $evalstr .= '
1179             eval {
1180             $result = $self->'.$function.'(@extraevalargs, @{$testptr->{q#'.$rulename.'#}->[1]}); 1;
1181             } or do {
1182 1352 50       2409 $result = 0;
1183             die "rules: $@\n" if $@ =~ /__alarm__ignore__/;
1184             $self->handle_eval_rule_errors($rulename);
1185 1352         2035 };
1186             ';
1187              
1188             if ($have_ran_rule) {
1189             # XXX - should we use helper function here?
1190             $evalstr .= '
1191             $self->{main}->call_plugins("ran_rule", {
1192             permsgstatus => $self, ruletype => "eval", rulename => $rulename
1193 1352 50       1924 });
1194             ';
1195 0         0 }
1196              
1197             $evalstr .= '
1198             if ($result) {
1199             $self->got_hit($rulename, $prepend2desc, ruletype => "eval", value => $result);
1200             '.$dbgstr.'
1201             }
1202             }
1203             ';
1204             }
1205 1352         2485  
1206             # don't free the eval ruleset here -- we need it in the compiled code!
1207              
1208             # nothing done in the loop, that means no rules
1209             return unless ($evalstr);
1210            
1211             $evalstr = <<"EOT";
1212             {
1213             package $package_name;
1214              
1215 1352 50       2001 sub ${methodname} {
1216             my (\$self, \@extraevalargs) = \@_;
1217 0         0  
1218             my \$testptr = \$self->{conf}->{$evalname}->{$priority};
1219             my \$prepend2desc = q#$prepend2desc#;
1220             my \$rulename;
1221             my \$result;
1222             $evalstr
1223             }
1224 1352         2834  
1225             1;
1226             }
1227             EOT
1228              
1229             undef &{$methodname};
1230              
1231             dbg("rules: run_eval_tests - compiling eval code: %s, priority %s",
1232             $testtype, $priority);
1233             # dbg("rules: eval code(3): %s", $evalstr);
1234             my $eval_result;
1235             { my $timer = $self->{main}->time_method('compile_eval');
1236 198 100       540 $eval_result = eval($evalstr);
1237             }
1238 121         862 if (!$eval_result) {
1239             my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat;
1240             warn "rules: failed to compile eval tests, skipping some: $eval_stat\n";
1241             $self->{rule_errors}++;
1242             }
1243             else {
1244             my $method = "${package_name}::${methodname}";
1245             push (@TEMPORARY_METHODS, $methodname);
1246             # dbg("rules: run_eval_tests - calling the just compiled %s", $method);
1247             my $t = Mail::SpamAssassin::Timeout->new({ deadline => $master_deadline });
1248             my $err = $t->run(sub {
1249             no strict "refs";
1250             &{$method}($pms,@extraevalargs);
1251             });
1252             if ($t->timed_out() && $master_deadline && time > $master_deadline) {
1253             info("check: exceeded time limit in $method, skipping further tests");
1254             $pms->{deadline_exceeded} = 1;
1255             }
1256 121         200 }
  121         451  
1257             }
1258 121         409  
1259             ###########################################################################
1260             # Helper Functions
1261 121         173  
1262 121         159 my ($self, $pms, $rulename) = @_;
  121         499  
1263 121 0   16   119852 # using tainted subr. argument may taint the whole expression, avoid
  25 50   25   67  
  25 100   12   79  
  25 50       98  
  12 50       24  
  12 100       19  
  12 50       20  
  12 50       23  
  30 100       63  
  30 0       50  
  30 50       114  
  12 50       32  
  30 0       76  
  25 50       243  
  25 50       74  
  13 0       21  
  1 50       2  
  12 50       18  
  25 0       57  
  13 50       24  
  12 50       20  
  25 0       38  
  25 50       39  
  12 100       28  
  25 0       40  
  25 50       90  
  16 50       35  
  25 0       71  
  25 50       203  
  25 50       93  
  0 0       0  
  18 50       35  
  18 50       53  
  30 0       90  
  18 50       62  
  12 50       26  
  12 0       29  
  12 50       21  
  12 50       30  
  12 0       24  
  12 50       41  
  12 50       28  
  12 0       30  
  12 50       180  
  12 100       45  
  0 0       0  
  0 50       0  
  0 50       0  
  12 0       189  
  0 50       0  
  12 50       43  
  12 0       41  
  12 50       24  
  12 50       26  
  12 0       27  
  12 50       26  
  12 50       24  
  12 0       33  
  12 50       35  
  12 50       66  
  12 0       27  
  12 50       25  
  12 50       204  
  12 0       51  
  0 50       0  
  0 50       0  
  0 0       0  
  12 50       47  
  0 50       0  
  12 0       23  
  12 50       25  
  12 50       27  
  12 0       25  
  12 50       24  
  12 50       53  
  12 0       31  
  12 50       27  
  12 50       193  
  12 0       47  
  0 50       0  
  0 50       0  
  0 0       0  
  12 50       36  
  0 50       0  
  12 0       18  
  12 50       24  
  12 50       22  
  12 0       40  
  12 50       35  
  12 50       37  
  12 0       35  
  12 50       23  
  12 50       186  
  12 0       54  
  0 50       0  
  0 50       0  
  0 0       0  
  12 50       44  
  0 50       0  
  12         24  
  12         25  
  12         27  
  12         28  
  12         25  
  12         50  
  12         26  
  12         21  
  12         201  
  12         48  
  0         0  
  0         0  
  0         0  
  12         36  
  0         0  
  12         19  
  12         22  
  12         20  
  12         27  
  12         30  
  12         44  
  12         33  
  12         26  
  12         188  
  12         53  
  0         0  
  0         0  
  0         0  
  12         38  
  5         30  
  12         25  
  12         26  
  12         29  
  12         26  
  12         28  
  12         70  
  12         24  
  12         26  
  12         187  
  12         173  
  0         0  
  0         0  
  0         0  
  12         43  
  0         0  
  12         20  
  12         30  
  12         20  
  12         32  
  12         21  
  12         42  
  12         28  
  12         24  
  12         176  
  12         54  
  0         0  
  0         0  
  0         0  
  12         49  
  0         0  
  12         20  
  12         26  
  12         18  
  12         50  
  12         24  
  12         46  
  12         24  
  12         30  
  12         189  
  12         57  
  0         0  
  0         0  
  0         0  
  12         47  
  0         0  
  12         24  
  12         26  
  12         28  
  12         28  
  12         29  
  12         132  
  12         33  
  12         24  
  12         191  
  12         73  
  0         0  
  0         0  
  0         0  
  12         38  
  0         0  
  12         25  
  12         23  
  12         26  
  12         30  
  12         25  
  12         42  
  12         40  
  12         25  
  12         188  
  12         59  
  0         0  
  0         0  
  0         0  
  12         40  
  0         0  
  12         24  
  12         26  
  12         22  
  12         25  
  12         27  
  12         40  
  12         28  
  12         28  
  12         184  
  12         43  
  0         0  
  0         0  
  0         0  
  12         37  
  0         0  
  12         24  
  12         22  
  12         22  
  12         31  
  12         23  
  12         38  
  12         27  
  12         27  
  12         189  
  12         46  
  0         0  
  0         0  
  0         0  
  12         40  
  1         7  
  12         20  
  12         26  
  12         20  
  12         28  
  12         36  
  12         46  
  12         29  
  12         24  
  12         179  
  12         47  
  0         0  
  0         0  
  0         0  
  12         42  
  0         0  
  12         20  
  12         27  
  12         21  
  12         24  
  12         25  
  12         41  
  12         27  
  12         54  
  12         186  
  12         102  
  0         0  
  0         0  
  0         0  
  12         41  
  0         0  
  12         35  
  12         34  
  12         20  
  12         27  
  12         23  
  12         47  
  12         27  
  12         26  
  12         176  
  12         49  
  0         0  
  0         0  
  0         0  
  12         41  
  0         0  
  12         17  
  12         36  
  12         26  
  12         26  
  12         25  
  12         40  
  12         27  
  12         23  
  12         174  
  12         55  
  0         0  
  0         0  
  0         0  
  12         43  
  0         0  
  12         22  
  12         21  
  12         25  
  12         25  
  12         28  
  12         40  
  12         29  
  12         25  
  12         176  
  12         47  
  0         0  
  0         0  
  0         0  
  12         110  
  0         0  
  12         23  
  12         23  
  12         22  
  12         32  
  12         24  
  12         41  
  12         27  
  12         22  
  12         196  
  12         62  
  0         0  
  0         0  
  0         0  
  12         38  
  0         0  
  12         20  
  12         22  
  12         22  
  12         26  
  12         23  
  12         43  
  12         28  
  12         27  
  12         175  
  12         47  
  0         0  
  0         0  
  0         0  
  12         48  
  0         0  
  12         26  
  12         27  
  12         24  
  12         27  
  12         24  
  12         42  
  12         24  
  12         29  
  12         186  
  12         48  
  0         0  
  0         0  
  0         0  
  12         58  
  0         0  
  12         24  
  12         22  
  12         21  
  12         28  
  12         21  
  12         39  
  12         77  
  12         25  
  12         215  
  12         48  
  0         0  
  0         0  
  0         0  
  12         39  
  0         0  
  12         22  
  12         23  
  12         22  
  12         30  
  12         26  
  12         44  
  12         25  
  12         24  
  12         191  
  12         60  
  0         0  
  0         0  
  0         0  
  12         55  
  0         0  
  12         20  
  12         31  
  12         20  
  12         27  
  12         27  
  12         50  
  12         25  
  12         27  
  12         183  
  12         56  
  0         0  
  0         0  
  0         0  
  12         40  
  0         0  
  12         22  
  12         24  
  12         26  
  12         22  
  12         26  
  12         39  
  12         28  
  12         26  
  12         196  
  12         48  
  0         0  
  0         0  
  0         0  
  12         51  
  0         0  
  12         33  
  12         28  
  12         26  
  12         27  
  12         31  
  12         42  
  12         33  
  12         36  
  12         180  
  12         50  
  0         0  
  0         0  
  0         0  
  12         38  
  0         0  
  12         24  
  12         25  
  12         23  
  12         25  
  12         26  
  12         47  
  12         31  
  12         28  
  12         195  
  12         46  
  0         0  
  0         0  
  0         0  
  12         38  
  0         0  
  12         17  
  12         32  
  12         22  
  12         27  
  12         27  
  12         47  
  12         36  
  12         27  
  12         173  
  12         44  
  0         0  
  0         0  
  0         0  
  12         68  
  0         0  
  12         23  
  12         25  
  12         23  
  12         27  
  12         27  
  12         52  
  12         27  
  12         26  
  12         196  
  12         63  
  0         0  
  0         0  
  0         0  
  12         41  
  0         0  
  12         17  
  12         32  
  12         24  
  12         28  
  12         30  
  12         44  
  12         28  
  12         26  
  12         186  
  12         44  
  0         0  
  0         0  
  0         0  
  12         277  
  0         0  
  12         44  
  12         39  
  12         22  
  12         26  
  12         25  
  12         23  
  12         20  
  12         29  
  12         28  
  12         65  
  12         26  
  12         41  
  12         194  
  12         49  
  0         0  
  0         0  
  0         0  
  12         191  
  0         0  
1264             my $u = untaint_var($pms->{conf}->{source_file}->{$rulename});
1265 121 50       516 return sprintf("\n#line 1 \"%s, rule %s,\"", $u, $rulename);
1266 0 0       0 # return sprintf("\n#line 1 \"%s, rule %s,\"", $u, $rulename) .
  0         0  
1267 0         0 # "\ndbg(\"rules: will run %s\", q(".$rulename."));\n";
1268 0         0 }
1269              
1270             my ($self, $subname) = @_;
1271 121         326 my $package_name = __PACKAGE__;
1272 121         294 return 0 if (eval 'defined &'.$package_name.'::'.$subname);
1273             1;
1274 121         712 }
1275              
1276 23     23   183 my ($self, $ruletype, $pri) = @_;
  23         68  
  23         14496  
1277 121     135   298  
  121         3040  
1278 121         837 my $evalstr = '';
1279 121 0 33     615 if ($self->{main}->have_plugin("start_rules")) {
      33        
1280 0         0 $evalstr .= '
1281 0         0  
1282             $self->{main}->call_plugins ("start_rules", { permsgstatus => $self,
1283             ruletype => \''.$ruletype.'\',
1284             priority => '.$pri.' });
1285              
1286             ';
1287             }
1288              
1289             return $evalstr;
1290 95     108 0 159 }
1291              
1292 95         283 my ($self, $pms, $rulename, $ruletype, $loop_break_directive,
1293 95         543 $matching_string_unavailable) = @_;
1294              
1295             # note: keep this in 'single quotes' to avoid the $ & performance hit,
1296             # unless specifically requested by the caller. Also split the
1297             # two chars, just to be paranoid and ensure that a buggy perl interp
1298             # doesn't impose that hit anyway (just in case)
1299 0     13 0 0 my $match;
1300 0         0 if ($matching_string_unavailable) {
1301 0 0       0 $match = '"<YES>"'; # nothing better to report, $& is not set by this rule
1302 0         0 } else {
1303             # simple, but suffers from 'user data interpreted as a boolean', Bug 6360
1304             $match = '(defined $'.'& ? $'.'& : "negative match")';
1305             }
1306 132     150 0 216  
1307             my $debug_code = '';
1308 132         224 if (exists($pms->{should_log_rule_hits})) {
1309 132 50       353 $debug_code = '
1310 0         0 dbg("rules: ran '.$ruletype.' rule '.$rulename.' ======> got hit: \"" . '.
1311             $match.' . "\"");
1312             ';
1313             }
1314              
1315             my $save_hits_code = '';
1316             if ($pms->{save_pattern_hits}) {
1317             $save_hits_code = '
1318             $self->{pattern_hits}->{q{'.$rulename.'}} = '.$match.';
1319 132         364 ';
1320             }
1321              
1322             # if we're not running "tflags multiple", break out of the matching
1323 95     95 0 200 # loop this way
1324             my $multiple_code = '';
1325             if ($loop_break_directive &&
1326             ($pms->{conf}->{tflags}->{$rulename}||'') !~ /\bmultiple\b/) {
1327             $multiple_code = $loop_break_directive.';';
1328             }
1329              
1330 95         106 return $debug_code.$save_hits_code.$multiple_code;
1331 95 100       152 }
1332 7         16  
1333             my ($self, $rulename, $ruletype) = @_;
1334              
1335 88         123 return '' unless $self->{main}->have_plugin("ran_rule");
1336              
1337             # The $self here looks odd, but since we are inserting this into eval'd code it
1338 95         111 # needs to be $self which in that case is actually the PerMsgStatus object
1339 95 50       185 return '
1340 0         0 $self->{main}->call_plugins ("ran_rule", { permsgstatus => $self, rulename => \''.$rulename.'\', ruletype => \''.$ruletype.'\' });
1341             ';
1342             }
1343              
1344             my ($self, $pms, $type, $pri) = @_;
1345              
1346 95         117 # we can't do this, if we may need to recompile them again later
1347 95 50       185 return if $pms->{conf}->{allow_user_rules};
1348 0         0  
1349             # remove now-compiled rulesets
1350             if (exists $pms->{conf}->{$type.'_tests'}->{$pri}) {
1351             delete $pms->{conf}->{$type.'_tests'}->{$pri};
1352             }
1353             }
1354              
1355 95         111 ###########################################################################
1356 95 100 100     395  
      66        
1357             1;