in lib/Mail/SpamAssassin/Plugin/Check.pm [51:268]
sub check_main {
my ($self, $args) = @_;
my $pms = $args->{permsgstatus};
my $conf = $pms->{conf};
$would_log_rules_all = would_log('dbg', 'rules-all') == 2;
# Make AsyncLoop wait launch_queue() for launching queries
$pms->{async}->start_queue();
# initialize meta stuff
$pms->{meta_pending} = {};
foreach my $rulename (keys %{$conf->{meta_tests}}) {
$pms->{meta_pending}->{$rulename} = 1 if $conf->{scores}->{$rulename};
}
# metas without dependencies are ready to be run
foreach my $rulename (keys %{$conf->{meta_nodeps}}) {
$pms->{meta_check_ready}->{$rulename} = 1;
}
# rule_hits API implemented in 3.3.0
my $suppl_attrib = $pms->{msg}->{suppl_attrib};
if (ref $suppl_attrib && ref $suppl_attrib->{rule_hits}) {
my @caller_rule_hits = @{$suppl_attrib->{rule_hits}};
dbg("check: adding caller rule hits, %d rules", scalar(@caller_rule_hits));
for my $caller_rule_hit (@caller_rule_hits) {
next if ref $caller_rule_hit ne 'HASH';
my($rulename, $area, $score, $defscore, $value,
$ruletype, $tflags, $description) =
@$caller_rule_hit{qw(rule area score defscore value
ruletype tflags descr)};
dbg("rules: ran rule_hits rule $rulename ======> got hit (%s)",
defined $value ? $value : '1');
$pms->got_hit($rulename, $area,
!defined $score ? () : (score => $score),
!defined $defscore ? () : (defscore => $defscore),
!defined $value ? () : (value => $value),
!defined $tflags ? () : (tflags => $tflags),
!defined $description ? () : (description => $description),
ruletype => $ruletype);
delete $pms->{meta_pending}->{$rulename};
delete $pms->{meta_check_ready}->{$rulename};
}
}
# bug 4353:
# Do this before the RBL tests are kicked off. The metadata parsing
# will figure out the (un)trusted relays and such, which are used in the
# rbl calls.
$pms->extract_message_metadata();
my $do_dns = $pms->is_dns_available();
my $rbls_running = 0;
my $decoded = $pms->get_decoded_stripped_body_text_array();
my $bodytext = $pms->get_decoded_body_text_array();
my $fulltext = $pms->{msg}->get_pristine();
my $master_deadline = $pms->{master_deadline};
dbg("check: check_main, time limit in %.3f s",
$master_deadline - time) if $master_deadline;
# Make sure priority -100 exists for launching DNS
$conf->{priorities}->{-100} ||= 1 if $do_dns;
my @priorities = sort { $a <=> $b } keys %{$conf->{priorities}};
foreach my $priority (@priorities) {
# no need to run if there are no priorities at this level. This can
# happen in Conf.pm when we switch a rule from one priority to another
next unless ($conf->{priorities}->{$priority} > 0);
if ($pms->{deadline_exceeded}) {
last;
} elsif ($master_deadline && time > $master_deadline) {
info("check: exceeded time limit, skipping further tests");
$pms->{deadline_exceeded} = 1;
last;
} elsif ($self->{main}->call_plugins("have_shortcircuited",
{ permsgstatus => $pms })) {
# if shortcircuiting is hit, we skip all other priorities...
$pms->{shortcircuited} = 1;
last;
}
my $timer = $self->{main}->time_method("tests_pri_".$priority);
dbg("check: running tests for priority: $priority");
# Here, we launch all the DNS RBL queries and let them run while we
# inspect the message. We try to launch all DNS queries at priority
# -100, so one can shortcircuit tests at lower priority and not launch
# unneeded DNS queries.
if ($do_dns && !$rbls_running && $priority >= -100) {
$rbls_running = 1;
$pms->{async}->launch_queue(); # check if something was queued
$self->run_rbl_eval_tests($pms);
$self->{main}->call_plugins ("check_dnsbl", { permsgstatus => $pms });
}
$pms->harvest_completed_queries() if $rbls_running;
# allow other, plugin-defined rule types to be called here
$self->{main}->call_plugins ("check_rules_at_priority",
{ permsgstatus => $pms, priority => $priority, checkobj => $self });
# do head tests
$self->do_head_tests($pms, $priority);
$pms->harvest_completed_queries() if $rbls_running;
last if $pms->{deadline_exceeded} || $pms->{shortcircuited};
$self->do_head_eval_tests($pms, $priority);
$pms->harvest_completed_queries() if $rbls_running;
last if $pms->{deadline_exceeded} || $pms->{shortcircuited};
$self->do_body_tests($pms, $priority, $decoded);
$pms->harvest_completed_queries() if $rbls_running;
last if $pms->{deadline_exceeded} || $pms->{shortcircuited};
$self->do_uri_tests($pms, $priority, $pms->get_uri_list());
$pms->harvest_completed_queries() if $rbls_running;
last if $pms->{deadline_exceeded} || $pms->{shortcircuited};
$self->do_body_eval_tests($pms, $priority, $decoded);
$pms->harvest_completed_queries() if $rbls_running;
last if $pms->{deadline_exceeded} || $pms->{shortcircuited};
$self->do_rawbody_tests($pms, $priority, $bodytext);
$pms->harvest_completed_queries() if $rbls_running;
last if $pms->{deadline_exceeded} || $pms->{shortcircuited};
$self->do_rawbody_eval_tests($pms, $priority, $bodytext);
$pms->harvest_completed_queries() if $rbls_running;
last if $pms->{deadline_exceeded} || $pms->{shortcircuited};
$self->do_full_tests($pms, $priority, \$fulltext);
$pms->harvest_completed_queries() if $rbls_running;
last if $pms->{deadline_exceeded} || $pms->{shortcircuited};
$self->do_full_eval_tests($pms, $priority, \$fulltext);
$pms->harvest_completed_queries() if $rbls_running;
last if $pms->{deadline_exceeded} || $pms->{shortcircuited};
# we may need to call this more often than once through the loop, but
# it needs to be done at least once, either at the beginning or the end.
$self->{main}->call_plugins ("check_tick", { permsgstatus => $pms });
$pms->harvest_completed_queries() if $rbls_running;
# check for ready metas
$self->do_meta_tests($pms, $priority);
}
# Finish DNS results
if ($do_dns) {
$pms->harvest_dnsbl_queries();
$pms->rbl_finish();
$self->{main}->call_plugins ("check_post_dnsbl", { permsgstatus => $pms });
$pms->{resolver}->finish_socket() if $pms->{resolver};
}
if ($pms->{deadline_exceeded}) {
$pms->got_hit('TIME_LIMIT_EXCEEDED', '', defscore => 0.001,
description => 'Exceeded time limit / deadline');
}
# finished running rules
delete $pms->{current_rule_name};
undef $decoded;
undef $bodytext;
undef $fulltext;
# last chance to handle left callbacks, make rule hits etc
$self->{main}->call_plugins ("check_cleanup", { permsgstatus => $pms });
# final check for ready metas
$self->do_meta_tests($pms, undef, 1);
# check dns_block_rule (bug 6728)
# TODO No idea yet what would be the most logical place to do all these..
if ($conf->{dns_block_rule}) {
foreach my $rule (keys %{$conf->{dns_block_rule}}) {
next if !$pms->{tests_already_hit}->{$rule}; # hit?
foreach my $domain (keys %{$conf->{dns_block_rule}{$rule}}) {
my $blockfile = $self->{main}->sed_path("__global_state_dir__/dnsblock_$domain");
next if -f $blockfile; # no need to warn and create again..
warn "check: dns_block_rule $rule hit, creating $blockfile ".
"(This means DNSBL blocked you due to too many queries. ".
"Set all affected rules score to 0, or use ".
"\"dns_query_restriction deny $domain\" to disable queries)\n";
Mail::SpamAssassin::Util::touch_file($blockfile, { create_exclusive => 1 });
}
}
}
# PMS cleanup will write reports etc, all rule hits must be registered by now
$pms->check_cleanup();
if ($pms->{deadline_exceeded}) {
# dbg("check: exceeded time limit, skipping auto-learning");
} elsif ($master_deadline && time > $master_deadline) {
info("check: exceeded time limit, skipping auto-learning");
$pms->{deadline_exceeded} = 1;
} else {
# auto-learning
$pms->learn();
$self->{main}->call_plugins ("check_post_learn", { permsgstatus => $pms });
}
# track user_rules recompilations; each scanned message is 1 tick on this counter
if ($self->{done_user_rules}) {
my $counters = $conf->{want_rebuild_for_type};
foreach my $type (keys %{$self->{done_user_rules}}) {
if ($counters->{$type} > 0) {
$counters->{$type}--;
}
dbg("rules: user rules done; ticking want_rebuild counter for type $type to ".
$counters->{$type});
}
}
return 1;
}