You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by mm...@apache.org on 2009/11/19 01:51:10 UTC

svn commit: r882020 - in /spamassassin/trunk/lib/Mail: ./ SpamAssassin/ SpamAssassin/Plugin/

Author: mmartinec
Date: Thu Nov 19 00:51:09 2009
New Revision: 882020

URL: http://svn.apache.org/viewvc?rev=882020&view=rev
Log:
Bug 6238: introducing the 'time_limit' configuration option,
with associated code changes in various places

Modified:
    spamassassin/trunk/lib/Mail/SpamAssassin.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/AsyncLoop.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgLearner.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ASN.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Check.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DCC.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DKIM.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Pyzor.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Razor2.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/SPF.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Timeout.pm

Modified: spamassassin/trunk/lib/Mail/SpamAssassin.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin.pm Thu Nov 19 00:51:09 2009
@@ -485,8 +485,9 @@
 
 sub parse {
   my($self, $message, $parsenow, $suppl_attrib) = @_;
-  $self->init(1);
 
+  my $start_time = time;
+  $self->init(1);
   my $timer = $self->time_method("parse");
 
   my $msg = Mail::SpamAssassin::Message->new({
@@ -494,6 +495,15 @@
     normalize=>$self->{conf}->{normalize_charset},
     suppl_attrib=>$suppl_attrib });
 
+  if (ref $suppl_attrib && exists $suppl_attrib->{master_deadline}) {
+    $msg->{master_deadline} = $suppl_attrib->{master_deadline};  # may be undef
+  } elsif ($self->{conf}->{time_limit}) {  # defined and nonzero
+    $msg->{master_deadline} = $start_time + $self->{conf}->{time_limit};
+  }
+  if (defined $msg->{master_deadline}) {
+    dbg("config: time limit %.1f s", $msg->{master_deadline} - $start_time);
+  }
+
   # bug 5069: The goal here is to get rendering plugins to do things
   # like OCR, convert doc and pdf to text, etc, though it could be anything
   # that wants to process the message after it's been parsed.
@@ -524,10 +534,10 @@
   my ($self, $mail_obj) = @_;
 
   $self->init(1);
-  my $msg = Mail::SpamAssassin::PerMsgStatus->new($self, $mail_obj);
-  $msg->check();
+  my $pms = Mail::SpamAssassin::PerMsgStatus->new($self, $mail_obj);
+  $pms->check();
   dbg("timing: " . $self->timer_report())  if $self->{timer_enabled};
-  $msg;
+  $pms;
 }
 
 =item $status = $f->check_message_text ($mailtext)
@@ -1350,7 +1360,7 @@
     "Message-Id:  <"....@spamassassin_spamd_init>\n", "\n",
     "I need to make this message body somewhat long so TextCat preloads\n"x20);
 
-  my $mail = $self->parse(\@testmsg, 1);
+  my $mail = $self->parse(\@testmsg, 1, { master_deadline => undef });
   my $status = Mail::SpamAssassin::PerMsgStatus->new($self, $mail,
                         { disable_auto_learning => 1 } );
 

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/AsyncLoop.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/AsyncLoop.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/AsyncLoop.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/AsyncLoop.pm Thu Nov 19 00:51:09 2009
@@ -168,7 +168,7 @@
 =cut
 
 sub start_lookup {
-  my ($self, $ent) = @_;
+  my ($self, $ent, $master_deadline) = @_;
 
   die "oops, no id"   unless $ent->{id}   ne '';
   die "oops, no key"  unless $ent->{key}  ne '';
@@ -210,8 +210,17 @@
   $t_end = $settings->{rbl_timeout_min}  if $settings && !defined $t_end;
   $t_end = 0.2 * $t_init  if !defined $t_end;
   $t_end = 0  if $t_end < 0;  # just in case
-
   $t_init = $t_end  if $t_init < $t_end;
+
+  my $clipped_by_master_deadline = 0;
+  if (defined $master_deadline) {
+    my $time_avail = $master_deadline - time;
+    $time_avail = 0.5  if $time_avail < 0.5;  # give some slack
+    if ($t_init > $time_avail) {
+      $t_init = $time_avail; $clipped_by_master_deadline = 1;
+      $t_end  = $time_avail  if $t_end > $time_avail;
+    }
+  }
   $ent->{timeout_initial} = $t_init;
   $ent->{timeout_min} = $t_end;
 
@@ -224,8 +233,9 @@
   $self->{total_queries_started}++;
   $self->{pending_lookups}->{$key} = $ent;
 
-  dbg("async: starting: %s (timeout %.1fs, min %.1fs)",
-      $ent->{display_id}, $ent->{timeout_initial}, $ent->{timeout_min});
+  dbg("async: starting: %s (timeout %.1fs, min %.1fs)%s",
+      $ent->{display_id}, $ent->{timeout_initial}, $ent->{timeout_min},
+      !$clipped_by_master_deadline ? '' : ', capped by time limit');
   $ent;
 }
 

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm Thu Nov 19 00:51:09 2009
@@ -1573,6 +1573,61 @@
 
 =over 4
 
