Mailing List Archive

svn commit: rev 6768 - in incubator/spamassassin/trunk: . lib/Mail/SpamAssassin
Author: jm
Date: Wed Feb 18 23:34:09 2004
New Revision: 6768

Added:
incubator/spamassassin/trunk/lib/Mail/SpamAssassin/MsgMetadata.pm
Modified:
incubator/spamassassin/trunk/MANIFEST
incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Bayes.pm
incubator/spamassassin/trunk/lib/Mail/SpamAssassin/EvalTests.pm
incubator/spamassassin/trunk/lib/Mail/SpamAssassin/MsgContainer.pm
incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm
incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm
incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Received.pm
Log:
metadata support; sa-learn can now learn languages and trusted/untrusted relays, so these can now be Bayes tokens

Modified: incubator/spamassassin/trunk/MANIFEST
==============================================================================
--- incubator/spamassassin/trunk/MANIFEST (original)
+++ incubator/spamassassin/trunk/MANIFEST Wed Feb 18 23:34:09 2004
@@ -302,3 +302,5 @@
tools/triplets.pl
t/reportheader_8bit.t
t/uri.t
+masses/rule-dev/maildir-scan-headers
+lib/Mail/SpamAssassin/MsgMetadata.pm

Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Bayes.pm
==============================================================================
--- incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Bayes.pm (original)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Bayes.pm Wed Feb 18 23:34:09 2004
@@ -460,14 +460,7 @@
sub tokenize_headers {
my ($self, $msg) = @_;

- my $hdrs = $msg->get_all_headers();
-
- # jm: do not learn additional metadata (X-Languages, X-Relays-Untrusted)
- # until we can generate that while running sa-learn. TODO
- #
- # if ($msg->can ("get_all_metadata")) {
- # $hdrs .= $msg->get_all_metadata();
- # }
+ my $hdrs = $msg->get_all_headers() . $msg->get_all_metadata();

my %parsed = ();

@@ -677,6 +670,8 @@
return if $ignore;
}

+ $msg->extract_message_metadata ($self->{main});
+
my $body = $self->get_body_from_msg ($msg);
my $ret;

@@ -798,6 +793,8 @@

if (!$self->{conf}->{use_bayes}) { return; }
if (!defined $msg) { return; }
+
+ $msg->extract_message_metadata ($self->{main});
my $body = $self->get_body_from_msg ($msg);
my $ret;


Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/EvalTests.pm
==============================================================================
--- incubator/spamassassin/trunk/lib/Mail/SpamAssassin/EvalTests.pm (original)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/EvalTests.pm Wed Feb 18 23:34:09 2004
@@ -2252,35 +2252,14 @@
return $self->{undesired_language_body};
}

+ $self->{undesired_language_body} = 0;
my @languages = split (' ', $self->{conf}->{ok_languages});

- # map of languages that are very often mistaken for another, perhaps with
- # more than 0.02% false positives, we only map if length is < 2048 bytes
- my %mistakable = ('sco' => 'en');
-
if (grep { $_ eq "all" } @languages) {
- $self->{undesired_language_body} = 0;
- return $self->{undesired_language_body};
- }
-
- $body = join ("\n", @{$body});
- $body =~ s/^Subject://i;
-
- # need about 256 bytes for reasonably accurate match (experimentally derived)
- if (length($body) < 256)
- {
- dbg("Message too short for language analysis");
- $self->{undesired_language_body} = 0;
return $self->{undesired_language_body};
}

- my @matches = Mail::SpamAssassin::TextCat::classify($self, $body);
-
- # save matches for possible insertion into headers, etc.
- $self->{tag_data}->{LANGUAGES} = join(', ', @matches);
-
- # add to metadata, too, so Bayes gets to take a look
- $self->{msg}->put_metadata ("X-Languages", $self->{tag_data}->{LANGUAGES});
+ my @matches = @{$self->{msg}->{metadata}->{textcat_matches}};

# not able to get a match, assume it's okay
if (! @matches) {
@@ -2288,14 +2267,18 @@
return $self->{undesired_language_body};
}

