View | Details | Raw Unified | Return to bug 7529
Collapse All | Expand All

(-)lib/Mail/SpamAssassin/Plugin/RelayCountry.pm (-46 / +87 lines)
Lines 32-39 Link Here
32
32
33
=head1 REQUIREMENT
33
=head1 REQUIREMENT
34
34
35
This plugin requires the Geo::IP module from CPAN. For backward
35
This plugin requires the IP::Country::DB_File module from CPAN. For backward
36
compatibility IP::Country::Fast is used if Geo::IP is not installed.
36
compatibility IP::Country::Fast is used if IP::Country::DB_File is not installed.
37
37
38
=cut
38
=cut
39
39
Lines 49-100 Link Here
49
49
50
our @ISA = qw(Mail::SpamAssassin::Plugin);
50
our @ISA = qw(Mail::SpamAssassin::Plugin);
51
51
52
my ($db, $dbv6);
52
my $db;
53
my $ip_to_cc; # will hold a sub() for the lookup
53
my $ip_to_cc; # will hold a sub() for the lookup
54
my $db_info;  # will hold a sub() for database info
54
my $db_info;  # will hold a sub() for database info
55
my $geoiploaded;
55
56
56
# Try to load Geo::IP first
57
eval {
58
  require Geo::IP;
59
  $db = Geo::IP->open_type(Geo::IP->GEOIP_COUNTRY_EDITION, Geo::IP->GEOIP_STANDARD);
60
  die "GeoIP.dat not found" unless $db;
61
  # IPv6 requires version Geo::IP 1.39+ with GeoIP C API 1.4.7+
62
  if (Geo::IP->VERSION >= 1.39 && Geo::IP->api eq 'CAPI') {
63
    $dbv6 = Geo::IP->open_type(Geo::IP->GEOIP_COUNTRY_EDITION_V6, Geo::IP->GEOIP_STANDARD);
64
    if (!$dbv6) {
65
      dbg("metadata: RelayCountry: IPv6 support not enabled, GeoIPv6.dat not found");
66
    }
67
  } else {
68
    dbg("metadata: RelayCountry: IPv6 support not enabled, versions Geo::IP 1.39, GeoIP C API 1.4.7 required");
69
  }
70
  $ip_to_cc = sub {
71
    if ($dbv6 && $_[0] =~ /:/) {
72
      return $dbv6->country_code_by_addr_v6($_[0]) || "XX";
73
    } else {
74
      return $db->country_code_by_addr($_[0]) || "XX";
75
    }
76
  };
77
  $db_info = sub { return "Geo::IP " . ($db->database_info || '?') };
78
  1;
79
} or do {
80
  my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
81
  dbg("metadata: RelayCountry: failed to load 'Geo::IP', skipping: $eval_stat");
82
  # Try IP::Country::Fast as backup
83
  eval {
84
    require IP::Country::Fast;
85
    $db = IP::Country::Fast->new();
86
    $ip_to_cc = sub {
87
      return $db->inet_atocc($_[0]) || "XX";
88
    };
89
    $db_info = sub { return "IP::Country::Fast ".localtime($db->db_time()); };
90
    1;
91
  } or do {
92
    my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
93
    dbg("metadata: RelayCountry: failed to load 'IP::Country::Fast', skipping: $eval_stat");
94
    return 1;
95
  };
96
};
97
98
# constructor: register the eval rule
57
# constructor: register the eval rule
99
sub new {
58
sub new {
100
  my $class = shift;
59
  my $class = shift;
Lines 104-115 Link Here
104
  $class = ref($class) || $class;
63
  $class = ref($class) || $class;
105
  my $self = $class->SUPER::new($mailsaobject);
64
  my $self = $class->SUPER::new($mailsaobject);
106
  bless ($self, $class);
65
  bless ($self, $class);
66
67
  $self->set_config($mailsaobject->{conf});
107
  return $self;
68
  return $self;
108
}
69
}
109
70
71
sub set_config {
72
  my ($self, $conf) = @_;
73
  my @cmds;
74
75
=head1 USER PREFERENCES
76
77
The following options can be used in both site-wide (C<local.cf>) and
78
user-specific (C<user_prefs>) configuration files to customize how
79
SpamAssassin handles incoming email messages.
80
81
=over 4
82
83
=item country_db_path STRING
84
85
This option tells SpamAssassin where to find the country database.
86
87
=back
88
89
=cut
90
91
  push (@cmds, {
92
    setting => 'country_db_path',
93
    default => undef,
94
    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
95
    code => sub {
96
      my ($self, $key, $value, $line) = @_;
97
      if (!defined $value || !length $value) {
98
        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
99
      }
100
      if (!-f $value) {
101
        info("config: country_db_path \"$value\" is not accessible");
102
        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
103
      }
104
105
      $self->{country_db_path} = $value;
106
    }
107
  });
108
109
  $conf->{parser}->register_commands(\@cmds);
110
}
111
110
sub extract_metadata {
112
sub extract_metadata {
111
  my ($self, $opts) = @_;
113
  my ($self, $opts) = @_;
114
  my $geo;
115
  my $cc;
112
116
117
  my $conf_country_db_path = $self->{'main'}{'resolver'}{'conf'}->{country_db_path};
118
119
  # Try to load IP::Country::DB_File first
120
  eval {
121
    require IP::Country::DB_File;
122
    $db = IP::Country::DB_File->new($conf_country_db_path);
123
    die "Country db not found, please see build_ipcc.pl(1)" unless $db;
124
    $geoiploaded = 1;
125
    $db_info = sub { return "IP::Country::DB_File ".localtime($db->db_time()); };
126
    1;
127
  } or do {
128
    my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
129
    dbg("metadata: RelayCountry: failed to load 'IP::Country::DB_File', skipping: $eval_stat");
130
    # Try IP::Country::Fast as backup
131
    $geoiploaded = 0;
132
    eval {
133
      require IP::Country::Fast;
134
      $db = IP::Country::Fast->new();
135
      $db_info = sub { return "IP::Country::Fast ".localtime($db->db_time()); };
136
      1;
137
    } or do {
138
      my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
139
      dbg("metadata: RelayCountry: failed to load 'IP::Country::Fast', skipping: $eval_stat");
140
      return 1;
141
    };
142
  };
143
113
  return 1 unless $db;
144
  return 1 unless $db;
114
145
115
  dbg("metadata: RelayCountry: Using database: ".$db_info->());
146
  dbg("metadata: RelayCountry: Using database: ".$db_info->());
Lines 117-126 Link Here
117
148
118
  my $countries = '';
149
  my $countries = '';
119
  my $IP_PRIVATE = IP_PRIVATE;
150
  my $IP_PRIVATE = IP_PRIVATE;
151
  my $IPV4_ADDRESS = IPV4_ADDRESS;
120
  foreach my $relay (@{$msg->{metadata}->{relays_untrusted}}) {
152
  foreach my $relay (@{$msg->{metadata}->{relays_untrusted}}) {
121
    my $ip = $relay->{ip};
153
    my $ip = $relay->{ip};
122
    # Private IPs will always be returned as '**'
154
    # Private IPs will always be returned as '**'
123
    my $cc = $ip =~ /^$IP_PRIVATE$/o ? '**' : $ip_to_cc->($ip);
155
    if ( $geoiploaded ) {
156
	if ( $ip !~ /^$IPV4_ADDRESS$/o ) {
157
	    $cc = $db->inet6_atocc($ip)
158
	} else {
159
	    $cc = $db->inet_atocc($ip)
160
	}
161
    } else {
162
	$geo = $db->inet_atocc($ip) || "XX";
163
    	$cc = $ip =~ /^$IP_PRIVATE$/o ? '**' : $geo;
164
    }
124
    $countries .= $cc." ";
165
    $countries .= $cc." ";
125
  }
166
  }
126
167

Return to bug 7529