+=item time_limit n   (default: 0, i.e. unlimited)
+
+Specifies a limit on elapsed time in seconds that SpamAssassin is allowed
+to spend before providing a result. The value may be fractional and must not
+be negative, zero is interpreted as unlimited and is a default.
+
+This is a best-effort advisory setting, processing will not be abruptly
+aborted at an arbitrary point in processing when the time limit is exceeded,
+but only on reaching one of locations in the program flow equipped with a
+time test. Currently equipped with the test are the main checking loop,
+asynchronous DNS lookups, plugins which are calling external programs.
+Rule evaluation is guarded by starting a timer (alarm) on each set of
+compiled rules.
+
+When a message is passed to Mail::SpamAssassin::parse, a deadline time
+is established as a sum of current time and the C<time_limit> setting.
+This deadline may be overruled by a caller through option 'master_deadline'
+in $suppl_attrib on a call to parse(), possibly providing a more accurate
+deadline taking into account past and expected future processing of a
+message in a mail filtering setup.
+
+When a time limit is exceeded, most of the remaining tests will be skipped,
+as well as auto-learning. Whatever tests fired so far will determine the
+final score. The behaviour is similar to short-circuiting with attribute 'on',
+as implemented by a Shortcircuit plugin. A synthetic hit on a rule named
+TIME_LIMIT_EXCEEDED with a near-zero score is generated, so that the report
+will reflect the event.
+
+The C<time_limit> option is a useful protection against excessive processing
+time on certain degenerate or unusually long or complex mail messages, as well
+as against some DoS attacks. It is also needed in time-critical pre-queue
+filtering setups (e.g. milter, proxy, integration with MTA), where message
+processing must finish before a SMTP client times out.  RFC 5321 prescribes
+in section 4.5.3.2.6 the 'DATA Termination' time limit of 10 minutes,
+although it is not unusual to see some SMTP clients abort sooner on waiting
+for a response. A sensible C<time_limit> for a pre-queue filtering setup is
+maybe 50 seconds, assuming that clients are willing to wait at least a minute.
+
+=cut
+
+  push (@cmds, {
+    setting => 'time_limit',
+    default => 0,
+    type => $CONF_TYPE_NUMERIC,
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      if ($value !~ /^\d+(?:\.\d*)?$/) { return $INVALID_VALUE }
+      $value = 0+$value;
+      if ($value < 0) { return $INVALID_VALUE }
+      $self->{time_limit} = $value;
+    }
+  });
+
+=over 4
+
 =item lock_method type
 
 Select the file-locking method used to protect database files on-disk. By
@@ -2549,7 +2604,7 @@
 
 All DNS queries are made at the beginning of a check and we try to read
 the results at the end.  This value specifies the maximum period of time
-(in seconds) to wait for an DNS query.  If most of the DNS queries have
+(in seconds) to wait for a DNS query.  If most of the DNS queries have
 succeeded for a particular message, then SpamAssassin will not wait for
 the full period to avoid wasting time on unresponsive server(s), but will
 shrink the timeout according to a percentage of queries already completed.

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Dns.pm Thu Nov 19 00:51:09 2009
@@ -124,7 +124,8 @@
       });
 
     $ent->{id} = $id;     # tie up the loose end