+ # map of languages that are very often mistaken for another, perhaps with
+ # more than 0.02% false positives
+ my %mistakable = ('sco' => 'en');
+
# see if any matches are okay
foreach my $match (@matches) {
$match =~ s/\..*//;
- if (length($body) < 2048 && exists $mistakable{$match}) {
+ if (exists $mistakable{$match}) {
$match = $mistakable{$match};
}
foreach my $language (@languages) {
- if (length($body) < 2048 && exists $mistakable{$language}) {
+ if (exists $mistakable{$language}) {
$language = $mistakable{$language};
}
if ($match eq $language) {

Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/MsgContainer.pm
==============================================================================
--- incubator/spamassassin/trunk/lib/Mail/SpamAssassin/MsgContainer.pm (original)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/MsgContainer.pm Wed Feb 18 23:34:09 2004
@@ -38,6 +38,7 @@
use MIME::Base64;
use Mail::SpamAssassin;
use Mail::SpamAssassin::HTML;
+use Mail::SpamAssassin::MsgMetadata;
use MIME::Base64;
use MIME::QuotedPrint;

@@ -55,7 +56,7 @@
my $self = {
headers => {},
raw_headers => {},
- metadata => {},
+ meta_strings => {},
body_parts => [],
header_order => [],
already_parsed => 1,
@@ -585,13 +586,26 @@

# ---------------------------------------------------------------------------

+sub extract_message_metadata {
+ my ($self, $main) = @_;
+
+ # do this only once
+ if ($self->{already_extracted_metadata}) { return; }
+ $self->{already_extracted_metadata} = 1;
+
+ $self->{metadata} = Mail::SpamAssassin::MsgMetadata->new($self);
+ $self->{metadata}->extract ($self, $main);
+}
+
+# ---------------------------------------------------------------------------
+
=item $str = get_metadata($hdr)

=cut

sub get_metadata {
my ($self, $hdr) = @_;
- $self->{metadata}->{$hdr};
+ $self->{meta_strings}->{$hdr};
}

=item put_metadata($hdr, $text)
@@ -600,7 +614,7 @@

sub put_metadata {
my ($self, $hdr, $text) = @_;
- $self->{metadata}->{$hdr} = $text;
+ $self->{meta_strings}->{$hdr} = $text;
}

=item delete_metadata($hdr)
@@ -609,7 +623,7 @@

sub delete_metadata {
my ($self, $hdr) = @_;
- delete $self->{metadata}->{$hdr};
+ delete $self->{meta_strings}->{$hdr};
}

=item $str = get_all_metadata()
@@ -620,10 +634,23 @@
my ($self) = @_;

my @ret = ();
- foreach my $key (sort keys %{$self->{metadata}}) {
- push (@ret, $key, ": ", $self->{metadata}->{$key}, "\n");
+ foreach my $key (sort keys %{$self->{meta_strings}}) {
+ push (@ret, $key, ": ", $self->{meta_strings}->{$key}, "\n");
}
return join ("", @ret);
+}
+
+# ---------------------------------------------------------------------------
+
+=item finish()
+
+Clean up an object so that it can be destroyed.
+
+=cut
+
+sub finish {
+ my ($self) = @_;
+ $self->{metadata}->finish();
}

# ---------------------------------------------------------------------------

Added: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/MsgMetadata.pm
==============================================================================
--- (empty file)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/MsgMetadata.pm Wed Feb 18 23:34:09 2004
@@ -0,0 +1,215 @@
+# $Id: MIME.pm,v 1.8 2003/10/02 22:59:00 quinlan Exp $
+
+# <@LICENSE>
+# Copyright 2004 Apache Software Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# </@LICENSE>
+
+=head1 NAME
+
+Mail::SpamAssassin::MsgMetadata - extract metadata from a message
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+This module will extract metadata from an email message.
+
+=head1 PUBLIC METHODS
+
+=over 4
+
+=cut
+
+package Mail::SpamAssassin::MsgMetadata;
+use strict;
+
+use Mail::SpamAssassin;
+use Mail::SpamAssassin::Received;
+use Mail::SpamAssassin::TextCat;
+
+use constant MAX_BODY_LINE_LENGTH => 2048;
+
+=item new()
+
+=cut
+
+sub new {
+ my ($class, $msg) = @_;
+ $class = ref($class) || $class;
+ my $self = {
+ msg => $msg
+ };
+ bless($self,$class);
+ $self;
+}
+
+sub extract {
+ my ($self, $msg, $main) = @_;
+
+ # add pointers temporarily
+ $self->{main} = $main;
+ $self->{conf} = $main->{conf};
+
+ # pre-chew Received headers
+ $self->parse_received_headers ($msg);
+
+ # and identify the language (if we're going to do that), before we
+ # run any Bayes tests, so they can use that as a token
+ $self->check_language();
+
+ $self->{main}->call_plugins ("extract_metadata", { msg => $msg });
+
+ # remove pointers to avoid circular refs, which break GC'ing
+ delete $self->{main};
+ delete $self->{conf};
+}
+
+sub finish {
+ my ($self) = @_;
+ delete $self->{msg};
+}
+
+# ---------------------------------------------------------------------------
+
+sub check_language {
+ my ($self) = @_;
+
+ my @languages = split (' ', $self->{conf}->{ok_languages});
+ if (grep { $_ eq "all" } @languages) {
+ # user doesn't care what lang it's in, so return.
+ # TODO: might want to have them as bayes tokens all the same, though.
+ # should we add a new config setting to control that? or make it a
+ # plugin?
+ return;
+ }
+
+ my $body = $self->get_rendered_body_text_array();
+ $body = join ("\n", @{$body});
+ $body =~ s/^Subject://i;
+
+ # need about 256 bytes for reasonably accurate match (experimentally derived)
+ if (length($body) < 256)
+ {
+ dbg("Message too short for language analysis");
+ return;
+ }
+
+ my @matches = Mail::SpamAssassin::TextCat::classify($self, $body);
+ $self->{textcat_matches} = \@matches;
+ my $matches_str = join(' ', @matches);
+
+ # add to metadata so Bayes gets to take a look
+ $self->{msg}->put_metadata ("X-Languages", $matches_str);
+
+ dbg ("metadata: X-Languages: $matches_str");
+}
+
+# ---------------------------------------------------------------------------
+
+sub get_rendered_body_text_array {
+ my ($self) = @_;
+
+ if (exists $self->{text_rendered}) { return $self->{text_rendered}; }
+ local ($_);
+
+ $self->{text_rendered} = [];
+
+ # Find all parts which are leaves
+ my @parts = $self->{msg}->find_parts(qr/^(?:text|message)\b/i,1);
+ return $self->{text_rendered} unless @parts;
+
+ # Go through each part
+ my $text = $self->{msg}->get_header ('subject') || '';
+ for(my $pt = 0 ; $pt <= $#parts ; $pt++ ) {
+ my $p = $parts[$pt];
+
+ my($type, $rnd) = $p->rendered(); # decode this part
+ if ( defined $rnd ) {
+ # Only text/* types are rendered ...
+ $text .= $text ? "\n$rnd" : $rnd;
+
+ # TVD - if there are multiple parts, what should we do?
+ # right now, just use the last one ...
+ $self->{html} = $p->{html_results} if ( $type eq 'text/html' );
+ }
+ else {
+ $text .= $text ? "\n".$p->decode() : $p->decode();
+ }
+ }
+
+ # whitespace handling (warning: small changes have large effects!)
+ $text =~ s/\n+\s*\n+/\f/gs; # double newlines => form feed
+ $text =~ tr/ \t\n\r\x0b\xa0/ /s; # whitespace => space
+ $text =~ tr/\f/\n/; # form feeds => newline
+
+ my @textary = split_into_array_of_short_lines ($text);
+ $self->{text_rendered} = \@textary;
+
+ return $self->{text_rendered};
+}
+
+# ---------------------------------------------------------------------------
+
+sub get_decoded_body_text_array {
+ my ($self) = @_;
+
+ if (defined $self->{text_decoded}) { return $self->{text_decoded}; }
+
+ $self->{text_decoded} = [ ];
+ local ($_);
+
+ # Find all parts which are leaves
+ my @parts = $self->{msg}->find_parts(qr/./,1);
+ return $self->{text_decoded} unless @parts;
+
+ # Go through each part
+ for(my $pt = 0 ; $pt <= $#parts ; $pt++ ) {
+ my $p = $parts[$pt];
+
+ # For below, we really only care about textual parts
+ if ( $p->{'type'} !~ /^(?:text|message)\b/i ) {
+ # remove this part from our array
+ splice @parts, $pt--, 1;
+ next;
+ }
+
+ $p->decode(); # decode this part
+ push(@{$self->{text_decoded}}, "\n") if ( @{$self->{text_decoded}} );
+ push(@{$self->{text_decoded}},
+ map { split_into_array_of_short_lines($_) } @{$p->{'decoded'}} );
+ }
+
+ return $self->{text_decoded};
+}
+
+# ---------------------------------------------------------------------------
+
+sub split_into_array_of_short_lines {
+ my @result = ();
+ foreach my $line (split (/^/m, $_[0])) {
+ while (length ($line) > MAX_BODY_LINE_LENGTH) {
+ push (@result, substr($line, 0, MAX_BODY_LINE_LENGTH));
+ substr($line, 0, MAX_BODY_LINE_LENGTH) = '';
+ }
+ push (@result, $line);
+ }
+ @result;
+}
+
+# ---------------------------------------------------------------------------
+
+#sub dbg { Mail::SpamAssassin::dbg(@_); }
+
+1;

Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm
==============================================================================
--- incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm (original)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm Wed Feb 18 23:34:09 2004
@@ -55,12 +55,9 @@
use Mail::SpamAssassin::EvalTests;
use Mail::SpamAssassin::AutoWhitelist;
use Mail::SpamAssassin::Conf;
-use Mail::SpamAssassin::Received;
use Mail::SpamAssassin::Util;
use Mail::SpamAssassin::MsgContainer;

-use constant MAX_BODY_LINE_LENGTH => 2048;
-
use vars qw{
@ISA
};
@@ -140,19 +137,7 @@
$self->{conf}->set_score_set ($set|2);
}

- # pre-chew Received headers
- $self->parse_received_headers();
-
- # and identify the language (if we're going to do that), before we
- # run any Bayes tests, so they can use that as a token
- {
- my $decoded = $self->get_decoded_stripped_body_text_array();
- $self->_check_language ($decoded);
- undef $decoded; # this is cached anyway for the main set
- }
-
- # allow plugins to add more metadata, read the stuff that's there, etc.
- $self->{main}->call_plugins ("parsed_metadata", { permsgstatus => $self });
+ $self->extract_message_metadata();

{
# Here, we launch all the DNS RBL queries and let them run while we
@@ -1000,114 +985,37 @@
}

###########################################################################
-# Non-public methods from here on.

-sub get_decoded_body_text_array {
+sub extract_message_metadata {
my ($self) = @_;

- if (defined $self->{decoded_body_text_array}) { return $self->{decoded_body_text_array}; }
-
- local ($_);
+ $self->{msg}->extract_message_metadata($self->{main});

- $self->{decoded_body_text_array} = [ ];
- $self->{found_encoding_base64} = 0;
- $self->{found_encoding_quoted_printable} = 0;
-
- # Find all parts which are leaves
- my @parts = $self->{msg}->find_parts(qr/./,1);
- return $self->{decoded_body_text_array} unless @parts;
-
- # Go through each part
- for(my $pt = 0 ; $pt <= $#parts ; $pt++ ) {
- my $p = $parts[$pt];
-
- # Mark if there's a part with base64 or qp encoding. If we've already found at least one of each,
- # don't bother looking for anymore of them.
- unless ( $self->{found_encoding_base64} && $self->{found_encoding_quoted_printable} ) {
- my $cte = $p->get_header ('Content-Transfer-Encoding');
- if (defined $cte && $cte =~ /quoted-printable/i) {
- $self->{found_encoding_quoted_printable} = 1;
- }
- elsif (defined $cte && $cte =~ /base64/i) {
- $self->{found_encoding_base64} = 1;
- }
- }
-
- # For below, we really only care about textual parts
- if ( $p->{'type'} !~ /^(?:text|message)\b/i ) {
- # remove this part from our array
- splice @parts, $pt--, 1;
- next;
- }
-
- $p->decode(); # decode this part
- push(@{$self->{decoded_body_text_array}}, "\n") if ( @{$self->{decoded_body_text_array}} );
- push(@{$self->{decoded_body_text_array}},
- map { $self->split_into_array_of_short_lines($_) } @{$p->{'decoded'}} );
+ foreach my $item (qw(
+ relays_trusted relays_trusted_str num_relays_trusted
+ relays_untrusted relays_untrusted_str num_relays_untrusted
+ ))
+ {
+ $self->{$item} = $self->{msg}->{metadata}->{$item};
}

- return $self->{decoded_body_text_array};
-}
-
-sub split_into_array_of_short_lines {
- my $self = shift;
+ $self->{tag_data}->{RELAYSTRUSTED} = $self->{relays_trusted_str};
+ $self->{tag_data}->{RELAYSUNTRUSTED} = $self->{relays_untrusted_str};
+ $self->{tag_data}->{LANGUAGES} = $self->{msg}->{metadata}->{"X-Languages"};

- my @result = ();
- foreach my $line (split (/^/m, $_[0])) {
- while (length ($line) > MAX_BODY_LINE_LENGTH) {
- push (@result, substr($line, 0, MAX_BODY_LINE_LENGTH));
- substr($line, 0, MAX_BODY_LINE_LENGTH) = '';
- }
- push (@result, $line);
- }
- @result;
+ # allow plugins to add more metadata, read the stuff that's there, etc.
+ $self->{main}->call_plugins ("parsed_metadata", { permsgstatus => $self });
}

-
###########################################################################
+# Non-public methods from here on.

-# this really wants to get the rendered version ...
-sub get_decoded_stripped_body_text_array {
- my ($self) = @_;
-
- if (defined $self->{decoded_stripped_body_text_array}) { return $self->{decoded_stripped_body_text_array}; }
-
- local ($_);
-
- $self->{decoded_stripped_body_text_array} = [];
-
- # Find all parts which are leaves
- my @parts = $self->{msg}->find_parts(qr/^(?:text|message)\b/i,1);
- return $self->{decoded_stripped_body_text_array} unless @parts;
-
- # Go through each part
- my $text = $self->get('subject') || '';
- for(my $pt = 0 ; $pt <= $#parts ; $pt++ ) {
- my $p = $parts[$pt];
-
- my($type, $rnd) = $p->rendered(); # decode this part
- if ( defined $rnd ) {
- # Only text/* types are rendered ...
- $text .= $text ? "\n$rnd" : $rnd;
-
- # TVD - if there are multiple parts, what should we do?
- # right now, just use the last one ...
- $self->{html} = $p->{html_results} if ( $type eq 'text/html' );
- }
- else {
- $text .= $text ? "\n".$p->decode() : $p->decode();
- }
- }
-
- # whitespace handling (warning: small changes have large effects!)
- $text =~ s/\n+\s*\n+/\f/gs; # double newlines => form feed
- $text =~ tr/ \t\n\r\x0b\xa0/ /s; # whitespace => space
- $text =~ tr/\f/\n/; # form feeds => newline
-
- my @textary = $self->split_into_array_of_short_lines ($text);
- $self->{decoded_stripped_body_text_array} = \@textary;
+sub get_decoded_body_text_array {
+ return $_[0]->{msg}->{metadata}->get_decoded_body_text_array();
+}

- return $self->{decoded_stripped_body_text_array};
+sub get_decoded_stripped_body_text_array {
+ return $_[0]->{msg}->{metadata}->get_decoded_body_text_array();
}

###########################################################################

Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm
==============================================================================
--- incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm (original)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm Wed Feb 18 23:34:09 2004
@@ -214,10 +214,31 @@
}

# ---------------------------------------------------------------------------
+=item $plugin->extract_metadata ( { options ... } )
+
+Signals that a message is being mined for metadata. Some plugins may wish
+to add their own metadata as well.
+
+=over 4
+
+=item msg
+
+The C<Mail::SpamAssassin::MsgContainer> object for this message.
+
+=back
+
+=cut
+
+sub extract_metadata {
+ my ($self, $opts) = @_;
+ return 0; # implemented by subclasses, no-op by default
+}
+
+# ---------------------------------------------------------------------------
=item $plugin->parsed_metadata ( { options ... } )

Signals that a message's metadata has been parsed, and can now be
-accessed, or supplemented, by the plugin.
+accessed by the plugin.

=over 4


Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Received.pm
==============================================================================
--- incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Received.pm (original)
+++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Received.pm Wed Feb 18 23:34:09 2004
@@ -41,17 +41,22 @@
package Mail::SpamAssassin::Received;
1;

-package Mail::SpamAssassin::PerMsgStatus;
-
+package Mail::SpamAssassin::MsgMetadata;
use strict;
use bytes;

use Mail::SpamAssassin::Dns;
+use Mail::SpamAssassin::PerMsgStatus;

use vars qw{
- $LOCALHOST $CCTLDS_WITH_SUBDELEGATION
+ $LOCALHOST $CCTLDS_WITH_SUBDELEGATION $IP_ADDRESS $IPV4_ADDRESS
+ $IP_IN_RESERVED_RANGE
};

+$IPV4_ADDRESS = $Mail::SpamAssassin::PerMsgStatus::IPV4_ADDRESS;
+$IP_ADDRESS = $Mail::SpamAssassin::PerMsgStatus::IP_ADDRESS;
+$IP_IN_RESERVED_RANGE = $Mail::SpamAssassin::PerMsgStatus::IP_IN_RESERVED_RANGE;
+
$LOCALHOST = qr{(?:
localhost(?:\.localdomain|)|
127\.0\.0\.1|
@@ -74,11 +79,11 @@
# ---------------------------------------------------------------------------

sub parse_received_headers {
- my ($self) = @_;
+ my ($self, $msg) = @_;

$self->{relays} = [ ];

- my $hdrs = $self->get('Received');
+ my $hdrs = $msg->get_header('Received');
$hdrs ||= '';

$hdrs =~ s/\n[ \t]+/ /gs;
@@ -299,20 +304,17 @@
# so protect against that here. These will not appear in the final
# message; they're just used internally.

-# if ($self->{msg}->can ("delete_header")) {
-# $self->{msg}->delete_header ("X-Spam-Relays-Trusted");
-# $self->{msg}->delete_header ("X-Spam-Relays-Untrusted");
-#
-# if ($self->{msg}->can ("put_metadata")) {
-# $self->{msg}->put_metadata ("X-Spam-Relays-Trusted",
-# $self->{relays_trusted_str});
-# $self->{msg}->put_metadata ("X-Spam-Relays-Untrusted",
-# $self->{relays_untrusted_str});
-# }
-# }
-
- $self->{tag_data}->{RELAYSTRUSTED} = $self->{relays_trusted_str};
- $self->{tag_data}->{RELAYSUNTRUSTED} = $self->{relays_untrusted_str};
+ if ($self->{msg}->can ("delete_header")) {
+ $self->{msg}->delete_header ("X-Spam-Relays-Trusted");
+ $self->{msg}->delete_header ("X-Spam-Relays-Untrusted");
+
+ if ($self->{msg}->can ("put_metadata")) {
+ $self->{msg}->put_metadata ("X-Spam-Relays-Trusted",
+ $self->{relays_trusted_str});
+ $self->{msg}->put_metadata ("X-Spam-Relays-Untrusted",
+ $self->{relays_untrusted_str});
+ }
+ }

# be helpful; save some cumbersome typing
$self->{num_relays_trusted} = scalar (@{$self->{relays_trusted}});
@@ -1134,6 +1136,8 @@
my ($domain) = @_;
return ($domain =~ /\.${CCTLDS_WITH_SUBDELEGATION}$/);
}
+
+sub dbg { Mail::SpamAssassin::dbg(@_); }

# ---------------------------------------------------------------------------