sub check_main()

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;
}