svn commit: r1927723 - in spamassassin/trunk: . lib/Mail/SpamAssassin lib/Mail/SpamAssassin/Plugin t t/data/spam

[email protected]
Newsgroups gmane.mail.spam.spamassassin.cvs
Message-ID <175479601592.1120135.15200593140091646669@svn02-us-east.apache.org>
Author: fkento
Date: Sun Aug 10 03:20:15 2025
New Revision: 1927723

Log:
Bug 8342: Detect invalid address headers

Added:
   spamassassin/trunk/t/data/spam/invalid_from
   spamassassin/trunk/t/data/spam/invalid_replyto
   spamassassin/trunk/t/data/spam/invalid_sender
Modified:
   spamassassin/trunk/MANIFEST
   spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm
   spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HeaderEval.pm
   spamassassin/trunk/t/header.t

Modified: spamassassin/trunk/MANIFEST
==============================================================================
--- spamassassin/trunk/MANIFEST	Sun Aug 10 02:49:15 2025	(r1927722)
+++ spamassassin/trunk/MANIFEST	Sun Aug 10 03:20:15 2025	(r1927723)
@@ -459,6 +459,9 @@ t/data/spam/gtube.eml
 t/data/spam/gtubedcc.eml
 t/data/spam/gtubedcc_crlf.eml
 t/data/spam/hashbl
