sub parse_received_headers()

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});
}