Patrick,

> do you provide 2.5.3 , and 2.5.4 patch?
> My clients are suffering from this problem.

Try this one (against 2.5.4):


--- amavisd.orig        2008-04-10 16:18:49.000000000 +0200
+++ amavisd     2008-04-10 19:14:04.000000000 +0200
@@ -227,5 +227,6 @@
       @av_scanners @av_scanners_backup $first_infected_stops_scan
       $sa_spam_report_header $sa_spam_level_char $sa_mail_body_size_limit
-      $penpals_bonus_score $penpals_halflife $reputation_factor
+      $penpals_bonus_score $penpals_halflife $bounce_killer_score
+      $reputation_factor
       $undecipherable_subject_tag $localpart_is_case_sensitive
       $recipient_delimiter $replace_existing_extension
@@ -582,4 +583,6 @@
        # for testing and statistics gathering);
 
+  $bounce_killer_score = 100;
+
   #
   # Receiving mail related
@@ -6028,4 +6031,6 @@
 sub name_declared     # string or a ref to a list of strings
   { my($self)=shift; [EMAIL PROTECTED] ? $self->{nm_decl}  : 
($self->{nm_decl}=shift) };
+sub report_type       # a string, e.g. 'delivery-status', rfc3462
+  { my($self)=shift; [EMAIL PROTECTED] ? $self->{rep_typ}  : 
($self->{rep_typ}=shift) };
 sub size
   { my($self)=shift; [EMAIL PROTECTED] ? $self->{size}     : 
($self->{size}=shift) };
@@ -6500,4 +6505,6 @@
     }
     $part->name_declared(@rn==1 ? $rn[0] : [EMAIL PROTECTED])  if @rn;
+    $val = $head->mime_attr('content-type.report-type');
+    $part->report_type($val)  if $val ne '';
   }
   mime_decode_pre_epi('epilogue', $entity->epilogue,
@@ -6519,6 +6526,10 @@
                                                   $parent_obj));
   $parser->ignore_errors(1);  # also is the default
-# $parser->extract_nested_messages(0);
-  $parser->extract_nested_messages("NEST");  # parse embedded message/rfc822
+  # if bounce killer is enabled, extract_nested_messages must be off,
+  # otherwise we lose headers of attached message/rfc822 messages
+  $parser->extract_nested_messages(0);
+# $parser->extract_nested_messages("NEST");  # parse embedded message/rfc822
+    # "NEST" complains with "part did not end with expected boundary" when
+    # the outer message is message/partial and the inner message is chopped
   $parser->extract_uuencode(1);              # to enable or not to enable ???
   $parser->max_parts($MAXFILES)  if $MAXFILES > 0;
@@ -8778,4 +8789,5 @@
        ($will_do_virus_scanning || $will_do_banned_checking);
 