+t/data/spam/invalid_from
+t/data/spam/invalid_replyto
+t/data/spam/invalid_sender
 t/data/spam/olevbmacro/encrypted.eml
 t/data/spam/olevbmacro/goodcsv.eml
 t/data/spam/olevbmacro/macro.eml

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm	Sun Aug 10 02:49:15 2025	(r1927722)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm	Sun Aug 10 03:20:15 2025	(r1927723)
@@ -2377,6 +2377,7 @@ sub _get {
       # Note: parse_header_addresses always called with raw undecoded value
       # Skip invalid addresses here
       my @addrs = parse_header_addresses($line);
+      push @{$self->{address_details}->{$request_lc}}, @addrs; # Bug 8342: save for later
       if (@addrs) {
         if ($getaddr) {
           foreach my $addr (@addrs) {

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HeaderEval.pm
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HeaderEval.pm	Sun Aug 10 02:49:15 2025	(r1927722)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/HeaderEval.pm	Sun Aug 10 03:20:15 2025	(r1927723)
@@ -34,6 +34,21 @@ our @ISA = qw(Mail::SpamAssassin::Plugin
 
 my $IP_ADDRESS = IP_ADDRESS;
 
+=head1 NAME
+
+Mail::SpamAssassin::Plugin::HeaderEval - Plugin for evaluating header fields.
+
+=head1 SYNOPSIS
+
+  use Mail::SpamAssassin::Plugin::HeaderEval;
+
+=head1 DESCRIPTION
+
+The C<Mail::SpamAssassin::Plugin::HeaderEval> module provides functionality
+for evaluating email header fields against certain criteria in SpamAssassin.
+
+=cut
+
 # constructor: register the eval rule
 sub new {
   my $class = shift;
@@ -73,10 +88,23 @@ sub new {
   $self->register_eval_rule("gated_through_received_hdr_remover", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
   $self->register_eval_rule("received_within_months", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
   $self->register_eval_rule("check_equal_from_domains", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
+  $self->register_eval_rule("check_invalid_from", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
+  $self->register_eval_rule("check_invalid_sender", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
+  $self->register_eval_rule("check_invalid_replyto", $Mail::SpamAssassin::Conf::TYPE_HEAD_EVALS);
 
   return $self;
 }
 
+=over 4
+
+=item check_for_fake_aol_relay_in_rcvd
+
+Check if a relay present in Received header is a forged AOL header.
+
+=back
+
+=cut
+
 sub check_for_fake_aol_relay_in_rcvd {
   my ($self, $pms) = @_;
   local ($_);
@@ -134,6 +162,16 @@ sub check_for_unique_subject_id {
   return 0;
 }
 
+=over 4
+
+=item check_illegal_chars
+
+Check for 8-bit and other illegal characters that should be MIME encoded
+
+=back
+
+=cut
+
 # look for 8-bit and other illegal characters that should be MIME
 # encoded, these might want to exempt languages that do not use
 # Latin-based alphabets, but only if the user wants it that way
@@ -261,6 +299,16 @@ sub _check_for_forged_hotmail_received_h
   }
 }
 
+=over 4
+
+=item check_for_forged_hotmail_received_headers
+
+Check for forged Hotmail received headers
+
+=back
+
+=cut
+
 # FORGED_HOTMAIL_RCVD
 sub check_for_forged_hotmail_received_headers {
   my ($self, $pms) = @_;
@@ -268,6 +316,16 @@ sub check_for_forged_hotmail_received_he
   return $pms->{hotmail_addr_with_forged_hotmail_received};
 }
 
+=over 4
+
+=item check_for_no_forged_hotmail_received_headers
+
+Check for semi-forged Hotmail received headers
+
+=back
+
+=cut
+
 # SEMIFORGED_HOTMAIL_RCVD
 sub check_for_no_hotmail_received_headers {
   my ($self, $pms) = @_;
@@ -343,6 +401,16 @@ sub check_for_msn_groups_headers {
 
 ###########################################################################
 
+=over 4
+
+=item check_for_forged_eudoramail_received_headers
+
+Check for forged Eudora mail received headers
+
+=back
+
+=cut
+
 sub check_for_forged_eudoramail_received_headers {
   my ($self, $pms) = @_;
 
@@ -372,6 +440,16 @@ sub check_for_forged_eudoramail_received
 
 ###########################################################################
 
+=over 4
+
+=item check_for_forged_yahoo_received_headers
+
+Check for forged Yahoo received headers
+
+=back
+
+=cut
+
 sub check_for_forged_yahoo_received_headers {
   my ($self, $pms) = @_;
 
@@ -429,6 +507,16 @@ sub check_for_forged_yahoo_received_head
   return 1;
 }
 
+=over 4
+
+=item check_for_forged_juno_received_headers
+
+Check for forged Juno received headers
+
+=back
+
+=cut
+
 sub check_for_forged_juno_received_headers {
   my ($self, $pms) = @_;
 
@@ -460,6 +548,16 @@ sub check_for_forged_juno_received_heade
   return 0;   
 }
 
+=over 4
+
+=item check_for_forged_gmail_received_headers
+
+Check for forged Gmail received headers
+
+=back
+
+=cut
+
 sub check_for_forged_gmail_received_headers {
   my ($self, $pms) = @_;
   use constant GOOGLE_MESSAGE_STATE_LENGTH_MIN => 60;
@@ -487,12 +585,32 @@ sub check_for_forged_gmail_received_head
   return 1;
 }
 
+=over 4
+
+=item check_for_matching_env_and_hdr_from
+
+Check if EnvelopeFrom and From headers match
+
+=back
+
+=cut
+
 sub check_for_matching_env_and_hdr_from {
   my ($self, $pms) =@_;
   # two blank headers match so don't bother checking
   return (lc $pms->get('EnvelopeFrom:addr') eq lc $pms->get('From:addr'));
 }
 
+=over 4
+
+=item sorted_recipients
+
+Check if recipients are sorted
+
+=back
+
+=cut
+
 sub sorted_recipients {
   my ($self, $pms) = @_;
 
@@ -502,6 +620,16 @@ sub sorted_recipients {
   return $pms->{tocc_sorted};
 }
 
+=over 4
+
+=item similar_recipients
+
+Check if recipients are similar
+
+=back
+
+=cut
+
 sub similar_recipients {
   my ($self, $pms, $min, $max) = @_;
 
@@ -563,6 +691,16 @@ sub _check_recipients {
   }
 }
 
+=over 4
+
+=item check_for_missing_to_header
+
+Check if To: header is missing
+
+=back
+
+=cut
+
 sub check_for_missing_to_header {
   my ($self, $pms) = @_;
 
@@ -800,6 +938,15 @@ sub _check_date_diff {
   $pms->{date_diff} = $diffs[0];
 }
 
+=over 4
+
+=item subject_is_all_caps
+
+Check if Subject: header is all uppercase
+
+=back
+
+=cut
 
 sub subject_is_all_caps {
    my ($self, $pms) = @_;
@@ -824,6 +971,16 @@ sub subject_is_all_caps {
    return length($subject) && ($subject eq uc($subject));
 }
 
+=over 4
+
+=item check_for_to_in_subject
+
+Check if To: header value is present in the email Subject
+
+=back
+
+=cut
+
 sub check_for_to_in_subject {
   my ($self, $pms, $test) = @_;
 
@@ -852,6 +1009,16 @@ sub check_for_to_in_subject {
   return 0;
 }
 
+=over 4
+
+=item check_outlook_message_id
+
+Check if Message-ID header has been generated by Microsoft Outlook
+
+=back
+
+=cut
+
 sub check_outlook_message_id {
   my ($self, $pms) = @_;
   local ($_);
@@ -880,6 +1047,16 @@ sub check_outlook_message_id {
   return (abs($diff) >= $fudge);
 }
 
+=over 4
+
+=item check_messageid_not_usable
+
+Check for invalid Message-ID header
+
+=back
+
+=cut
+
 sub check_messageid_not_usable {
   my ($self, $pms) = @_;
   local ($_);
@@ -985,6 +1162,69 @@ sub check_equal_from_domains {
   return 0;
 }
 
+###########################################################################
+
+=over 4
+
+=item check_invalid_from
+
+Check if From: address is not valid
+
+=back
+
+=cut
+
+sub check_invalid_from {
+  my ($self, $pms) = @_;
+
+  unless (defined $pms->{invalid_from_addr}) {
+    $pms->get('From:addr'); # parse From:addr
+    $pms->{invalid_from_addr} = grep { $_->{invalid} } @{$pms->{address_details}{'from'} || []};
+  }
+
+  return $pms->{invalid_from_addr} ? 1 : 0;
+}
+
+=over 4
+
+=item check_invalid_sender
+
+Check if Sender header address is not valid
+
+=back
+
+=cut
+
+sub check_invalid_sender {
+  my ($self, $pms) = @_;
+
+  unless (defined $pms->{invalid_sender_addr}) {
+    $pms->get('Sender:addr'); # parse Sender:addr
+    $pms->{invalid_sender_addr} = grep {$_->{invalid}} @{$pms->{address_details}{'sender'} || []};
+  }
+
+  return $pms->{invalid_sender_addr} ? 1 : 0;
+}
+=over 4
+
+=item check_invalid_replyto
+
+Check if Reply-To header address is not valid
+
+=back
+
+=cut
+
+sub check_invalid_replyto {
+  my ($self, $pms) = @_;
+
+  unless (defined $pms->{invalid_replyto_addr}) {
+    $pms->get('Reply-To:addr'); # parse Reply-To:addr
+    $pms->{invalid_replyto_addr} = grep {$_->{invalid}} @{$pms->{address_details}{'reply-to'} || []};
+  }
+
+  return $pms->{invalid_replyto_addr} ? 1 : 0;
+}
 
 ###########################################################################
 
@@ -995,4 +1235,8 @@ sub dbg2 {
   }
 }
 
-1;
+sub has_check_invalid_from { 1 }
+sub has_check_invalid_sender { 1 }
+sub has_check_invalid_replyto { 1 }
+
+1;
\ No newline at end of file

Added: spamassassin/trunk/t/data/spam/invalid_from
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ spamassassin/trunk/t/data/spam/invalid_from	Sun Aug 10 03:20:15 2025	(r1927723)
@@ -0,0 +1,15 @@
+Return-Path: <[email protected]>
+Received: from google-public-dns-a.google.com (google-public-dns-a.google.com [8.8.8.8])
+	by in.example.com (Postfix) with ESMTPS
+	for <[email protected]>; Wed, 18 Jul 2018 21:12:22 +0200 (CEST)
+Received: by google-public-dns-a.google.com with SMTP id f21-v6so3811271wmc.5
+        for <[email protected]>; Wed, 18 Jul 2018 12:12:22 -0700 (PDT)
+From: "Support" <no [email protected]>
+To: [email protected]
+Reply-To: <[email protected]>
+Subject: Invalid From header test
+Date: Wed, 18 Jul 2018 12:12:00 -0700 (PDT)
+MIME-Version: 1.0
+Message-Id: <[email protected]>
+
+Test

Added: spamassassin/trunk/t/data/spam/invalid_replyto
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ spamassassin/trunk/t/data/spam/invalid_replyto	Sun Aug 10 03:20:15 2025	(r1927723)
@@ -0,0 +1,15 @@
+Return-Path: <[email protected]>
+Received: from google-public-dns-a.google.com (google-public-dns-a.google.com [8.8.8.8])
+	by in.example.com (Postfix) with ESMTPS
+	for <[email protected]>; Wed, 18 Jul 2018 21:12:22 +0200 (CEST)
+Received: by google-public-dns-a.google.com with SMTP id f21-v6so3811271wmc.5
+        for <[email protected]>; Wed, 18 Jul 2018 12:12:22 -0700 (PDT)
+From: "Support" <[email protected]>
+To: [email protected]
+Reply-To: [email protected]/test
+Subject: Invalid Reply-To header test
+Date: Wed, 18 Jul 2018 12:12:00 -0700 (PDT)
+MIME-Version: 1.0
+Message-Id: <[email protected]>
+
+Test

Added: spamassassin/trunk/t/data/spam/invalid_sender
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ spamassassin/trunk/t/data/spam/invalid_sender	Sun Aug 10 03:20:15 2025	(r1927723)
@@ -0,0 +1,15 @@
+Return-Path: <[email protected]>
+Received: from google-public-dns-a.google.com (google-public-dns-a.google.com [8.8.8.8])
+	by in.example.com (Postfix) with ESMTPS
+	for <[email protected]>; Wed, 18 Jul 2018 21:12:22 +0200 (CEST)
+Received: by google-public-dns-a.google.com with SMTP id f21-v6so3811271wmc.5
+        for <[email protected]>; Wed, 18 Jul 2018 12:12:22 -0700 (PDT)
+From: "Support" <[email protected]>
+To: [email protected]
+Sender: [email protected]_bar.com
+Subject: Invalid Sender header test
+Date: Wed, 18 Jul 2018 12:12:00 -0700 (PDT)
+MIME-Version: 1.0
+Message-Id: <[email protected]>
+
+Test

Modified: spamassassin/trunk/t/header.t
==============================================================================
--- spamassassin/trunk/t/header.t	Sun Aug 10 02:49:15 2025	(r1927722)
+++ spamassassin/trunk/t/header.t	Sun Aug 10 03:20:15 2025	(r1927723)
@@ -2,7 +2,7 @@
 
 use lib '.'; use lib 't';
 use SATest; sa_t_init("header");
-use Test::More tests => 26;
+use Test::More tests => 38;
 
 # ---------------------------------------------------------------------------
 
@@ -67,6 +67,54 @@ ok (sarun ("-L -t < data/nice/001", \&pa
 ok_all_patterns();
 
 ##########################################
+
+tstprefs('
+  # Check for invalid header parsing (Bug 8342)
+
+  header INVALID_FROM eval:check_invalid_from()
+  score INVALID_FROM 2.0
+
+  header INVALID_SENDER eval:check_invalid_sender()
+  score INVALID_SENDER 2.0
+
+  header INVALID_REPLY eval:check_invalid_replyto()
+  score INVALID_REPLY 2.0
+');
+
+%patterns = (
+    q{ 2.0 INVALID_FROM }, 'INVALID_FROM',
+);
+%anti_patterns = (
+    q{ 2.0 INVALID_REPLY }, 'INVALID_REPLY',
+    q{ 2.0 INVALID_SENDER }, 'INVALID_SENDER',
+);
+
+ok (sarun ("-L -t < data/spam/invalid_from", \&patterns_run_cb));
+ok_all_patterns();
+
+%patterns = (
+    q{ 2.0 INVALID_SENDER }, 'INVALID_SENDER',
+);
+%anti_patterns = (
+    q{ 2.0 INVALID_FROM }, 'INVALID_FROM',
+    q{ 2.0 INVALID_REPLY }, 'INVALID_REPLY',
+);
+
+ok (sarun ("-L -t < data/spam/invalid_sender", \&patterns_run_cb));
+ok_all_patterns();
+
+%patterns = (
+    q{ 2.0 INVALID_REPLY }, 'INVALID_REPLY',
+);
+%anti_patterns = (
+    q{ 2.0 INVALID_FROM }, 'INVALID_FROM',
+    q{ 2.0 INVALID_SENDER }, 'INVALID_SENDER',
+);
+
+ok (sarun ("-L -t < data/spam/invalid_replyto", \&patterns_run_cb));
+ok_all_patterns();
+
+##########################################
 
 tstprefs('
   # Test 4.0 multiple :addr parser
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.