-    $existing = $self->{async}->start_lookup($ent);
+    $existing =
+      $self->{async}->start_lookup($ent, $self->{master_deadline});
   }
 
   # always add set
@@ -172,7 +173,7 @@
     });
 
   $ent->{id} = $id;     # tie up the loose end
-  $self->{async}->start_lookup($ent);
+  $self->{async}->start_lookup($ent, $self->{master_deadline});
 }
 
 ###########################################################################

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgLearner.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgLearner.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgLearner.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgLearner.pm Thu Nov 19 00:51:09 2009
@@ -73,6 +73,7 @@
     'main'              => $main,
     'msg'               => $msg,
     'learned'		=> 0,
+    'master_deadline'   => $msg->{master_deadline},
   };
 
   $self->{conf} = $self->{main}->{conf};

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm Thu Nov 19 00:51:09 2009
@@ -53,6 +53,8 @@
 use warnings;
 use re 'taint';
 
+use Time::HiRes qw(time);
+
 use Mail::SpamAssassin::Constants qw(:sa);
 use Mail::SpamAssassin::AsyncLoop;
 use Mail::SpamAssassin::Conf;
@@ -92,7 +94,9 @@
     'disable_auto_learning' => 0,
     'auto_learn_status' => undef,
     'conf'              => $main->{conf},
-    'async'             => Mail::SpamAssassin::AsyncLoop->new($main)
+    'async'             => Mail::SpamAssassin::AsyncLoop->new($main),
+    'master_deadline'   => $msg->{master_deadline},  # typically just inherited
+    'deadline_exceeded' => 0,  # time limit exceeded, skipping further tests
   };
   #$self->{main}->{use_rule_subs} = 1;
 
@@ -2200,7 +2204,6 @@
 to be appended to an existing list of flags in $self->{conf}->{tflags},
 such as: "nice noautolearn multiple". No syntax checks are performed.
 
-
 =item description => $string
 
 Optional: a custom rule description string.  This is used in the

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ASN.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ASN.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ASN.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/ASN.pm Thu Nov 19 00:51:09 2009
@@ -225,7 +225,7 @@
       key=>$key, id=>$id, type=>'TXT',
       zone => $zone,  # serves to fetch other per-zone settings
     };
-    $scanner->{async}->start_lookup($ent);
+    $scanner->{async}->start_lookup($ent, $scanner->{master_deadline});
     dbg("asn: launched DNS TXT query for %s.%s in background",
         $reversed_ip_quad, $entry->{zone});
 

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Check.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Check.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Check.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Check.pm Thu Nov 19 00:51:09 2009
@@ -18,9 +18,12 @@
 use warnings;
 use re 'taint';
 
+use Time::HiRes qw(time);
+
 use Mail::SpamAssassin::Plugin;
 use Mail::SpamAssassin::Logger;
 use Mail::SpamAssassin::Util qw(untaint_var);
+use Mail::SpamAssassin::Timeout;
 use Mail::SpamAssassin::Constants qw(:sa);
 
 use vars qw(@ISA @TEMPORARY_METHODS);
@@ -49,7 +52,7 @@
   my $pms = $args->{permsgstatus};
 
   my $suppl_attrib = $pms->{msg}->{suppl_attrib};
-  if (defined $suppl_attrib && ref $suppl_attrib->{rule_hits}) {
+  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) {
@@ -79,6 +82,7 @@
   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};
 
   my @uris = $pms->get_uri_list();
 
@@ -87,11 +91,19 @@
     # happen in Conf.pm when we switch a rule from one priority to another
     next unless ($pms->{conf}->{priorities}->{$priority} > 0);
 
-    my $timer = $self->{main}->time_method("tests_pri_".$priority);
-
-    # if shortcircuiting is hit, we skip all other priorities...
-    last if $self->{main}->call_plugins("have_shortcircuited", { permsgstatus => $pms });
+    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...
+      last;
+    }
 