+    my($bounce_header_fields_ref,$bounce_msgid);
     if ($will_do_parts_decoding) {  # decoding parts can take a lot of time
       $which_section = "mime_decode-1";
@@ -8786,4 +8798,15 @@
       prolong_timer($which_section);
 
+      if (c('bounce_killer_score') > 0) {
+        $which_section = "dsn_parse";
+        # analyze a bounce after MIME decoding but before further archive
+        # decoding, which often replaces decoded files
+        $bounce_header_fields_ref = inspect_a_bounce_message($msginfo);
+        $bounce_msgid = $bounce_header_fields_ref->{'message-id'}
+          if $bounce_header_fields_ref &&
+             exists $bounce_header_fields_ref->{'message-id'};
+        prolong_timer($which_section);
+      }
+
       $which_section = "parts_decode_ext";
       snmp_count('OpsDec');
@@ -9014,4 +9037,5 @@
 
     $which_section = "penpals_check";
+    my($pp_age);
     if (defined($sql_storage) && [EMAIL PROTECTED]) {
       my($pp_bonus) = c('penpals_bonus_score');  # score points
@@ -9022,8 +9046,8 @@
            (defined $penpals_threshold_low || defined $penpals_threshold_high);
       if ($pp_bonus <= 0 || $pp_halflife <= 0) {}
-      elsif (defined($penpals_threshold_low) &&
+      elsif (defined($penpals_threshold_low)  && !defined($bounce_msgid) &&
              $spam_level + max(@boost_list) < $penpals_threshold_low) {}
         # low score for all recipients, no need for aid
-      elsif (defined($penpals_threshold_high) &&
+      elsif (defined($penpals_threshold_high) && !defined($bounce_msgid) &&
              $spam_level + min(@boost_list) - $pp_bonus
                                             > $penpals_threshold_high) {}
@@ -9043,9 +9067,11 @@
                             $msginfo->orig_header_fields->{'references'};
             my(@refs) = $refs_str eq '' ? () : parse_message_id($refs_str);
+            push(@refs,$bounce_msgid)  if defined $bounce_msgid;
             do_log(4,"penpals: references: %s", join(", ",@refs))  if @refs;
             # NOTE: swap $rid and $sid as args here, as we are now checking
             # for a potential reply mail - whether the current recipient has
             # recently sent any mail to the sender of the current mail:
-            my($pp_age,$pp_mail_id,$pp_subj) =
+            my($pp_mail_id,$pp_subj);
+            ($pp_age,$pp_mail_id,$pp_subj) =
               $sql_storage->penpals_find($rid,$sid,[EMAIL 
PROTECTED],$msginfo->rx_time);
             if (defined($pp_age)) {  # found info about previous correspondence
@@ -9077,4 +9103,43 @@
     }
 
+    $which_section = "bounce_killer";
+    if ($bounce_header_fields_ref) {  # message looks like a DSN
+      my($bounce_rescued);
+      if (defined $pp_age) {  # found by pen pals by a Message-ID in attachment
+        # is a bounce, refers to our previous outgoing message, treat it kindly
+        snmp_count('InMsgsBounceRescuedByPenPals');
+        $bounce_rescued = 'by penpals';
+      } elsif (defined($bounce_msgid) && $bounce_msgid =~ /([EMAIL 
PROTECTED]@>]+)>?\z/ &&
+               lookup(0,$1, @{ca('local_domains_maps')})) {
+        # not in pen pals, but domain in Message-ID is a local domain
+        snmp_count('InMsgsBounceRescuedByDomain');
+        $bounce_rescued = 'by domain';
+      }
+      ll(2) && do_log(2, "bounce %s, %s -> %s, %s",
+                 defined $bounce_rescued ?'rescued '.$bounce_rescued :'killed',
+                 qquote_rfc2821_local($sender),
+                 join(',', qquote_rfc2821_local(@recips)),
+                 join(', ', map { $_ . ': ' . $bounce_header_fields_ref->{$_} }
+                                sort(keys %$bounce_header_fields_ref)) );
+      if (!$bounce_rescued) {
+        snmp_count('InMsgsBounceNotFromUsKilled');
+        my($bounce_killer_score) = c('bounce_killer_score');
+        for my $r (@{$msginfo->per_recip_data}) {
+          my($boost) = $r->recip_score_boost || 0;
+          $r->recip_score_boost($boost + $bounce_killer_score);
+        }
+      }
+    } elsif ($sender eq '' ||
+             $sender          =~ /^(?:postmaster|mailer-daemon)(?:\@|\z)/si ||
+             $rfc2822_from[0] =~ /^(?:postmaster|mailer-daemon|double-bounce|
+                                      mailer|uucp) (?:\@|\z)/xsi) {
+      # message could be some kind of a non-standard bounce, but lacks
+      # recognizable structure and/or a header section from original mail
+      ll(3) && do_log(3, "bounce unverifiable, %s -> %s, ",
+                         qquote_rfc2821_local($sender),
+                         join(',', qquote_rfc2821_local(@recips)));
+      snmp_count('InMsgsBounceUnverifiable');
+    }
+
     $which_section = "decide_mail_destiny";
     $snmp_db->register_proc(2,0,'r',$am_id)  if defined $snmp_db;  # results...
@@ -9743,4 +9808,109 @@
 }
 
+# Check if a message is a bounce, and if it is, try to obtain essential
+# information from an attached header section of an attached original message,
+# mainly the Message-ID.
+#
+sub inspect_a_bounce_message($) {
+  my($msginfo) = @_;
+  my(%header_field); my($is_true_bounce) = 0; 
+  my($parts_root) = $msginfo->parts_root;
+  if (defined $parts_root) {
+    my($sender) = $msginfo->sender;
+    my($structure_type) = '?';
+    my($top) = $parts_root->children;
+    $top = $top->[0]  if defined $top;  # there should only be one top part
+    my(@parts); my($fname_ind,$fname); my($plaintext) = 0;
+    if (defined $top)
+      { my($ch) = $top->children;  @parts = ($top, !defined $ch ? () : @$ch) }
+    my(@t) =
+      map { my($t)=$_->type_declared; lc(ref $t ? $t->[0] : $t) } @parts;
+    ll(5) && do_log(5, "inspect_dsn: parts: %s", join(", ",@t));
+    if (  @parts >= 2 && @parts <= 4  &&  # a root, with 2 or 3 leaves
+          $t[0] eq 'multipart/report' &&
+        ( $t[2] eq 'message/delivery-status' ||
+          $t[2] eq 'message/feedback-report' ) &&
+          $t[2] eq 'message/'.lc($parts[0]->report_type) &&
+        ( $t[3] eq 'text/rfc822-headers' || $t[3] eq 'message/rfc822' )) {
+      # standard DSN or feedback-report
+      $fname_ind = 3; $is_true_bounce = 1; $structure_type = 'standard DSN';
+    } elsif (@parts >= 3 && @parts <= 4  &&  # a root, with 2 or 3 leaves
+          $t[0] eq 'multipart/report' &&
+          lc($parts[0]->report_type) eq 'delivery-status' &&
+        ( $t[-1] eq 'text/rfc822-headers' || $t[-1] eq 'message/rfc822' )) {
+      # not quite std. DSN (missing message/delivery-status), but recognizable
+      $fname_ind = -1; $is_true_bounce = 1;
+      $structure_type = 'DSN, missing delivery-status part';
+    } elsif (@parts >= 3 && @parts <= 5 && $sender eq '' &&
+          $t[0] eq 'multipart/mixed' &&
+        ( $t[-1] eq 'text/rfc822-headers' || $t[-1] eq 'message/rfc822' )) {
+      # sometimes qmail
+      $fname_ind = -1; $structure_type = 'multipart/mixed with attached msg';
+    } elsif (@parts == 1 && $sender eq '') {
+      # nonstructured, possibly a non-standard bounce (qmail, gmail.com, ...)
+      $fname_ind = 0; $plaintext = 1; $structure_type = 'nonstructured';
+    }
+    if (defined $fname_ind) {  # we have a header section from original mail
+      $fname_ind = $#parts  if $fname_ind == -1;
+      $fname = $parts[$fname_ind]->full_name;
+      ll(5) && do_log(5, "inspect_dsn: %s, base_name(%s): %s, fname: %s",
+        $structure_type, $fname_ind, $parts[$fname_ind]->base_name, $fname);
+      defined $fname or die "inspect_dsn: no filename ($fname_ind), ".
+                            "make sure the extract_nested_messages is off!";
+      my(%collectable_header_fields);
+      $collectable_header_fields{lc($_)} = 1
+        for qw(From Message-ID Return-Path);
+      my($fh) = IO::File->new;
+      $fh->open($fname,'<') or die "Can't open file $fname: $!";
+      binmode($fh,":bytes") or die "Can't cancel :utf8 mode: $!"
+        if $unicode_aware;
+      my($curr_head,$ln); my($nr) = 0; my($have_msgid) = 0; local($1,$2);
+      for ($! = 0; defined($ln = $fh->getline); $! = 0) {
+        $nr++;  last if $nr > 1000;  # safety measure
+        if ($ln =~ /^[ \t]/) {  # folded
+          $curr_head .= $ln  if length($curr_head) < 2000;  # safety measure
+        } else {  # a new header field, process previous if any
+          $header_field{lc($1)} = $2  if defined($curr_head) &&
+                          $curr_head =~ /^([!-9;-\176]{1,30})[ \t]*:(.*)\z/s &&
+                          $collectable_header_fields{lc($1)};
+          $curr_head = $ln;
+          $have_msgid = 1  if defined $header_field{'from'} &&
+                              defined $header_field{'message-id'};
+          last  if ($ln eq "\n" || $ln =~ /^--/) && !$plaintext;
+          last  if $have_msgid;
+        }
+      }
+      defined $ln || $!==0  or die "Error reading from $fname: $!";
+      $fh->close or die "Error closing $fname: $!";
+      $header_field{lc($1)} = $2  if defined($curr_head) &&
+                          $curr_head =~ /^([!-9;-\176]{1,30})[ \t]*:(.*)\z/s &&
+                          $collectable_header_fields{lc($1)};
+      $have_msgid = 1  if defined $header_field{'from'} &&
+                          defined $header_field{'message-id'};
+      $is_true_bounce = 1  if $have_msgid;
+      if ($is_true_bounce) {
+        for (@header_field{keys %header_field})
+          { s/\n(?=[ \t])//g; s/^[ \t]+//; s/[ \t\n]+\z// }
+        $header_field{'message-id'} =
+          (parse_message_id($header_field{'message-id'}))[0]
+          if defined $header_field{'message-id'};
+      }
+      section_time("inspect_dsn");
+    }
+    if ($is_true_bounce) {
+      do_log(3, "inspect_dsn: bounce, struct(%s): %s, <%s>, %s",
+                !defined($fname_ind) ? '-' : $fname_ind,
+                $structure_type, $sender,
+                join(", ", map { $_ . ": " . $header_field{$_} }
+                               sort(keys %header_field)) )  if ll(3);
+    } elsif ($sender eq '') {
+      do_log(3, "inspect_dsn: not a bounce, struct(%s): %s, parts: %s",
+                !defined($fname_ind) ? '-' : $fname_ind,
+                $structure_type, join(", ",@t))  if ll(3);
+    }
+  }
+  !$is_true_bounce ? 0 : \%header_field;
+}
+
 sub add_forwarding_header_edits_common($$$$$$$) {
   my($conn, $msginfo, $hdr_edits, $hold, $any_undecipherable,




Mark

-------------------------------------------------------------------------
This SF.net email is sponsored by the 2008 JavaOne(SM) Conference 
Don't miss this year's exciting event. There's still time to save $100. 
Use priority code J8TL2D2. 
http://ad.doubleclick.net/clk;198757673;13503038;p?http://java.sun.com/javaone
_______________________________________________
AMaViS-user mailing list
AMaViS-user@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/amavis-user
AMaViS-FAQ:http://www.amavis.org/amavis-faq.php3
AMaViS-HowTos:http://www.amavis.org/howto/

Reply via email to