--- init.pre-orig 2015-01-23 09:39:49.000000000 -0800 +++ init.pre 2015-01-23 09:42:13.000000000 -0800 @@ -17,7 +17,7 @@ # RelayCountry - add metadata for Bayes learning, marking the countries # a message was relayed through # -# Note: This requires the IP::Country::Fast Perl module +# Note: This requires the Geo::IP Perl module # loadplugin Mail::SpamAssassin::Plugin::RelayCountry --- RelayCountry.pm-orig 2015-01-23 09:38:49.000000000 -0800 +++ RelayCountry.pm 2015-01-23 09:41:15.000000000 -0800 @@ -25,14 +25,15 @@ =head1 DESCRIPTION -By the RelayCountry plugin attempts to determine the domain country -codes of each relay used in the delivery path of messages and add that -information to the message metadata as "X-Relay-Countries", or -the C<_RELAYCOUNTRY_> header markup. +The RelayCountry plugin attempts to determine the domain country codes +of each relay used in the delivery path of messages and add that information +to the message metadata as "X-Relay-Countries", or the C<_RELAYCOUNTRY_> +header markup. =head1 REQUIREMENT -This plugin requires the IP::Country module from CPAN. +This plugin requires the Geo::IP module from CPAN. For backward +compatibility IP::Country::Fast is used if Geo::IP is not installed. =cut @@ -40,6 +41,7 @@ use Mail::SpamAssassin::Plugin; use Mail::SpamAssassin::Logger; +use Mail::SpamAssassin::Constants qw(:ip); use strict; use warnings; use bytes; @@ -48,6 +50,52 @@ use vars qw(@ISA); @ISA = qw(Mail::SpamAssassin::Plugin); +my ($db, $dbv6); +my $ip_to_cc; # will hold a sub() for the lookup +my $db_info; # will hold a sub() for database info + +# Try to load Geo::IP first +eval { + require Geo::IP; + $db = Geo::IP->open_type(Geo::IP->GEOIP_COUNTRY_EDITION, Geo::IP->GEOIP_STANDARD); + die "GeoIP.dat not found" unless $db; + # IPv6 requires version Geo::IP 1.39+ with GeoIP C API 1.4.7+ + if (Geo::IP->VERSION >= 1.39 && Geo::IP->api eq 'CAPI') { + $dbv6 = Geo::IP->open_type(Geo::IP->GEOIP_COUNTRY_EDITION_V6, Geo::IP->GEOIP_STANDARD); + if (!$dbv6) { + dbg("metadata: RelayCountry: IPv6 support not enabled, GeoIPv6.dat not found"); + } + } else { + dbg("metadata: RelayCountry: IPv6 support not enabled, versions Geo::IP 1.39, GeoIP C API 1.4.7 required"); + } + $ip_to_cc = sub { + if ($dbv6 && $_[0] =~ /:/) { + return $dbv6->country_code_by_addr_v6($_[0]) || "XX"; + } else { + return $db->country_code_by_addr($_[0]) || "XX"; + } + }; + $db_info = sub { return "Geo::IP " . ($db->database_info || '?') }; + 1; +} or do { + my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; + dbg("metadata: RelayCountry: failed to load 'Geo::IP', skipping: $eval_stat"); + # Try IP::Country::Fast as backup + eval { + require IP::Country::Fast; + $db = IP::Country::Fast->new(); + $ip_to_cc = sub { + return $db->inet_atocc($_[0]) || "XX"; + }; + $db_info = sub { return "IP::Country::Fast ".localtime($db->db_time()); }; + 1; + } or do { + my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; + dbg("metadata: RelayCountry: failed to load 'IP::Country::Fast', skipping: $eval_stat"); + return 1; + }; +}; + # constructor: register the eval rule sub new { my $class = shift; @@ -63,24 +111,17 @@ sub extract_metadata { my ($self, $opts) = @_; - my $reg; - - eval { - require IP::Country::Fast; - $reg = IP::Country::Fast->new(); - 1; - } or do { - my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; - dbg("metadata: failed to load 'IP::Country::Fast', skipping: $eval_stat"); - return 1; - }; + return 1 unless $db; + dbg("metadata: RelayCountry: Using database: ".$db_info->()); my $msg = $opts->{msg}; my $countries = ''; + my $IP_PRIVATE = IP_PRIVATE; foreach my $relay (@{$msg->{metadata}->{relays_untrusted}}) { my $ip = $relay->{ip}; - my $cc = $reg->inet_atocc($ip) || "XX"; + # Private IPs will always be returned as '**' + my $cc = $ip =~ /^$IP_PRIVATE$/o ? '**' : $ip_to_cc->($ip); $countries .= $cc." "; } @@ -93,8 +134,14 @@ sub parsed_metadata { my ($self, $opts) = @_; + + return 1 unless $db; + + my $countries = + $opts->{permsgstatus}->get_message->get_metadata('X-Relay-Countries'); + my @c_list = split(' ', $countries); $opts->{permsgstatus}->set_tag ("RELAYCOUNTRY", - $opts->{permsgstatus}->get_message->get_metadata('X-Relay-Countries')); + @c_list == 1 ? $c_list[0] : \@c_list); return 1; }