1# <@LICENSE> 2# Licensed to the Apache Software Foundation (ASF) under one or more 3# contributor license agreements. See the NOTICE file distributed with 4# this work for additional information regarding copyright ownership. 5# The ASF licenses this file to you under the Apache License, Version 2.0 6# (the "License"); you may not use this file except in compliance with 7# the License. You may obtain a copy of the License at: 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# </@LICENSE> 17 18=head1 NAME 19 20RelayCountry - add message metadata indicating the country code of each relay 21 22=head1 SYNOPSIS 23 24 loadplugin Mail::SpamAssassin::Plugin::RelayCountry 25 26=head1 DESCRIPTION 27 28The RelayCountry plugin attempts to determine the domain country codes 29of each relay used in the delivery path of messages and add that information 30to the message metadata. 31 32Following metadata headers and tags are added: 33 34 X-Relay-Countries _RELAYCOUNTRY_ 35 All untrusted relays. Contains all relays starting from the 36 trusted_networks border. This method has been used by default since 37 early SA versions. 38 39 X-Relay-Countries-External _RELAYCOUNTRYEXT_ 40 All external relays. Contains all relays starting from the 41 internal_networks border. Could be useful in some cases when 42 trusted/msa_networks extend beyond the internal border and those 43 need to be checked too. 44 45 X-Relay-Countries-All _RELAYCOUNTRYALL_ 46 All possible relays (internal + external). 47 48 X-Relay-Countries-Auth _RELAYCOUNTRYAUTH_ 49 Auth will contain all relays starting from the first relay that used 50 authentication. For example, this could be used to check for hacked 51 local users coming in from unexpected countries. If there are no 52 authenticated relays, this will be empty. 53 54=head1 REQUIREMENT 55 56This plugin uses Mail::SpamAssassin::GeoDB and requires a module supported 57by it, for example MaxMind::DB::Reader (GeoIP2). 58 59=cut 60 61package Mail::SpamAssassin::Plugin::RelayCountry; 62 63use Mail::SpamAssassin::Plugin; 64use Mail::SpamAssassin::Logger; 65use strict; 66use warnings; 67# use bytes; 68use re 'taint'; 69 70our @ISA = qw(Mail::SpamAssassin::Plugin); 71 72my $db; 73my $dbv6; 74my $db_info; # will hold database info 75my $db_type; # will hold database type 76 77# constructor: register the eval rule 78sub new { 79 my $class = shift; 80 my $mailsaobject = shift; 81 82 # some boilerplate... 83 $class = ref($class) || $class; 84 my $self = $class->SUPER::new($mailsaobject); 85 bless ($self, $class); 86 87 # we need GeoDB country 88 $self->{main}->{geodb_wanted}->{country} = 1; 89 90 return $self; 91} 92 93sub extract_metadata { 94 my ($self, $opts) = @_; 95 my $pms = $opts->{permsgstatus}; 96 97 return if $self->{relaycountry_disabled}; 98 99 if (!$self->{main}->{geodb} || 100 !$self->{main}->{geodb}->can('country')) { 101 dbg("metadata: RelayCountry: plugin disabled, GeoDB country not available"); 102 $self->{relaycountry_disabled} = 1; 103 return; 104 } 105 106 my $msg = $opts->{msg}; 107 my $geodb = $self->{main}->{geodb}; 108 109 my @cc_untrusted; 110 foreach my $relay (@{$msg->{metadata}->{relays_untrusted}}) { 111 my $ip = $relay->{ip}; 112 my $cc = $geodb->get_country($ip); 113 push @cc_untrusted, $cc; 114 } 115 116 my @cc_external; 117 foreach my $relay (@{$msg->{metadata}->{relays_external}}) { 118 my $ip = $relay->{ip}; 119 my $cc = $geodb->get_country($ip); 120 push @cc_external, $cc; 121 } 122 123 my @cc_auth; 124 my $found_auth; 125 foreach my $relay (@{$msg->{metadata}->{relays_trusted}}) { 126 if ($relay->{auth}) { 127 $found_auth = 1; 128 } 129 if ($found_auth) { 130 my $ip = $relay->{ip}; 131 my $cc = $geodb->get_country($ip); 132 push @cc_auth, $cc; 133 } 134 } 135 136 my @cc_all; 137 foreach my $relay (@{$msg->{metadata}->{relays_internal}}, @{$msg->{metadata}->{relays_external}}) { 138 my $ip = $relay->{ip}; 139 my $cc = $geodb->get_country($ip); 140 push @cc_all, $cc; 141 } 142 143 my $ccstr = join(' ', @cc_untrusted); 144 $msg->put_metadata("X-Relay-Countries", $ccstr); 145 dbg("metadata: X-Relay-Countries: $ccstr"); 146 $pms->set_tag("RELAYCOUNTRY", @cc_untrusted == 1 ? $cc_untrusted[0] : \@cc_untrusted); 147 148 $ccstr = join(' ', @cc_external); 149 $msg->put_metadata("X-Relay-Countries-External", $ccstr); 150 dbg("metadata: X-Relay-Countries-External: $ccstr"); 151 $pms->set_tag("RELAYCOUNTRYEXT", @cc_external == 1 ? $cc_external[0] : \@cc_external); 152 153 $ccstr = join(' ', @cc_auth); 154 $msg->put_metadata("X-Relay-Countries-Auth", $ccstr); 155 dbg("metadata: X-Relay-Countries-Auth: $ccstr"); 156 $pms->set_tag("RELAYCOUNTRYAUTH", @cc_auth == 1 ? $cc_auth[0] : \@cc_auth); 157 158 $ccstr = join(' ', @cc_all); 159 $msg->put_metadata("X-Relay-Countries-All", $ccstr); 160 dbg("metadata: X-Relay-Countries-All: $ccstr"); 161 $pms->set_tag("RELAYCOUNTRYALL", @cc_all == 1 ? $cc_all[0] : \@cc_all); 162} 163 1641; 165