+    my $timer = $self->{main}->time_method("tests_pri_".$priority);
     dbg("check: running tests for priority: $priority");
 
     # only harvest the dnsbl queries once priority HARVEST_DNSBL_PRIORITY
@@ -122,33 +134,49 @@
     # do head tests
     $self->do_head_tests($pms, $priority);
     $pms->harvest_completed_queries();
+    last if $pms->{deadline_exceeded};
+
     $self->do_head_eval_tests($pms, $priority);
     $pms->harvest_completed_queries();
+    last if $pms->{deadline_exceeded};
 
     $self->do_body_tests($pms, $priority, $decoded);
     $pms->harvest_completed_queries();
+    last if $pms->{deadline_exceeded};
+
     $self->do_uri_tests($pms, $priority, @uris);
     $pms->harvest_completed_queries();
+    last if $pms->{deadline_exceeded};
+
     $self->do_body_eval_tests($pms, $priority, $decoded);
     $pms->harvest_completed_queries();
+    last if $pms->{deadline_exceeded};
   
     $self->do_rawbody_tests($pms, $priority, $bodytext);
     $pms->harvest_completed_queries();
+    last if $pms->{deadline_exceeded};
+
     $self->do_rawbody_eval_tests($pms, $priority, $bodytext);
     $pms->harvest_completed_queries();
+    last if $pms->{deadline_exceeded};
   
     $self->do_full_tests($pms, $priority, \$fulltext);
     $pms->harvest_completed_queries();
+    last if $pms->{deadline_exceeded};
+
     $self->do_full_eval_tests($pms, $priority, \$fulltext);
     $pms->harvest_completed_queries();
+    last if $pms->{deadline_exceeded};
 
     $self->do_meta_tests($pms, $priority);
     $pms->harvest_completed_queries();
+    last if $pms->{deadline_exceeded};
 
     # 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();
+    last if $pms->{deadline_exceeded};
   }
 
   # sanity check, it is possible that no rules >= HARVEST_DNSBL_PRIORITY ran so the harvest
@@ -167,15 +195,27 @@
     $pms->{resolver}->finish_socket() if $pms->{resolver};
   }
 
+  if ($pms->{deadline_exceeded}) {
+    $pms->got_hit('TIME_LIMIT_EXCEEDED', '', score => 0.001,
+                  description => 'Exceeded time limit / deadline');
+  }
+
   # finished running rules
   delete $pms->{current_rule_name};
   undef $decoded;
   undef $bodytext;
   undef $fulltext;
 
