in lib/Mail/SpamAssassin/Message/Metadata/Received.pm [59:302]
sub parse_received_headers {
my ($self, $pms, $msg) = @_;
# a caller may assert that a message is coming from inside or from an
# authenticated roaming users; this info may not be available in mail
# header section, e.g. in case of nonstandard authentication mechanisms
my $originating; # boolean
if (exists $msg->{suppl_attrib}->{originating}) {
$originating = $msg->{suppl_attrib}->{originating} || 0;
dbg("metadata: set originating from suppl_attrib: %s", $originating);
}
$self->{relays_trusted} = [ ];
$self->{num_relays_trusted} = 0;
$self->{relays_trusted_str} = '';
$self->{relays_untrusted} = [ ];
$self->{num_relays_untrusted} = 0;
$self->{relays_untrusted_str} = '';
$self->{relays_internal} = [ ];
$self->{num_relays_internal} = 0;
$self->{relays_internal_str} = '';
$self->{relays_external} = [ ];
$self->{num_relays_external} = 0;
$self->{relays_external_str} = '';
$self->{num_relays_unparseable} = 0;
$self->{last_trusted_relay_index} = -1; # last counting from the top,
$self->{last_internal_relay_index} = -1; # first in time
$self->{allow_mailfetch_markers} = 1; # This needs to be set for the
# first Received: header
# now figure out what relays are trusted...
my $trusted = $pms->{main}->{conf}->{trusted_networks};
my $internal = $pms->{main}->{conf}->{internal_networks};
my $msa = $pms->{main}->{conf}->{msa_networks};
my $did_user_specify_trust = $pms->{main}->{conf}->{trusted_networks_configured};
my $did_user_specify_internal = $pms->{main}->{conf}->{internal_networks_configured};
my $in_trusted = 1;
my $in_internal = 1;
my $found_msa = 0;
unless ($did_user_specify_trust && $did_user_specify_internal) {
if (!$did_user_specify_trust && !$did_user_specify_internal) {
dbg('config: trusted_networks are not configured; it is recommended '.
'that you configure trusted_networks manually');
} elsif (!$did_user_specify_internal) {
# use 'trusted' for 'internal'; compatibility with SpamAssassin 2.60
$internal = $trusted;
dbg('config: internal_networks not configured, using trusted_networks '.
'configuration for internal_networks; if you really want '.
'internal_networks to only contain the required 127/8 add '.
"'internal_networks !0/0' to your configuration");
} else {
# use 'internal' for 'trusted'; I don't know why we let people define
# internal without trusted, but we do... and we rely on trusted being set
$trusted = $internal;
dbg('config: trusted_networks not configured, using internal_networks '.
'configuration for trusted_networks');
}
}
my @hdrs = $msg->get_header('Received');
# Now add the single line headers like X-Originating-IP. (bug 5680)
# we convert them into synthetic "Received" headers so we can share
# code below.
foreach my $header (@{$pms->{main}->{conf}->{originating_ip_headers}}) {
my $str = $msg->get_header($header);
next unless ($str && $str =~ m/($IP_ADDRESS)/);
push @hdrs, "from X-Originating-IP: $1\n";
}
foreach my $line ( @hdrs ) {
# qmail-scanner support hack: we may have had one of these set from the
# previous (read: more recent) Received header. if so, add it on to this
# header's set, since that's the handover it was describing.
my $qms_env_from;
if ($self->{qmail_scanner_env_from}) {
$qms_env_from = $self->{qmail_scanner_env_from};
delete $self->{qmail_scanner_env_from};
}
$line =~ s/\n[ \t]+/ /gs;
my $relay = $self->parse_received_line ($line);
if (!defined $relay) {
dbg("received-header: unparseable: $line");
$self->{num_relays_unparseable}++;
}
# undefined or 0 means there's no result, so goto the next header
unless ($relay) {
$self->{last_trusted_relay_index}++ if $in_trusted;
$self->{last_internal_relay_index}++ if $in_internal;
next;
}
# hack for qmail-scanner, as described above; add in the saved
# metadata
if ($qms_env_from) {
$relay->{envfrom} = $qms_env_from;
$self->make_relay_as_string($relay);
}
# relay status only changes when we're still in the trusted portion of the
# relays and we haven't yet found an MSA
if ($in_trusted && !$found_msa) {
unless ($did_user_specify_trust || $did_user_specify_internal) {
# OK, infer the trusted/untrusted handover, we don't have real info
my $inferred_as_trusted = 0;
# if the 'from' IP addr is in a reserved net range, it's not on
# the public internet.
if ($relay->{ip_private}) {
dbg("received-header: 'from' ".$relay->{ip}." has private IP");
$inferred_as_trusted = 1;
}
# if we find authentication tokens in the received header we can extend
# the trust boundary to that host
if ($relay->{auth}) {
dbg("received-header: authentication method ".$relay->{auth});
$inferred_as_trusted = 1;
}
# if the user didn't specify any trusted/internal config, everything
# we assume as trusted is also internal, just like we'd do if they
# specified trusted but not any internal networks or vice versa
if (!$inferred_as_trusted) {
dbg("received-header: do not trust any hosts from here on");
$in_trusted = 0;
$in_internal = 0;
}
} else {
# trusted_networks matches?
if (!$relay->{auth} && !$trusted->contains_ip($relay->{ip})) {
if (!$originating) {
$in_trusted = 0; # break the trust chain
} else { # caller asserts a msg was submitted from inside or auth'd
$found_msa = 1; # let's assume the previous hop was actually
# an MSA, and propagate trust from here on
dbg('received-header: originating, '.
'%s and remaining relays will be considered trusted%s',
$relay->{ip}, !$in_internal ? '' : ', but no longer internal');
}
$in_internal = 0; # if it's not trusted it's not internal
} else {
# internal_networks matches?
if ($in_internal && !$relay->{auth} && !$internal->contains_ip($relay->{ip})) {
$in_internal = 0;
}
# msa_networks matches?
if ($msa->contains_ip($relay->{ip})) {
dbg('received-header: found MSA relay, remaining relays will be'.
' considered trusted: '.($in_trusted ? 'yes' : 'no').
' internal: '.($in_internal ? 'yes' : 'no'));
$found_msa = 1;
$relay->{msa} = 1;
}
}
}
}
dbg("received-header: relay ".$relay->{ip}.
" trusted? ".($in_trusted ? "yes" : "no").
" internal? ".($in_internal ? "yes" : "no").
" msa? ".($relay->{msa} ? "yes" : "no"));
$relay->{internal} = $in_internal;
$relay->{msa} ||= 0;
# be sure to mark up the as_string version for users too
$relay->{as_string} =~ s/ intl=\d / intl=$relay->{internal} /;
$relay->{as_string} =~ s/ msa=\d / msa=$relay->{msa} /;
if ($in_trusted) {
push (@{$self->{relays_trusted}}, $relay);
$self->{allow_mailfetch_markers} = 1;
$self->{last_trusted_relay_index}++;
} else {
push (@{$self->{relays_untrusted}}, $relay);
$self->{allow_mailfetch_markers} = 0;
}
if ($in_internal) {
push (@{$self->{relays_internal}}, $relay);
$self->{last_internal_relay_index}++;
} else {
push (@{$self->{relays_external}}, $relay);
}
}
$self->{relays_trusted_str} = join(' ', map { $_->{as_string} }
@{$self->{relays_trusted}});
$self->{relays_untrusted_str} = join(' ', map { $_->{as_string} }
@{$self->{relays_untrusted}});
$self->{relays_internal_str} = join(' ', map { $_->{as_string} }
@{$self->{relays_internal}});
$self->{relays_external_str} = join(' ', map { $_->{as_string} }
@{$self->{relays_external}});
# OK, we've now split the relay list into trusted and untrusted.
# add the stringified representation to the message object, so Bayes
# and rules can use it. Note that rule_tests.t does not impl put_metadata,
# 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");
$self->{msg}->delete_header ("X-Spam-Relays-Internal");
$self->{msg}->delete_header ("X-Spam-Relays-External");
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->{msg}->put_metadata ("X-Spam-Relays-Internal",
$self->{relays_internal_str});
$self->{msg}->put_metadata ("X-Spam-Relays-External",
$self->{relays_external_str});
}
}
# be helpful; save some cumbersome typing
$self->{num_relays_trusted} = scalar (@{$self->{relays_trusted}});
$self->{num_relays_untrusted} = scalar (@{$self->{relays_untrusted}});
$self->{num_relays_internal} = scalar (@{$self->{relays_internal}});
$self->{num_relays_external} = scalar (@{$self->{relays_external}});
dbg("metadata: X-Spam-Relays-Trusted: ".$self->{relays_trusted_str});
dbg("metadata: X-Spam-Relays-Untrusted: ".$self->{relays_untrusted_str});
dbg("metadata: X-Spam-Relays-Internal: ".$self->{relays_internal_str});
dbg("metadata: X-Spam-Relays-External: ".$self->{relays_external_str});
}