-  # auto-learning
-  $pms->learn();
-  $self->{main}->call_plugins ("check_post_learn", { permsgstatus => $pms });
+  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}) {
@@ -239,8 +279,17 @@
 sub run_generic_tests {
   my ($self, $pms, $priority, %opts) = @_;
 
-  return if $self->{main}->call_plugins("have_shortcircuited",
-                                        { permsgstatus => $pms });
+  my $master_deadline = $pms->{master_deadline};
+  if ($pms->{deadline_exceeded}) {
+    return;
+  } elsif ($master_deadline && time > $master_deadline) {
+    info("check: (run_generic) exceeded time limit, skipping further tests");
+    $pms->{deadline_exceeded} = 1;
+    return;
+  } elsif ($self->{main}->call_plugins("have_shortcircuited",
+                                        { permsgstatus => $pms })) {
+    return;
+  }
 
   my $ruletype = $opts{type};
   dbg("rules: running $ruletype tests; score so far=".$pms->{score});
@@ -257,11 +306,17 @@
   my $methodname = $package_name."::_".$ruletype."_tests_".$clean_priority;
 
   if (defined &{$methodname} && !$doing_user_rules) {
-    no strict "refs";
 run_compiled_method:
   # dbg("rules: run_generic_tests - calling %s", $methodname);
-    $methodname->($pms, @{$opts{args}});
-    use strict "refs";
+    my $t = Mail::SpamAssassin::Timeout->new({ deadline => $master_deadline });
+    my $err = $t->run(sub {
+      no strict "refs";
+      $methodname->($pms, @{$opts{args}});
+    });
+    if ($t->timed_out() && $master_deadline && time > $master_deadline) {
+      info("check: exceeded time limit in $methodname, skipping further tests");
+      $pms->{deadline_exceeded} = 1;
+    }
     return;
   }
 
@@ -1030,8 +1085,17 @@
 sub run_eval_tests {
   my ($self, $pms, $testtype, $evalhash, $prepend2desc, $priority, @extraevalargs) = @_;
  
-  return if $self->{main}->call_plugins("have_shortcircuited",
-                                        { permsgstatus => $pms });
+  my $master_deadline = $pms->{master_deadline};
+  if ($pms->{deadline_exceeded}) {
+    return;
+  } elsif ($master_deadline && time > $master_deadline) {
+    info("check: (run_eval) exceeded time limit, skipping further tests");
+    $pms->{deadline_exceeded} = 1;
+    return;
+  } elsif ($self->{main}->call_plugins("have_shortcircuited",
+                                        { permsgstatus => $pms })) {
+    return;
+  }
 
   my $conf = $pms->{conf};
   my $doing_user_rules = $conf->{want_rebuild_for_type}->{$testtype};
@@ -1053,10 +1117,17 @@
   if (defined &{"${package_name}::${methodname}"}
       && !$doing_user_rules)
   {
+    my $method = "${package_name}::${methodname}";
   # dbg("rules: run_eval_tests - calling %s", $methodname);
-    no strict "refs";
-    &{"${package_name}::${methodname}"}($pms,@extraevalargs);
-    use strict "refs";
+    my $t = Mail::SpamAssassin::Timeout->new({ deadline => $master_deadline });
+    my $err = $t->run(sub {
+      no strict "refs";
+      &{$method}($pms,@extraevalargs);
+    });
+    if ($t->timed_out() && $master_deadline && time > $master_deadline) {
+      info("check: exceeded time limit in $method, skipping further tests");
+      $pms->{deadline_exceeded} = 1;
+    }
     return;
   }
 
@@ -1207,9 +1278,15 @@
     my $method = "${package_name}::${methodname}";
     push (@TEMPORARY_METHODS, $methodname);
   # dbg("rules: run_eval_tests - calling %s", $methodname);
-    no strict "refs";
-    &{$method}($pms,@extraevalargs);
-    use strict "refs";
+    my $t = Mail::SpamAssassin::Timeout->new({ deadline => $master_deadline });
+    my $err = $t->run(sub {
+      no strict "refs";
+      &{$method}($pms,@extraevalargs);
+    });
+    if ($t->timed_out() && $master_deadline && time > $master_deadline) {
+      info("check: exceeded time limit in $method, skipping further tests");
+      $pms->{deadline_exceeded} = 1;
+    }
   }
 }
 

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DCC.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DCC.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DCC.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DCC.pm Thu Nov 19 00:51:09 2009
@@ -679,7 +679,8 @@
 
   $permsgstatus->enter_helper_run_mode();
 
-  my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout });
+  my $timer = Mail::SpamAssassin::Timeout->new(
+           { secs => $timeout, deadline => $permsgstatus->{master_deadline} });
   my $err = $timer->run_and_catch(sub {
 
     local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" };
@@ -757,7 +758,8 @@
   my $tmpf = $permsgstatus->create_fulltext_tmpfile($fulltext);
   my $pid;
 
-  my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout });
+  my $timer = Mail::SpamAssassin::Timeout->new(
+           { secs => $timeout, deadline => $permsgstatus->{master_deadline} });
   my $err = $timer->run_and_catch(sub {
 
     local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" };

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DKIM.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DKIM.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DKIM.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/DKIM.pm Thu Nov 19 00:51:09 2009
@@ -719,7 +719,8 @@
     };
 
     my $timeout = $pms->{conf}->{dkim_timeout};
-    my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout });
+    my $timer = Mail::SpamAssassin::Timeout->new(
+                  { secs => $timeout, deadline => $pms->{master_deadline} });
 
     my $err = $timer->run_and_catch(sub {
       dbg("dkim: performing public key lookup and signature verification");
@@ -909,7 +910,8 @@
 
           my $practices;  # author domain signing practices object
           my $timeout = $pms->{conf}->{dkim_timeout};
-          my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout });
+          my $timer = Mail::SpamAssassin::Timeout->new(
+                    { secs => $timeout, deadline => $pms->{master_deadline} });
           my $err = $timer->run_and_catch(sub {
             eval {
               if (Mail::DKIM::AuthorDomainPolicy->UNIVERSAL::can("fetch")) {

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Pyzor.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Pyzor.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Pyzor.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Pyzor.pm Thu Nov 19 00:51:09 2009
@@ -267,7 +267,8 @@
 
   $permsgstatus->enter_helper_run_mode();
 
-  my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout });
+  my $timer = Mail::SpamAssassin::Timeout->new(
+           { secs => $timeout, deadline => $permsgstatus->{master_deadline} });
   my $err = $timer->run_and_catch(sub {
 
     local $SIG{PIPE} = sub { die "__brokenpipe__ignore__\n" };

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Razor2.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Razor2.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Razor2.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/Razor2.pm Thu Nov 19 00:51:09 2009
@@ -140,7 +140,7 @@
 }
 
 sub razor2_access {
-  my ($self, $fulltext, $type) = @_;
+  my ($self, $fulltext, $type, $deadline) = @_;
   my $timeout = $self->{main}->{conf}->{razor_timeout};
   my $return = 0;
   my @results;
@@ -155,7 +155,8 @@
 
   Mail::SpamAssassin::PerMsgStatus::enter_helper_run_mode($self);
 
-  my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout });
+  my $timer = Mail::SpamAssassin::Timeout->new(
+               { secs => $timeout, deadline => $deadline });
   my $err = $timer->run_and_catch(sub {
 
     local ($^W) = 0;    # argh, warnings in Razor
@@ -347,7 +348,7 @@
   return unless $self->{main}->{conf}->{use_razor2};
   return if $options->{report}->{options}->{dont_report_to_razor};
 
-  if ($self->razor2_access($options->{text}, 'report')) {
+  if ($self->razor2_access($options->{text}, 'report', undef)) {
     $options->{report}->{report_available} = 1;
     info('reporter: spam reported to Razor');
     $options->{report}->{report_return} = 1;
@@ -365,7 +366,7 @@
   return unless $self->{main}->{conf}->{use_razor2};
   return if $options->{revoke}->{options}->{dont_report_to_razor};
 
-  if ($self->razor2_access($options->{text}, 'revoke')) {
+  if ($self->razor2_access($options->{text}, 'revoke', undef)) {
     $options->{revoke}->{revoke_available} = 1;
     dbg('reporter: spam revoked from Razor');
     $options->{revoke}->{revoke_return} = 1;
@@ -394,7 +395,8 @@
 
   # do it this way to make it easier to get out the results later from the
   # netcache plugin
-  ($return, @results) = $self->razor2_access($full, 'check');
+  ($return, @results) =
+    $self->razor2_access($full, 'check', $permsgstatus->{master_deadline});
   $self->{main}->call_plugins ('process_razor_result',
   	{ results => \@results, permsgstatus => $permsgstatus }
   );

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/SPF.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/SPF.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/SPF.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/SPF.pm Thu Nov 19 00:51:09 2009
@@ -542,7 +542,8 @@
 
     my $timeout = $scanner->{conf}->{spf_timeout};
 
-    my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout });
+    my $timer = Mail::SpamAssassin::Timeout->new(
+                { secs => $timeout, deadline => $scanner->{master_deadline} });
     $err = $timer->run_and_catch(sub {
 
       my $query = $self->{spf_server}->process($request);
@@ -579,7 +580,8 @@
 
     my $timeout = $scanner->{conf}->{spf_timeout};
 
-    my $timer = Mail::SpamAssassin::Timeout->new({ secs => $timeout });
+    my $timer = Mail::SpamAssassin::Timeout->new(
+                { secs => $timeout, deadline => $scanner->{master_deadline} });
     $err = $timer->run_and_catch(sub {
 
       ($result, $comment) = $query->result();

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URIDNSBL.pm Thu Nov 19 00:51:09 2009
@@ -924,7 +924,7 @@
       }
     }
   };
-  $scanner->{async}->start_lookup($ent);
+  $scanner->{async}->start_lookup($ent, $scanner->{master_deadline});
   return $ent;
 }
 

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Timeout.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Timeout.pm?rev=882020&r1=882019&r2=882020&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Timeout.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Timeout.pm Thu Nov 19 00:51:09 2009
@@ -23,7 +23,7 @@
 
     # non-timeout code...
 
-    my $t = Mail::SpamAssassin::Timeout->new({ secs => 5 });
+    my $t = Mail::SpamAssassin::Timeout->new({ secs => 5, deadline => $when });
     
     $t->run(sub {
         # code to run with a 5-second timeout...
@@ -58,6 +58,8 @@
 use bytes;
 use re 'taint';
 
+use Time::HiRes qw(time alarm);
+
 use vars qw{
   @ISA
 };
@@ -74,7 +76,14 @@
 
 =item secs => $seconds
 
-timeout, in seconds.  Optional; if not specified, no timeouts will be applied.
+time interval, in seconds. Optional; if neither C<secs> nor C<deadline> is
+specified, no timeouts will be applied.
+
+=item deadline => $unix_timestamp
+
+Unix timestamp (time in seconds since epoch) when a timeout is reached.
+Optional; if neither B<secs> nor B<deadline> is specified, no timeouts will
+be applied. If both are specified, the shorter interval of the two prevails.
 
 =back
 
@@ -96,7 +105,8 @@
 
 Run a code reference within the currently-defined timeout.
 
-The timeout is as defined by the B<secs> parameter to the constructor.
+The timeout is as defined by the B<secs> and B<deadline> parameters
+to the constructor.
 
 Returns whatever the subroutine returns, or C<undef> on timeout.
 If the timer times out, C<$t-<gt>timed_out()> will return C<1>.
@@ -123,13 +133,22 @@
 
   delete $self->{timed_out};
 
-  if (!$self->{secs}) { # no timeout!  just call the sub and return.
+  my $secs = $self->{secs};
+  my $deadline = $self->{deadline};
+
+  if (defined $deadline) {
+    my $dt = $deadline - time;
+    $dt = 1  if $dt < 1;  # give some slack
+    $secs = $dt  if !defined $secs || $dt < $secs;
+  }
+
+  if (!defined $secs) {  # no timeout!  just call the sub and return.
     return &$sub;
   }
 
   # assertion
-  if ($self->{secs} < 0) {
-    die "Mail::SpamAssassin::Timeout: oops? neg value for 'secs': $self->{secs}";
+  if ($secs < 0) {
+    die "Mail::SpamAssassin::Timeout: oops? neg value for 'secs': $secs";
   }
 
   my $oldalarm = 0;
@@ -146,7 +165,7 @@
     local $SIG{ALRM} = sub { $timedout++; die "__alarm__ignore__\n" };
     local $SIG{__DIE__};   # bug 4631
 
-    $oldalarm = alarm($self->{secs});
+    $oldalarm = alarm($secs);
 
     $ret = &$sub;
 
@@ -187,11 +206,7 @@
     $self->{timed_out} = 1;
   }
 
-  if ($and_catch) {
-    return;                 # undef
-  } else {
-    return $ret;
-  }
+  return $and_catch ? undef : $ret;
 }
 
 ###########################################################################
@@ -219,7 +234,16 @@
 
 sub reset {
   my ($self) = @_;
-  alarm($self->{secs});
+
+  my $secs = $self->{secs};
+  my $deadline = $self->{deadline};
+
+  if (defined $deadline) {
+    my $dt = $deadline - time;
+    $dt = 1  if $dt < 1;  # give some slack
+    $secs = $dt  if !defined $secs || $dt < $secs;
+  }
+  alarm($secs)  if defined $secs;
 }
 
 ###########################################################################