You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by he...@apache.org on 2018/09/22 13:40:58 UTC

svn commit: r1841681 - in /spamassassin/trunk: ./ lib/Mail/SpamAssassin/ lib/Mail/SpamAssassin/Plugin/ t/ t/data/geodb/ t/data/spam/

Author: hege
Date: Sat Sep 22 13:40:58 2018
New Revision: 1841681

URL: http://svn.apache.org/viewvc?rev=1841681&view=rev
Log:
Bug 7634 - GeoDB module for unified geodatabase handling

Added:
    spamassassin/trunk/lib/Mail/SpamAssassin/GeoDB.pm
    spamassassin/trunk/t/data/geodb/
    spamassassin/trunk/t/data/geodb/GeoIP2-City.mmdb   (with props)
    spamassassin/trunk/t/data/geodb/GeoIP2-Country.mmdb   (with props)
    spamassassin/trunk/t/data/geodb/GeoIP2-ISP.mmdb   (with props)
    spamassassin/trunk/t/data/geodb/GeoIPCity.dat   (with props)
    spamassassin/trunk/t/data/geodb/GeoIPISP.dat   (with props)
    spamassassin/trunk/t/data/geodb/create_GeoIP2-City.pl
    spamassassin/trunk/t/data/geodb/create_GeoIP2-Country.pl
    spamassassin/trunk/t/data/geodb/create_GeoIP2-ISP.pl
    spamassassin/trunk/t/data/geodb/create_GeoIPCity.README
    spamassassin/trunk/t/data/geodb/create_GeoIPISP.README
    spamassassin/trunk/t/urilocalbl_fast.t   (with props)
    spamassassin/trunk/t/urilocalbl_geoip2.t   (with props)
Modified:
    spamassassin/trunk/MANIFEST
    spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/RelayCountry.pm
    spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URILocalBL.pm
    spamassassin/trunk/t/data/spam/relayUS.eml
    spamassassin/trunk/t/relaycountry_fast.t
    spamassassin/trunk/t/relaycountry_geoip.t
    spamassassin/trunk/t/relaycountry_geoip2.t
    spamassassin/trunk/t/urilocalbl_geoip.t

Modified: spamassassin/trunk/MANIFEST
URL: http://svn.apache.org/viewvc/spamassassin/trunk/MANIFEST?rev=1841681&r1=1841680&r2=1841681&view=diff
==============================================================================
--- spamassassin/trunk/MANIFEST (original)
+++ spamassassin/trunk/MANIFEST Sat Sep 22 13:40:58 2018
@@ -49,6 +49,7 @@ lib/Mail/SpamAssassin/Constants.pm
 lib/Mail/SpamAssassin/DBBasedAddrList.pm
 lib/Mail/SpamAssassin/Dns.pm
 lib/Mail/SpamAssassin/DnsResolver.pm
+lib/Mail/SpamAssassin/GeoDB.pm
 lib/Mail/SpamAssassin/HTML.pm
 lib/Mail/SpamAssassin/Locales.pm
 lib/Mail/SpamAssassin/Locker.pm
@@ -297,6 +298,13 @@ t/data/dkim/test-pass-19.msg
 t/data/etc/hello.txt
 t/data/etc/testhost.cert
 t/data/etc/testhost.key
+t/data/geodb/create_GeoIPCity.README
+t/data/geodb/create_GeoIPISP.README
+t/data/geodb/GeoIP2-City.mmdb
+t/data/geodb/GeoIP2-Country.mmdb
+t/data/geodb/GeoIP2-ISP.mmdb
+t/data/geodb/GeoIPCity.dat
+t/data/geodb/GeoIPISP.dat
 t/data/mime-subject.txt
 t/data/nice/001
 t/data/nice/002
@@ -548,7 +556,9 @@ t/uri_html.t
 t/uri_list.t
 t/uri_text.t
 t/uribl.t
+t/urilocalbl_fast.t
 t/urilocalbl_geoip.t
+t/urilocalbl_geoip2.t
 t/utf8.t
 t/util_wrap.t
 t/whitelist_addrs.t

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm?rev=1841681&r1=1841680&r2=1841681&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm Sat Sep 22 13:40:58 2018
@@ -4288,6 +4288,186 @@ end with a '|'.  Also ignore rules with
     type     => $CONF_TYPE_BOOL,
   });
 
+=item geodb_module STRING
+
+This option tells SpamAssassin which geolocation module to use. 
+If not specified, all supported ones are tried in this order:
+
+Plugins can override this internally if required.
+
+ GeoIP2::Database::Reader
+ Geo::IP
+ IP::Country::DB_File  (not used unless geodb_options path set)
+ IP::Country::Fast
+
+=cut
+
+  push (@cmds, {
+    setting => 'geodb_module',
+    default => undef,
+    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      if ($value eq 'GeoIP2::Database::Reader' || $value eq 'GeoIP2') {
+        $self->{geodb}->{module} = 'geoip2';
+      } elsif ($value eq 'Geo::IP' || $value eq 'GeoIP') {
+        $self->{geodb}->{module} = 'geoip';
+      } elsif ($value eq 'IP::Country::DB_File' || $value eq 'DB_File') {
+        $self->{geodb}->{module} = 'dbfile';
+      } elsif ($value eq 'IP::Country::Fast' || $value eq 'Fast') {
+        $self->{geodb}->{module} = 'fast';
+      } else {
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+      }
+    }
+  });
+
+  # support deprecated RelayCountry setting
+  push (@cmds, {
+    setting => 'country_db_type',
+    default => undef,
+    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      warn("deprecated setting used, change country_db_type to geodb_module");
+      if ($value =~ /GeoIP2/i) {
+        $self->{geodb}->{module} = 'geoip2';
+      } elsif ($value =~ /Geo/i) {
+        $self->{geodb}->{module} = 'geoip';
+      } elsif ($value =~ /Fast/i) {
+        $self->{geodb}->{module} = 'fast';
+      } else {
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+      }
+    }
+  });
+
+=item geodb_options dbtype:/path/to/db ...
+
+Supported dbtypes:
+
+I<city> - use City database
+I<country> - use Country database
+I<isp> - try loading ISP database
+I<asn> - try loading ASN database
+
+Append full database path with colon, for example:
+I<isp:/opt/geoip/isp.mmdb>
+
+Plugins can internally request all types they require, geoip_options is only
+needed if the default location search (described below) does not work.
+
+GeoIP/GeoIP2 searches these files/directories:
+
+ country:
+   GeoIP2-Country.mmdb, GeoLite2-Country.mmdb
+   GeoIP.dat (and v6 version)
+ city:
+   GeoIP2-City.mmdb, GeoLite2-City.mmdb
+   GeoIPCity.dat, GeoLiteCity.dat (and v6 versions)
+ isp:
+   GeoIP2-ISP.mmdb
+   GeoIPISP.dat, GeoLiteISP.dat (and v6 versions)
+ directories:
+   /usr/local/share/GeoIP
+   /usr/share/GeoIP
+   /var/lib/GeoIP
+   /opt/share/GeoIP
+
+=cut
+
+  push (@cmds, {
+    setting => 'geodb_options',
+    type => $CONF_TYPE_HASH_KEY_VALUE,
+    default => {},
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      foreach my $option (split (/\s+/, lc $value)) {
+        my ($opt, $db) = split(/:/, $option, 2);        
+        if ($option eq 'reset') {
+          $self->{geodb}->{options} = {};
+        } elsif ($option eq 'country') {
+          $self->{geodb}->{options}->{country} = $db || undef;
+        } elsif ($option eq 'city') {
+          $self->{geodb}->{options}->{city} = $db || undef;
+        } elsif ($option eq 'isp') {
+          $self->{geodb}->{options}->{isp} = $db || undef;
+        } else {
+          return $INVALID_VALUE;
+        }
+      }
+    }
+  });
+
+=item geodb_search_path /path/to/GeoIP ...
+
+Alternative to geoip_options. Overrides the default list of directories to
+search for default filenames.
+
+=cut
+
+  push (@cmds, {
+    setting => 'geoip_search_path',
+    default => [],
+    type => $CONF_TYPE_STRINGLIST,
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      if ($value eq 'reset') {
+        $self->{geodb}->{geoip_search_path} = [];
+      } elsif ($value eq '') {
+        return $MISSING_REQUIRED_VALUE;
+      } else {
+        push(@{$self->{geodb}->{geoip_search_path}}, split(/\s+/, $value));
+      }
+    }
+  });
+
+  # support deprecated RelayCountry setting
+  push (@cmds, {
+    setting => 'country_db_path',
+    default => undef,
+    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      warn("deprecated setting used, change country_db_path to geodb_options");
+      if ($value ne '') {
+        $self->{geodb}->{options}->{country} = $value;
+      } else {
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+      }
+    }
+  });
+  # support deprecated URILocalBL setting
+  push (@cmds, {
+    setting => 'uri_country_db_path',
+    default => undef,
+    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      warn("deprecated setting used, change uri_country_db_path to geodb_options");
+      if ($value ne '') {
+        $self->{geodb}->{options}->{country} = $value;
+      } else {
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+      }
+    }
+  });
+  # support deprecated URILocalBL setting
+  push (@cmds, {
+    setting => 'uri_country_db_isp_path',
+    default => undef,
+    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
+    code => sub {
+      my ($self, $key, $value, $line) = @_;
+      warn("deprecated setting used, change uri_country_db_isp_path to geodb_options");
+      if ($value ne '') {
+        $self->{geodb}->{options}->{isp} = $value;
+      } else {
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+      }
+    }
+  });
+
 =back
 
 =head1 PREPROCESSING OPTIONS
@@ -5232,6 +5412,7 @@ sub feature_bug6558_free { 1 }
 sub feature_edns { 1 }  # supports 'dns_options edns' config option
 sub feature_dns_query_restriction { 1 }  # supported config option
 sub feature_registryboundaries { 1 } # replaces deprecated registrarboundaries
+sub feature_geodb { 1 } # if needed for some reason
 sub perl_min_version_5010000 { return $] >= 5.010000 }  # perl version check ("perl_version" not neatly backwards-compatible)
 
 ###########################################################################

Added: spamassassin/trunk/lib/Mail/SpamAssassin/GeoDB.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/GeoDB.pm?rev=1841681&view=auto
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/GeoDB.pm (added)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/GeoDB.pm Sat Sep 22 13:40:58 2018
@@ -0,0 +1,726 @@
+# <@LICENSE>
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# </...@LICENSE>
+
+=head1 NAME
+
+Mail::SpamAssassin::GeoDB - unified interface for geoip modules
+
+(internal stuff still subject to change)
+
+=cut
+
+package Mail::SpamAssassin::GeoDB;
+
+use strict;
+use warnings;
+# use bytes;
+use re 'taint';
+
+use Socket;
+
+our @ISA = qw();
+
+use Mail::SpamAssassin::Constants qw(:ip);
+use Mail::SpamAssassin::Logger;
+
+my @geoip_default_path = qw(
+  /usr/local/share/GeoIP
+  /usr/share/GeoIP
+  /var/lib/GeoIP
+  /opt/share/GeoIP
+);
+
+# load order (city contains country, isp contains asn)
+my @geoip_types = qw( city country isp asn );
+
+# v6 is not needed, automatically tries *v6.dat also
+my %geoip_default_files = (
+  'city' => ['GeoIPCity.dat','GeoLiteCity.dat'],
+  'country' => ['GeoIP.dat'],
+  'isp' => ['GeoIPISP.dat'],
+  'asn' => ['GeoIPASNum.dat'],
+);
+
+my %geoip2_default_files = (
+  'city' => ['GeoIP2-City.mmdb','GeoLite2-City.mmdb'],
+  'country' => ['GeoIP2-Country.mmdb','GeoLite2-Country.mmdb'],
+  'isp' => ['GeoIP2-ISP.mmdb','GeoLite2-ISP.mmdb'],
+  'asn' => ['GeoIP2-ASN.mmdb','GeoLite2-ASN.mmdb'],
+);
+
+my %country_to_continent = (
+'AP'=>'AS','EU'=>'EU','AD'=>'EU','AE'=>'AS','AF'=>'AS','AG'=>'NA',
+'AI'=>'NA','AL'=>'EU','AM'=>'AS','CW'=>'NA','AO'=>'AF','AQ'=>'AN',
+'AR'=>'SA','AS'=>'OC','AT'=>'EU','AU'=>'OC','AW'=>'NA','AZ'=>'AS',
+'BA'=>'EU','BB'=>'NA','BD'=>'AS','BE'=>'EU','BF'=>'AF','BG'=>'EU',
+'BH'=>'AS','BI'=>'AF','BJ'=>'AF','BM'=>'NA','BN'=>'AS','BO'=>'SA',
+'BR'=>'SA','BS'=>'NA','BT'=>'AS','BV'=>'AN','BW'=>'AF','BY'=>'EU',
+'BZ'=>'NA','CA'=>'NA','CC'=>'AS','CD'=>'AF','CF'=>'AF','CG'=>'AF',
+'CH'=>'EU','CI'=>'AF','CK'=>'OC','CL'=>'SA','CM'=>'AF','CN'=>'AS',
+'CO'=>'SA','CR'=>'NA','CU'=>'NA','CV'=>'AF','CX'=>'AS','CY'=>'AS',
+'CZ'=>'EU','DE'=>'EU','DJ'=>'AF','DK'=>'EU','DM'=>'NA','DO'=>'NA',
+'DZ'=>'AF','EC'=>'SA','EE'=>'EU','EG'=>'AF','EH'=>'AF','ER'=>'AF',
+'ES'=>'EU','ET'=>'AF','FI'=>'EU','FJ'=>'OC','FK'=>'SA','FM'=>'OC',
+'FO'=>'EU','FR'=>'EU','FX'=>'EU','GA'=>'AF','GB'=>'EU','GD'=>'NA',
+'GE'=>'AS','GF'=>'SA','GH'=>'AF','GI'=>'EU','GL'=>'NA','GM'=>'AF',
+'GN'=>'AF','GP'=>'NA','GQ'=>'AF','GR'=>'EU','GS'=>'AN','GT'=>'NA',
+'GU'=>'OC','GW'=>'AF','GY'=>'SA','HK'=>'AS','HM'=>'AN','HN'=>'NA',
+'HR'=>'EU','HT'=>'NA','HU'=>'EU','ID'=>'AS','IE'=>'EU','IL'=>'AS',
+'IN'=>'AS','IO'=>'AS','IQ'=>'AS','IR'=>'AS','IS'=>'EU','IT'=>'EU',
+'JM'=>'NA','JO'=>'AS','JP'=>'AS','KE'=>'AF','KG'=>'AS','KH'=>'AS',
+'KI'=>'OC','KM'=>'AF','KN'=>'NA','KP'=>'AS','KR'=>'AS','KW'=>'AS',
+'KY'=>'NA','KZ'=>'AS','LA'=>'AS','LB'=>'AS','LC'=>'NA','LI'=>'EU',
+'LK'=>'AS','LR'=>'AF','LS'=>'AF','LT'=>'EU','LU'=>'EU','LV'=>'EU',
+'LY'=>'AF','MA'=>'AF','MC'=>'EU','MD'=>'EU','MG'=>'AF','MH'=>'OC',
+'MK'=>'EU','ML'=>'AF','MM'=>'AS','MN'=>'AS','MO'=>'AS','MP'=>'OC',
+'MQ'=>'NA','MR'=>'AF','MS'=>'NA','MT'=>'EU','MU'=>'AF','MV'=>'AS',
+'MW'=>'AF','MX'=>'NA','MY'=>'AS','MZ'=>'AF','NA'=>'AF','NC'=>'OC',
+'NE'=>'AF','NF'=>'OC','NG'=>'AF','NI'=>'NA','NL'=>'EU','NO'=>'EU',
+'NP'=>'AS','NR'=>'OC','NU'=>'OC','NZ'=>'OC','OM'=>'AS','PA'=>'NA',
+'PE'=>'SA','PF'=>'OC','PG'=>'OC','PH'=>'AS','PK'=>'AS','PL'=>'EU',
+'PM'=>'NA','PN'=>'OC','PR'=>'NA','PS'=>'AS','PT'=>'EU','PW'=>'OC',
+'PY'=>'SA','QA'=>'AS','RE'=>'AF','RO'=>'EU','RU'=>'EU','RW'=>'AF',
+'SA'=>'AS','SB'=>'OC','SC'=>'AF','SD'=>'AF','SE'=>'EU','SG'=>'AS',
+'SH'=>'AF','SI'=>'EU','SJ'=>'EU','SK'=>'EU','SL'=>'AF','SM'=>'EU',
+'SN'=>'AF','SO'=>'AF','SR'=>'SA','ST'=>'AF','SV'=>'NA','SY'=>'AS',
+'SZ'=>'AF','TC'=>'NA','TD'=>'AF','TF'=>'AN','TG'=>'AF','TH'=>'AS',
+'TJ'=>'AS','TK'=>'OC','TM'=>'AS','TN'=>'AF','TO'=>'OC','TL'=>'AS',
+'TR'=>'EU','TT'=>'NA','TV'=>'OC','TW'=>'AS','TZ'=>'AF','UA'=>'EU',
+'UG'=>'AF','UM'=>'OC','US'=>'NA','UY'=>'SA','UZ'=>'AS','VA'=>'EU',
+'VC'=>'NA','VE'=>'SA','VG'=>'NA','VI'=>'NA','VN'=>'AS','VU'=>'OC',
+'WF'=>'OC','WS'=>'OC','YE'=>'AS','YT'=>'AF','RS'=>'EU','ZA'=>'AF',
+'ZM'=>'AF','ME'=>'EU','ZW'=>'AF','AX'=>'EU','GG'=>'EU','IM'=>'EU',
+'JE'=>'EU','BL'=>'NA','MF'=>'NA','BQ'=>'NA','SS'=>'AF','**'=>'**',
+);
+
+my $IP_PRIVATE = IP_PRIVATE;
+my $IP_ADDRESS = IP_ADDRESS;
+my $IPV4_ADDRESS = IPV4_ADDRESS;
+
+sub new {
+  my ($class, $conf) = @_;
+  $class = ref($class) || $class;
+
+  my $self = {};
+  bless ($self, $class);
+
+  $self->{cache} = ();
+  $self->init_database($conf || {});
+  $self;
+}
+
+sub init_database {
+  my ($self, $opts) = @_;
+
+  my $geodb_module = $opts->{conf}->{module} || undef;
+  my $geodb_dbs = $opts->{conf}->{options} || {};
+  my $geodb_wanted = $opts->{wanted} || {};
+
+  my @geoip_search_path = defined $opts->{conf}->{geoip_search_path} ?
+    @{$opts->{conf}->{geoip_search_path}} : @geoip_default_path;
+
+  my $db;
+  my $dbapi;
+
+  ##
+  ## GeoIP2
+  ##
+
+  if (!$db && (!$geodb_module || $geodb_module eq 'geoip2')) {
+    my $prep_ok = 0;
+    eval {
+      require GeoIP2::Database::Reader;
+      $prep_ok = 1;
+    } or do {
+      dbg("geodb: GeoIP2::Database::Reader module load failed: $@");
+    };
+
+    if ($prep_ok) {
+      my %path;
+      foreach my $dbtype (@geoip_types) {
+        # only autosearch if no absolute path given
+        next if %$geodb_dbs && !exists $geodb_dbs->{$dbtype};
+        # skip if not needed
+        next if %$geodb_wanted && !$geodb_wanted->{$dbtype};
+        # skip country if city already loaded
+        next if $dbtype eq 'country' && $db->{city};
+        # skip asn if isp already loaded
+        next if $dbtype eq 'asn' && $db->{isp};
+        if (!defined $geodb_dbs->{$dbtype}) {
+          # Try some default locations
+          PATHS_GEOIP2: foreach my $p (@geoip_search_path) {
+            foreach my $f (@{$geoip2_default_files{$dbtype}}) {
+              if (-f "$p/$f") {
+                $path{$dbtype} = "$p/$f";
+                dbg("geodb: GeoIP2: search found $dbtype $p/$f");
+                last PATHS_GEOIP2;
+              }
+            }
+          }
+        } else {
+          if (!-f $geodb_dbs->{$dbtype}) {
+            dbg("geodb: GeoIP2: $dbtype database requested, but not found: ".$geodb_dbs->{$dbtype});
+            next;
+          }
+          $path{$dbtype} = $geodb_dbs->{$dbtype};
+        }
+
+        if (defined $path{$dbtype}) {
+          eval {
+            $db->{$dbtype} = GeoIP2::Database::Reader->new(
+              file => $path{$dbtype},
+              locales => [ 'en' ]
+            );
+            die "unknown error" unless $db->{$dbtype};
+            1;
+          } or do {
+            $@ =~ s/\s+Trace begun.*//s;
+            dbg("geodb: GeoIP2: $dbtype load failed: $@");
+          };
+        } else {
+          my $from = defined $geodb_dbs->{$dbtype} ?
+            $geodb_dbs->{$dbtype} : "default locations";
+          dbg("geodb: GeoIP2: $dbtype database not found from $from");
+        } 
+
+        if ($db->{$dbtype}) {
+          dbg("geodb: GeoIP2: loaded $dbtype from $path{$dbtype}");
+        }
+      }
+    }
+
+    # dbinfo_DBTYPE()
+    $db->{city} and $dbapi->{dbinfo_city} = sub {
+      my $m = $_[0]->{db}->{city}->metadata;
+      return "GeoIP2 city: ".$m->description()->{en}." / ".localtime($m->build_epoch());
+    };
+    $db->{country} and $dbapi->{dbinfo_country} = sub {
+      my $m = $_[0]->{db}->{country}->metadata;
+      return "GeoIP2 country: ".$m->description()->{en}." / ".localtime($m->build_epoch());
+    };
+    $db->{isp} and $dbapi->{dbinfo_isp} = sub {
+      my $m = $_[0]->{db}->{isp}->metadata;
+      return "GeoIP2 isp: ".$m->description()->{en}." / ".localtime($m->build_epoch());
+    };
+    $db->{asn} and $dbapi->{dbinfo_asn} = sub {
+      my $m = $_[0]->{db}->{asn}->metadata;
+      return "GeoIP2 asn: ".$m->description()->{en}." / ".localtime($m->build_epoch());
+    };
+
+    # city()
+    $db->{city} and $dbapi->{city} = sub {
+      my $res = {};
+      my $city;
+      eval {
+        $city = $_[0]->{db}->{city}->city(ip=>$_[1]);
+        1;
+      } or do {
+        $@ =~ s/\s+Trace begun.*//s;
+        dbg("geodb: GeoIP2 city query failed for $_[1]: $@");
+        return $res;
+      };
+      eval {
+        $res->{city_name} = $city->{raw}->{city}->{names}->{en};
+        $res->{country} = $city->{raw}->{country}->{iso_code};
+        $res->{country_name} = $city->{raw}->{country}->{names}->{en};
+        $res->{continent} = $city->{raw}->{continent}->{code};
+        $res->{continent_name} = $city->{raw}->{continent}->{names}->{en};
+        1;
+      };
+      return $res;
+    };
+
+    # country()
+    $db->{country} and $dbapi->{country} = sub {
+      my $res = {};
+      my $country;
+      eval {
+        $country = $_[0]->{db}->{country}->country(ip=>$_[1]);
+        1;
+      } or do {
+        $@ =~ s/\s+Trace begun.*//s;
+        dbg("geodb: GeoIP2 country query failed for $_[1]: $@");
+        return $res;
+      };
+      eval {
+        $res->{country} = $country->{raw}->{country}->{iso_code};
+        $res->{country_name} = $country->{raw}->{country}->{names}->{en};
+        $res->{continent} = $country->{raw}->{continent}->{code};
+        $res->{continent_name} = $country->{raw}->{continent}->{names}->{en};
+        1;
+      };
+      return $res;
+    };
+
+    # isp()
+    $db->{isp} and $dbapi->{isp} = sub {
+      my $res = {};
+      my $isp;
+      eval {
+        $isp = $_[0]->{db}->{isp}->isp(ip=>$_[1]);
+        1;
+      } or do {
+        $@ =~ s/\s+Trace begun.*//s;
+        dbg("geodb: GeoIP2 isp query failed for $_[1]: $@");
+        return $res;
+      };
+      eval {
+        $res->{asn} = $isp->autonomous_system_number();
+        $res->{asn_organization} = $isp->autonomous_system_organization();
+        $res->{isp} = $isp->isp();
+        $res->{organization} = $isp->organization();
+        1;
+      };
+      return $res;
+    };
+
+    # asn()
+    $db->{asn} and $dbapi->{asn} = sub {
+      my $res = {};
+      my $asn;
+      eval {
+        $asn = $_[0]->{db}->{asn}->asn(ip=>$_[1]);
+        1;
+      } or do {
+        $@ =~ s/\s+Trace begun.*//s;
+        dbg("geodb: GeoIP2 asn query failed for $_[1]: $@");
+        return $res;
+      };
+      eval {
+        $res->{asn} = $asn->autonomous_system_number();
+        $res->{asn_organization} = $asn->autonomous_system_organization();
+        1;
+      };
+      return $res;
+    };
+  }
+
+  ##
+  ## Geo::IP
+  ##
+
+  if (!$db && (!$geodb_module || $geodb_module eq 'geoip')) {
+    my $prep_ok = 0;
+    my ($gic_wanted,$gic_have,$gip_wanted,$gip_have);
+    my ($flags,$fix_stderr,$can_ipv6);
+    eval {
+      require Geo::IP;
+      # need GeoIP C library 1.6.3 and GeoIP perl API 1.4.4 or later to avoid messages leaking - Bug 7153
+      $gip_wanted = version->parse('v1.4.4');
+      $gip_have = version->parse(Geo::IP->VERSION);
+      $gic_wanted = version->parse('v1.6.3');
+      eval { $gic_have = version->parse(Geo::IP->lib_version()); }; # might not have lib_version()
+      $gic_have = 'none' if !defined $gic_have;
+      dbg("geodb: GeoIP: versions: Geo::IP $gip_have, C library $gic_have");
+      $flags = 0;
+      $fix_stderr = 0;
+      if (ref($gic_have) eq 'version') {
+        # this code burps an ugly message if it fails, but that's redirected elsewhere
+        eval '$flags = Geo::IP::GEOIP_SILENCE' if $gip_wanted >= $gip_have;
+        $fix_stderr = $flags && $gic_wanted >= $gic_have;
+      }
+      $can_ipv6 = Geo::IP->VERSION >= 1.39 && Geo::IP->api eq 'CAPI';
+      $prep_ok = 1;
+      1;
+    } or do {
+      dbg("geodb: Geo::IP module load failed: $@");
+    };
+
+    if ($prep_ok) {
+      my %path;
+
+      foreach my $dbtype (@geoip_types) {
+        # only autosearch if no absolute path given
+        next if %$geodb_dbs && !exists $geodb_dbs->{$dbtype};
+        # skip if not needed
+        next if %$geodb_wanted && !$geodb_wanted->{$dbtype};
+        # skip country if city already loaded
+        next if $dbtype eq 'country' && $db->{city};
+        # skip asn if isp already loaded
+        next if $dbtype eq 'asn' && $db->{isp};
+        if (!defined $geodb_dbs->{$dbtype}) {
+          # Try some default locations
+          PATHS_GEOIP: foreach my $p (@geoip_search_path) {
+            foreach my $f (@{$geoip_default_files{$dbtype}}) {
+              if (-f "$p/$f") {
+                $path{$dbtype} = "$p/$f";
+                dbg("geodb: GeoIP: search found $dbtype $p/$f");
+                if ($can_ipv6 && $f =~ s/\.(dat)$/v6.$1/i) {
+                  if (-f "$p/$f") {
+                    $path{$dbtype."_v6"} = "$p/$f";
+                    dbg("geodb: GeoIP: search found $dbtype $p/$f");
+                  }
+                }
+                last PATHS_GEOIP;
+              }
+            }
+          }
+        } else {
+          if (!-f $geodb_dbs->{$dbtype}) {
+            dbg("geodb: GeoIP: $dbtype database requested, but not found: ".$geodb_dbs->{$dbtype});
+            next;
+          }
+          $path{$dbtype} = $geodb_dbs->{$dbtype};
+        }
+      }
+
+      if (!$can_ipv6) {
+        dbg("geodb: GeoIP: IPv6 support not enabled, versions Geo::IP 1.39, GeoIP C API 1.4.7 required");
+      }
+
+      if ($fix_stderr) {
+        open(OLDERR, ">&STDERR");
+        open(STDERR, ">/dev/null");
+      }
+      foreach my $dbtype (@geoip_types) {
+        next unless defined $path{$dbtype};
+        eval {
+          $db->{$dbtype} = Geo::IP->open($path{$dbtype}, Geo::IP->GEOIP_STANDARD | $flags);
+          if ($can_ipv6 && defined $path{$dbtype."_v6"}) {
+            $db->{$dbtype."_v6"} = Geo::IP->open($path{$dbtype."_v6"}, Geo::IP->GEOIP_STANDARD | $flags);
+          }
+        };
+        if ($@ || !$db->{$dbtype}) {
+          dbg("geodb: GeoIP: database $path{$dbtype} load failed: $@");
+        };
+      }
+      if ($fix_stderr) {
+        open(STDERR, ">&OLDERR");
+        close(OLDERR);
+      }
+    }
+
+    # dbinfo_DBTYPE()
+    $db->{city} and $dbapi->{dbinfo_city} = sub {
+      return "Geo::IP IPv4 city: " . ($_[0]->{db}->{city}->database_info || '?')." / IPv6: ".
+        ($_[0]->{db}->{city_v6} ? $_[0]->{db}->{city_v6}->database_info || '?' : 'no')
+    };
+    $db->{country} and $dbapi->{dbinfo_country} = sub {
+      return "Geo::IP IPv4 country: " . ($_[0]->{db}->{country}->database_info || '?')." / IPv6: ".
+        ($_[0]->{db}->{country_v6} ? $_[0]->{db}->{country_v6}->database_info || '?' : 'no')
+    };
+    $db->{isp} and $dbapi->{dbinfo_isp} = sub {
+      return "Geo::IP IPv4 isp: " . ($_[0]->{db}->{isp}->database_info || '?')." / IPv6: ".
+        ($_[0]->{db}->{isp_v6} ? $_[0]->{db}->{isp_v6}->database_info || '?' : 'no')
+    };
+    $db->{asn} and $dbapi->{dbinfo_asn} = sub {
+      return "Geo::IP IPv4 asn: " . ($_[0]->{db}->{asn}->database_info || '?')." / IPv6: ".
+        ($_[0]->{db}->{asn_v6} ? $_[0]->{db}->{asn_v6}->database_info || '?' : 'no')
+    };
+
+    # city()
+    $db->{city} and $dbapi->{city} = sub {
+      my $res = {};
+      my $city;
+      if ($_[1] =~ /^$IPV4_ADDRESS$/o) {
+        $city = $_[0]->{db}->{city}->record_by_addr($_[1]);
+      } elsif ($_[0]->{db}->{city_v6}) {
+        $city = $_[0]->{db}->{city_v6}->country_code_by_addr_v6($_[1]);
+        return $res if !defined $city;
+        $res->{country} = $city;
+        return $res;
+      }
+      if (!defined $city) {
+        dbg("geodb: GeoIP city query failed for $_[1]");
+        return $res;
+      }
+      $res->{city_name} = $city->city;
+      $res->{country} = $city->country_code;
+      $res->{country_name} = $city->country_name;
+      $res->{continent} = $city->continent_code;
+      return $res;
+    };
+
+    # country()
+    $db->{country} and $dbapi->{country} = sub {
+      my $res = {};
+      my $country;
+      eval {
+        if ($_[1] =~ /^$IPV4_ADDRESS$/o) {
+          $country = $_[0]->{db}->{country}->country_code_by_addr($_[1]);
+        } elsif ($_[0]->{db}->{country_v6}) {
+          $country = $_[0]->{db}->{country_v6}->country_code_by_addr_v6($_[1]);
+        }
+        1;
+      };
+      if (!defined $country) {
+        dbg("geodb: GeoIP country query failed for $_[1]");
+        return $res;
+      };
+      $res->{country} = $country;
+      $res->{continent} = $country_to_continent{$country} || 'XX';
+      return $res;
+    };
+
+    # isp()
+    $db->{isp} and $dbapi->{isp} = sub {
+      my $res = {};
+      my $isp;
+      eval {
+        if ($_[1] =~ /^$IPV4_ADDRESS$/o) {
+          $isp = $_[0]->{db}->{isp}->isp_by_addr($_[1]);
+        } else {
+          # TODO?
+          return $res;
+        }
+        1;
+      };
+      if (!defined $isp) {
+        dbg("geodb: GeoIP isp query failed for $_[1]");
+        return $res;
+      };
+      $res->{isp} = $isp;
+      return $res;
+    };
+  }
+
+  ##
+  ## IP::Country::DB_File
+  ##
+
+  # Only try if geodb_module and path to ipcc.db specified
+  if (!$db && $geodb_module eq 'dbfile') {
+    if (defined $geodb_dbs->{country} && -f $geodb_dbs->{country}) {
+      eval {
+        require IP::Country::DB_File;
+        $db->{country} = IP::Country::DB_File->new($geodb_dbs->{country});
+        1;
+      };
+      if ($@ || !$db->{country}) {
+        dbg("geodb: IP::Country::DB_File load failed: $@");
+      }
+    } else {
+      dbg("IP::Country::DB_File requires geodb_options country:/path/to/ipcc.db\n");
+    }
+
+    # dbinfo_DBTYPE()
+    $db->{country} and $dbapi->{dbinfo_country} = sub {
+      return "IP::Country::DB_File country: ".localtime($_[0]->{db}->{country}->db_time());
+    };
+
+    # country();
+    $db->{country} and $dbapi->{country} = sub {
+      my $res = {};
+      my $country;
+      if ($_[1] =~ /^$IPV4_ADDRESS$/o) {
+        $country = $_[0]->{db}->{country}->inet_atocc($_[1]);
+      } else {
+        $country = $_[0]->{db}->{country}->inet6_atocc($_[1]);
+      }
+      $res->{country} = $country;
+      $res->{continent} = $country_to_continent{$country} || 'XX';
+      return $res;
+    };
+  } 
+
+  ##
+  ## IP::Country::Fast
+  ##
+
+  if (!$db && (!$geodb_module || $geodb_module eq 'fast')) {
+    eval {
+      require IP::Country::Fast;
+      $db->{country} = IP::Country::Fast->new();
+      1;
+    };
+    if ($@ || !$db->{country}) {
+      my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
+      dbg("geodb: IP::Country::Fast load failed: $eval_stat");
+    }
+
+    # dbinfo_DBTYPE()
+    $db->{country} and $dbapi->{dbinfo_country} = sub {
+      return "IP::Country::Fast country: ".localtime($_[0]->{db}->{country}->db_time());
+    };
+
+    # country();
+    $db->{country} and $dbapi->{country} = sub {
+      my $res = {};
+      my $country;
+      if ($_[1] =~ /^$IPV4_ADDRESS$/o) {
+        $country = $_[0]->{db}->{country}->inet_atocc($_[1]);
+      } else {
+        return $res
+      }
+      $res->{country} = $country;
+      $res->{continent} = $country_to_continent{$country} || 'XX';
+      return $res;
+    };
+  }
+
+  ##
+
+  if (!%$db) {
+    dbg("geodb: No supported database could be loaded");
+    die("No supported GeoDB database could be loaded\n");
+  }
+
+  $self->{db} = $db;
+  $self->{dbapi} = $dbapi;
+
+  foreach (@{$self->get_dbinfo()}) {
+    dbg("geodb: database info: ".$_);
+  }
+  #dbg("geodb: apis available: ".join(', ', sort keys %{$self->{dbapi}}));
+
+  return 1;
+}
+
+# return array, infoline per database type
+sub get_dbinfo {
+  my ($self, $db) = @_;
+
+  my @lines;
+  foreach (@geoip_types) {
+    if (exists $self->{dbapi}->{"dbinfo_".$_}) {
+      push @lines,
+        $self->{dbapi}->{"dbinfo_".$_}->($self) || "$_ failed";
+    }
+  }
+
+  return \@lines;
+}
+
+sub get_country {
+  my ($self, $ip) = @_;
+
+  return undef if !defined $ip || $ip !~ /\S/;
+
+  if ($ip =~ /^$IP_PRIVATE$/o) {
+    return '**';
+  }
+
+  if ($ip !~ /^$IP_ADDRESS$/o) {
+    $ip = name_to_ip($ip);
+    return 'XX' if !defined $ip;
+  }
+  
+  if ($self->{dbapi}->{city}) {
+    return $self->_get('city',$ip)->{country} || 'XX';
+  } elsif ($self->{dbapi}->{country}) {
+    return $self->_get('country',$ip)->{country} || 'XX';
+  } else {
+    return undef;
+  }
+}
+
+sub get_continent {
+  my ($self, $ip) = @_;
+
+  return undef if !defined $ip || $ip !~ /\S/;
+
+  # If it's already CC, use our own lookup table..
+  if (length($ip) == 2) {
+    return $country_to_continent{uc($ip)} || 'XX';
+  }
+
+  if ($self->{dbapi}->{city}) {
+    return $self->_get('city',$ip)->{continent} || 'XX';
+  } elsif ($self->{dbapi}->{country}) {
+    return $self->_get('country',$ip)->{continent} || 'XX';
+  } else {
+    return undef;
+  }
+}
+
+sub get_isp {
+  my ($self, $ip) = @_;
+
+  return undef if !defined $ip || $ip !~ /\S/;
+
+  if ($self->{dbapi}->{isp}) {
+    return $self->_get('isp',$ip)->{isp};
+  } else {
+    return undef;
+  }
+}
+
+sub get_all {
+  my ($self, $ip) = @_;
+
+  return undef if !defined $ip || $ip !~ /\S/;
+
+  my $all = {};
+
+  if ($ip =~ /^$IP_PRIVATE$/o) {
+    return { 'country' => '**' };
+  }
+
+  if ($ip !~ /^$IP_ADDRESS$/o) {
+    $ip = name_to_ip($ip);
+    if (!defined $ip) {
+      return { 'country' => 'XX' };
+    }
+  }
+  
+  if ($self->{dbapi}->{city}) {
+    my $res = $self->_get('city',$ip);
+    $all->{$_} = $res->{$_} foreach (keys %$res);
+  } elsif ($self->{dbapi}->{country}) {
+    my $res = $self->_get('country',$ip);
+    $all->{$_} = $res->{$_} foreach (keys %$res);
+  }
+
+  if ($self->{dbapi}->{isp}) {
+    my $res = $self->_get('isp',$ip);
+    $all->{$_} = $res->{$_} foreach (keys %$res);
+  }
+
+  if ($self->{dbapi}->{asn}) {
+    my $res = $self->_get('asn',$ip);
+    $all->{$_} = $res->{$_} foreach (keys %$res);
+  }
+
+  return $all;
+}
+
+sub can {
+  my ($self, $check) = @_;
+  return $self->{dbapi}->{$check};
+}
+
+# TODO: use SA internal dns
+# This shouldn't be called much, as plugins
+# should do their own resolving if needed
+sub name_to_ip {
+  my $name = shift;
+  if (my $ip = inet_aton($name)) {
+    return inet_ntoa($ip);
+  }
+  return undef;
+}
+
+sub _get {
+  my ($self, $type, $ip) = @_;
+
+  # reset cache at 100 ips
+  if (scalar keys %{$self->{cache}} >= 100) {
+    $self->{cache} = ();
+  }
+
+  if (!defined $self->{cache}{$ip}{$type}) {
+    if ($self->{dbapi}->{$type}) {
+      $self->{cache}{$ip}{$type} = $self->{dbapi}->{$type}->($self,$ip);
+    } else {
+      return undef;
+    }
+  }
+
+  return $self->{cache}{$ip}{$type};
+}
+
+1;

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/RelayCountry.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/RelayCountry.pm?rev=1841681&r1=1841680&r2=1841681&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/RelayCountry.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/RelayCountry.pm Sat Sep 22 13:40:58 2018
@@ -32,10 +32,8 @@ header markup.
 
 =head1 REQUIREMENT
 
-This plugin requires the GeoIP2, Geo::IP, IP::Country::DB_File or 
-IP::Country::Fast module from CPAN.
-For backward compatibility IP::Country::Fast is used as fallback if no db_type
-is specified in the config file.
+This plugin uses Mail::SpamAssassin::GeoDB and requires a module supported
+by it, for example GeoIP2::Database::Reader.
 
 =cut
 
@@ -43,7 +41,7 @@ package Mail::SpamAssassin::Plugin::Rela
 
 use Mail::SpamAssassin::Plugin;
 use Mail::SpamAssassin::Logger;
-use Mail::SpamAssassin::Constants qw(:ip);
+use Mail::SpamAssassin::GeoDB;
 use strict;
 use warnings;
 # use bytes;
@@ -66,263 +64,47 @@ sub new {
   my $self = $class->SUPER::new($mailsaobject);
   bless ($self, $class);
 
-  $self->set_config($mailsaobject->{conf});
   return $self;
 }
 
-sub set_config {
-  my ($self, $conf) = @_;
-  my @cmds;
-
-=head1 USER PREFERENCES
-
-The following options can be used in both site-wide (C<local.cf>) and
-user-specific (C<user_prefs>) configuration files to customize how
-SpamAssassin handles incoming email messages.
-
-=over 4
-
-=item country_db_type STRING
-
-This option tells SpamAssassin which type of Geo database to use.
-Valid database types are GeoIP, GeoIP2, DB_File and Fast.
-
-=back
-
-=cut
-
-  push (@cmds, {
-    setting => 'country_db_type',
-    default => "GeoIP",
-    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
-    code => sub {
-      my ($self, $key, $value, $line) = @_;
-      if ($value !~ /^(?:GeoIP|GeoIP2|DB_File|Fast)$/) {
-        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
-      }
-      $self->{country_db_type} = $value;
-    }
-  });
-
-=over 4
-
-=item country_db_path STRING
-
-This option tells SpamAssassin where to find MaxMind GeoIP2 or IP::Country::DB_File database.
-
-If not defined, GeoIP2 default search includes:
- /usr/local/share/GeoIP/GeoIP2-Country.mmdb
- /usr/share/GeoIP/GeoIP2-Country.mmdb
- /var/lib/GeoIP/GeoIP2-Country.mmdb
- /usr/local/share/GeoIP/GeoLite2-Country.mmdb
- /usr/share/GeoIP/GeoLite2-Country.mmdb
- /var/lib/GeoIP/GeoLite2-Country.mmdb
-
-=back
-
-=cut
-
-  push (@cmds, {
-    setting => 'country_db_path',
-    default => "",
-    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
-    code => sub {
-      my ($self, $key, $value, $line) = @_;
-      if (!defined $value || !length $value) {
-        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
-      }
-      if (!-e $value) {
-        info("config: country_db_path \"$value\" is not accessible");
-        $self->{country_db_path} = $value;
-        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
-      }
-      $self->{country_db_path} = $value;
-    }
-  });
-
-  push (@cmds, {
-    setting => 'geoip2_default_db_path',
-    default => [
-      '/usr/local/share/GeoIP/GeoIP2-Country.mmdb',
-      '/usr/share/GeoIP/GeoIP2-Country.mmdb',
-      '/var/lib/GeoIP/GeoIP2-Country.mmdb',
-      '/usr/local/share/GeoIP/GeoLite2-Country.mmdb',
-      '/usr/share/GeoIP/GeoLite2-Country.mmdb',
-      '/var/lib/GeoIP/GeoLite2-Country.mmdb',
-      ],
-    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRINGLIST,
-    code => sub {
-      my ($self, $key, $value, $line) = @_;
-      if ($value eq '') {
-        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
-      }
-      push(@{$self->{geoip2_default_db_path}}, split(/\s+/, $value));
-    }
-  });
-  
-  $conf->{parser}->register_commands(\@cmds);
-}
-
 sub extract_metadata {
   my ($self, $opts) = @_;
+  
+  return if $self->{relaycountry_disabled};
 
-  my $country_db_type = $opts->{conf}->{country_db_type};
-  my $country_db_path = $opts->{conf}->{country_db_path};
-
-  if ($country_db_type eq "GeoIP") {
-    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: GeoIP: IPv6 support not enabled, GeoIPv6.dat not found");
-        }
-      } else {
-        dbg("metadata: RelayCountry: GeoIP: IPv6 support not enabled, versions Geo::IP 1.39, GeoIP C API 1.4.7 required");
-      }
-      $db_info = sub { return "Geo::IP IPv4: " . ($db->database_info || '?')." / IPv6: ".($dbv6 ? $dbv6->database_info || '?' : '?') };
-      1;
-    } or do {
-      # Fallback to IP::Country::Fast
-      dbg("metadata: RelayCountry: GeoIP: GeoIP.dat not found, trying IP::Country::Fast as fallback");
-      $country_db_type = "Fast";
-    }
-  }
-  elsif ($country_db_type eq "GeoIP2") {
-    if (!$country_db_path) {
-      # Try some default locations
-      foreach (@{$opts->{conf}->{geoip2_default_db_path}}) {
-        if (-f $_) {
-          $country_db_path = $_;
-          last;
-        }
-      }
-    }
-    if (-f $country_db_path) {
-      eval {
-        require GeoIP2::Database::Reader;
-        $db = GeoIP2::Database::Reader->new(
-          file => $country_db_path,
-          locales => [ 'en' ]
-        );
-        die "unknown error" unless $db;
-        $db_info = sub {
-          my $m = $db->metadata();
-          return "GeoIP2 ".$m->description()->{en}." / ".localtime($m->build_epoch());
-        };
-        1;
-      } or do {
-        # Fallback to IP::Country::Fast
-        $@ =~ s/\s+Trace begun.*//s;
-        dbg("metadata: RelayCountry: GeoIP2: ${country_db_path} load failed: $@, trying IP::Country::Fast as fallback");
-        $country_db_type = "Fast";
-      }
-    } else {
-      # Fallback to IP::Country::Fast
-      my $err = $country_db_path ?
-        "$country_db_path not found" : "database not found from default locations";
-      dbg("metadata: RelayCountry: GeoIP2: $err, trying IP::Country::Fast as fallback");
-      $country_db_type = "Fast";
-    }
-  }
-  elsif ($country_db_type eq "DB_File") {
-    if (-f $country_db_path) {
-      eval {
-        require IP::Country::DB_File;
-        $db = IP::Country::DB_File->new($country_db_path);
-        die "unknown error" unless $db;
-        $db_info = sub { return "IP::Country::DB_File ".localtime($db->db_time()); };
-        1;
-      } or do {
-        # Fallback to IP::Country::Fast
-        dbg("metadata: RelayCountry: DB_File: ${country_db_path} load failed: $@, trying IP::Country::Fast as fallback");
-        $country_db_type = "Fast";
-      }
-    } else {
-      # Fallback to IP::Country::Fast
-      dbg("metadata: RelayCountry: DB_File: ${country_db_path} not found, trying IP::Country::Fast as fallback");
-      $country_db_type = "Fast";
-    }
-  } 
-
-  if ($country_db_type eq "Fast") {
-    my $eval_stat = $@ ne '' ? $@ : "errno=$!";  chomp $eval_stat;
+  if (!$self->{geodb}) {
     eval {
-      require IP::Country::Fast;
-      $db = IP::Country::Fast->new();
-      $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;
+      $self->{geodb} = Mail::SpamAssassin::GeoDB->new({
+        conf => $opts->{conf}->{geodb},
+        wanted => { country => 1, city => 1 },
+      });
+    };
+    if (!$self->{geodb}) {
+      dbg("metadata: RelayCountry: plugin disabled: $@");
+      $self->{relaycountry_disabled} = 1;
+      return;
     }
   }
 
-  return 1 unless $db;
-
-  dbg("metadata: RelayCountry: Using database: ".$db_info->());
+  my $geodb = $self->{geodb};
   my $msg = $opts->{msg};
 
   my $countries = '';
-  my $IP_PRIVATE = IP_PRIVATE;
-  my $IPV4_ADDRESS = IPV4_ADDRESS;
   foreach my $relay (@{$msg->{metadata}->{relays_untrusted}}) {
     my $ip = $relay->{ip};
-    my $cc;
-
-    # Private IPs will always be returned as '**'
-    if ($ip =~ /^$IP_PRIVATE$/o) {
-      $cc = "**";
-    }
-    elsif ($country_db_type eq "GeoIP") {
-      if ($ip =~ /^$IPV4_ADDRESS$/o) {
-        $cc = $db->country_code_by_addr($ip);
-      } elsif (defined $dbv6) {
-        $cc = $dbv6->country_code_by_addr_v6($ip);
-      }
-    }
-    elsif ($country_db_type eq "GeoIP2") {
-      my ($country, $country_rec);
-      eval {
-        $country = $db->country( ip => $ip );
-        $country_rec = $country->country();
-        $cc = $country_rec->iso_code();
-        1;
-      } or do {
-        $@ =~ s/\s+Trace begun.*//s;
-        dbg("metadata: RelayCountry: GeoIP2 failed: $@");
-      }
-    }
-    elsif ($country_db_type eq "DB_File") {
-      if ($ip =~ /^$IPV4_ADDRESS$/o ) {
-        $cc = $db->inet_atocc($ip);
-      } else {
-        $cc = $db->inet6_atocc($ip);
-      }
-    }
-    elsif ($country_db_type eq "Fast") {
-      $cc = $db->inet_atocc($ip);
-    }
-
-    $cc ||= 'XX';
+    my $cc = $self->{geodb}->get_country($ip);
     $countries .= $cc." ";
   }
 
   chop $countries;
   $msg->put_metadata("X-Relay-Countries", $countries);
   dbg("metadata: X-Relay-Countries: $countries");
-
-  return 1;
 }
 
 sub parsed_metadata {
   my ($self, $opts) = @_;
 
-  return 1 unless $db;
+  return 1 unless $self->{geodb};
 
   my $countries =
     $opts->{permsgstatus}->get_message->get_metadata('X-Relay-Countries');

Modified: spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URILocalBL.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URILocalBL.pm?rev=1841681&r1=1841680&r2=1841681&view=diff
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URILocalBL.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/Plugin/URILocalBL.pm Sat Sep 22 13:40:58 2018
@@ -100,12 +100,13 @@ required.
 
 package Mail::SpamAssassin::Plugin::URILocalBL;
 use Mail::SpamAssassin::Plugin;
-use Mail::SpamAssassin::Logger;
 use Mail::SpamAssassin::Constants qw(:ip);
 use Mail::SpamAssassin::Util qw(untaint_var);
+use Mail::SpamAssassin::GeoDB;
 
-use Net::CIDR::Lite;
+use Net::CIDR::Lite; #TODO: use SA internal NetSet or such
 use Socket;
+use Data::Dumper;
 
 use strict;
 use warnings;
@@ -115,8 +116,7 @@ use version;
 
 our @ISA = qw(Mail::SpamAssassin::Plugin);
 
-use constant HAS_GEOIP => eval { require Geo::IP; };
-use constant HAS_GEOIP2 => eval { require GeoIP2::Database::Reader; };
+sub dbg { Mail::SpamAssassin::Plugin::dbg ("URILocalBL: @_"); }
 
 # constructor
 sub new {
@@ -128,13 +128,7 @@ sub new {
   my $self = $class->SUPER::new($mailsaobject);
   bless ($self, $class);
 
-  # how to handle failure to get the database handle?
-  # and we don't really have a valid return value...
-  # can we defer getting this handle until we actually see
-  # a uri_block_cc rule?
-
   $self->register_eval_rule("check_uri_local_bl");
-
   $self->set_config($mailsaobject->{conf});
 
   return $self;
@@ -144,8 +138,6 @@ sub set_config {
   my ($self, $conf) = @_;
   my @cmds;
 
-  my $pluginobj = $self;        # allow use inside the closure below
-
   push (@cmds, {
     setting => 'uri_block_cc',
     is_priv => 1,
@@ -153,35 +145,35 @@ sub set_config {
       my ($self, $key, $value, $line) = @_;
 
       if ($value !~ /^(\S+)\s+(.+)$/) {
-	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
       }
       my $name = $1;
       my $def = $2;
       my $added_criteria = 0;
 
-      $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{countries} = {};
+      $conf->{parser}->{conf}->{urilocalbl}->{$name}->{countries} = {};
 
       # this should match all country codes including satellite providers
       while ($def =~ m/^\s*([a-z][a-z0-9])(\s+(.*)|)$/) {
-	my $cc = $1;
-	my $rest = $2;
+        my $cc = $1;
+        my $rest = $2;
 
-	#dbg("config: uri_block_cc adding %s to %s\n", $cc, $name);
-        $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{countries}->{uc($cc)} = 1;
-	$added_criteria = 1;
+        #dbg("config: uri_block_cc adding $cc to $name");
+        $conf->{parser}->{conf}->{urilocalbl}->{$name}->{countries}->{uc($cc)} = 1;
+        $added_criteria = 1;
 
         $def = $rest;
       }
 
       if ($added_criteria == 0) {
         warn "config: no arguments";
-	return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
+        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
       } elsif ($def ne '') {
         warn "config: failed to add invalid rule $name";
-	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
       }
 
-      dbg("config: uri_block_cc added %s\n", $name);
+      dbg("config: uri_block_cc added $name");
 
       $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
     }
@@ -194,35 +186,35 @@ sub set_config {
       my ($self, $key, $value, $line) = @_;
 
       if ($value !~ /^(\S+)\s+(.+)$/) {
-	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
       }
       my $name = $1;
       my $def = $2;
       my $added_criteria = 0;
 
-      $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{continents} = {};
+      $conf->{parser}->{conf}->{urilocalbl}->{$name}->{continents} = {};
 
       # this should match all continent codes
       while ($def =~ m/^\s*([a-z]{2})(\s+(.*)|)$/) {
-	my $cont = $1;
-	my $rest = $2;
+        my $cont = $1;
+        my $rest = $2;
 
-	# dbg("config: uri_block_cont adding %s to %s\n", $cont, $name);
-        $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{continents}->{uc($cont)} = 1;
-	$added_criteria = 1;
+        # dbg("config: uri_block_cont adding $cont to $name");
+        $conf->{parser}->{conf}->{urilocalbl}->{$name}->{continents}->{uc($cont)} = 1;
+        $added_criteria = 1;
 
         $def = $rest;
       }
 
       if ($added_criteria == 0) {
         warn "config: no arguments";
-	return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
+        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
       } elsif ($def ne '') {
         warn "config: failed to add invalid rule $name";
-	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
       }
 
-      dbg("config: uri_block_cont added %s\n", $name);
+      dbg("config: uri_block_cont added $name");
 
       $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
     }
@@ -235,32 +227,33 @@ sub set_config {
       my ($self, $key, $value, $line) = @_;
 
       if ($value !~ /^(\S+)\s+(.+)$/) {
-	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
       }
       my $name = $1;
       my $def = $2;
       my $added_criteria = 0;
 
-      $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{isps} = {};
+      $conf->{parser}->{conf}->{urilocalbl}->{$name}->{isps} = {};
 
       # gather up quoted strings
       while ($def =~ m/^\s*"([^"]*)"(\s+(.*)|)$/) {
-	my $isp = $1;
-	my $rest = $2;
+        my $isp = $1;
+        my $rest = $2;
 
-	dbg("config: uri_block_isp adding \"%s\" to %s\n", $isp, $name);
-        $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{isps}->{$isp} = 1;
-	$added_criteria = 1;
+        dbg("config: uri_block_isp adding \"$isp\" to $name");
+        my $ispkey = uc($isp); $ispkey =~ s/\s+//gs;
+        $conf->{parser}->{conf}->{urilocalbl}->{$name}->{isps}->{$ispkey} = $isp;
+        $added_criteria = 1;
 
         $def = $rest;
       }
 
       if ($added_criteria == 0) {
         warn "config: no arguments";
-	return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
+        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
       } elsif ($def ne '') {
         warn "config: failed to add invalid rule $name";
-	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
       }
 
       $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
@@ -274,39 +267,39 @@ sub set_config {
       my ($self, $key, $value, $line) = @_;
 
       if ($value !~ /^(\S+)\s+(.+)$/) {
-	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
       }
       my $name = $1;
       my $def = $2;
       my $added_criteria = 0;
 
-      $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{cidr} = new Net::CIDR::Lite;
+      $conf->{parser}->{conf}->{urilocalbl}->{$name}->{cidr} = new Net::CIDR::Lite;
 
       # match individual IP's, subnets, and ranges
       while ($def =~ m/^\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2}|-\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?)(\s+(.*)|)$/) {
-	my $addr = $1;
-	my $rest = $3;
+        my $addr = $1;
+        my $rest = $3;
 
-	dbg("config: uri_block_cidr adding %s to %s\n", $addr, $name);
+        dbg("config: uri_block_cidr adding $addr to $name");
 
-        eval { $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{cidr}->add_any($addr) };
+        eval { $conf->{parser}->{conf}->{urilocalbl}->{$name}->{cidr}->add_any($addr) };
         last if ($@);
 
-	$added_criteria = 1;
+        $added_criteria = 1;
 
         $def = $rest;
       }
 
       if ($added_criteria == 0) {
         warn "config: no arguments";
-	return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
+        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
       } elsif ($def ne '') {
         warn "config: failed to add invalid rule $name";
-	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
       }
 
       # optimize the ranges
-      $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{cidr}->clean();
+      $conf->{parser}->{conf}->{urilocalbl}->{$name}->{cidr}->clean();
 
       $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
     }
@@ -319,346 +312,178 @@ sub set_config {
       my ($self, $key, $value, $line) = @_;
 
       if ($value !~ /^(\S+)\s+(.+)$/) {
-	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
+        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
       }
       my $name = $1;
       my $def = $2;
       my $added_criteria = 0;
 
-      $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{exclusions} = {};
+      $conf->{parser}->{conf}->{urilocalbl}->{$name}->{exclusions} = {};
 
       # match individual IP's, or domain names
       while ($def =~ m/^\s*((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(([a-z0-9][-a-z0-9]*[a-z0-9](\.[a-z0-9][-a-z0-9]*[a-z0-9]){1,})))(\s+(.*)|)$/) {
-	my $addr = $1;
-	my $rest = $6;
+        my $addr = $1;
+        my $rest = $6;
 
-	dbg("config: uri_block_exclude adding %s to %s\n", $addr, $name);
+        dbg("config: uri_block_exclude adding $addr to $name");
 
-        $conf->{parser}->{conf}->{uri_local_bl}->{$name}->{exclusions}->{$addr} = 1;
+        $conf->{parser}->{conf}->{urilocalbl}->{$name}->{exclusions}->{$addr} = 1;
 
-	$added_criteria = 1;
+        $added_criteria = 1;
 
         $def = $rest;
       }
 
       if ($added_criteria == 0) {
         warn "config: no arguments";
-	return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
+        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
       } elsif ($def ne '') {
         warn "config: failed to add invalid rule $name";
-	return $Mail::SpamAssassin::Conf::INVALID_VALUE;
-      }
-
-      $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
-    }
-  });
-
-=over 2  
-
-=item uri_country_db_path STRING
-
-This option tells SpamAssassin where to find the MaxMind country GeoIP2 
-database.
-
-=back
-
-=cut
-
-  push (@cmds, {
-    setting => 'uri_country_db_path',
-    is_priv => 1,
-    default => undef,
-    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
-    code => sub {
-      my ($self, $key, $value, $line) = @_;
-      if (!defined $value || !length $value) {
-        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
-      }
-      if (!-f $value) {
-        info("config: uri_country_db_path \"$value\" is not accessible");
-        $self->{uri_country_db_path} = $value;
         return $Mail::SpamAssassin::Conf::INVALID_VALUE;
       }
 
-      $self->{uri_country_db_path} = $value;
+      $conf->{parser}->add_test($name, 'check_uri_local_bl()', $Mail::SpamAssassin::Conf::TYPE_BODY_EVALS);
     }
   });
 
-=over 2
-
-=item uri_country_db_isp_path STRING
-
-This option tells SpamAssassin where to find the MaxMind isp GeoIP2 database.
-
-=back
-
-=cut
-
-  push (@cmds, {
-    setting => 'uri_country_db_isp_path',
-    is_priv => 1,
-    default => undef,
-    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
-    code => sub {
-      my ($self, $key, $value, $line) = @_;
-      if (!defined $value || !length $value) {
-        return $Mail::SpamAssassin::Conf::MISSING_REQUIRED_VALUE;
-      }
-      if (!-f $value) {
-        info("config: uri_country_db_isp_path \"$value\" is not accessible");
-        $self->{uri_country_db_isp_path} = $value;
-        return $Mail::SpamAssassin::Conf::INVALID_VALUE;
-      }
-
-      $self->{uri_country_db_isp_path} = $value;
-    }
-  });  
- 
   $conf->{parser}->register_commands(\@cmds);
-}  
+}
 
 sub check_uri_local_bl {
-  my ($self, $permsg) = @_;
+  my ($self, $pms) = @_;
 
-  my $cc;
-  my $cont;
-  my $db_info;
-  my $isp;
- 
-  my $conf_country_db_path = $self->{'main'}{'resolver'}{'conf'}->{uri_country_db_path};
-  my $conf_country_db_isp_path = $self->{'main'}{'resolver'}{'conf'}->{uri_country_db_isp_path};
-  # If country_db_path is set I am using GeoIP2 api
-  if ( HAS_GEOIP2 and ( ( defined $conf_country_db_path ) or ( defined $conf_country_db_isp_path ) ) ) {
-
-    $self->{geoip} = GeoIP2::Database::Reader->new(
-  		file	=> $conf_country_db_path,
-  		locales	=> [ 'en' ]
-    ) if (( defined $conf_country_db_path ) && ( -f $conf_country_db_path));
-    if ( defined ($conf_country_db_path) ) {
-      $db_info = sub { return "GeoIP2 " . ($self->{geoip}->metadata()->description()->{en} || '?') };
-      warn "$conf_country_db_path not found" unless $self->{geoip};
-    }
+  return 0 if $self->{urilocalbl_disabled};
 
-    $self->{geoisp} = GeoIP2::Database::Reader->new(
-  		file	=> $conf_country_db_isp_path,
-  		locales	=> [ 'en' ]
-    ) if (( defined $conf_country_db_isp_path ) && ( -f $conf_country_db_isp_path));
-    if ( defined ($conf_country_db_isp_path) ) {
-      warn "$conf_country_db_isp_path not found" unless $self->{geoisp};
-    }
-    $self->{use_geoip2} = 1;
-  } elsif ( HAS_GEOIP ) {
-    BEGIN {
-      Geo::IP->import( qw(GEOIP_MEMORY_CACHE GEOIP_CHECK_CACHE GEOIP_ISP_EDITION) );
+  if (!$self->{geodb}) {
+    eval {
+      $self->{geodb} = Mail::SpamAssassin::GeoDB->new({
+        conf => $pms->{conf}->{geodb},
+        wanted => { country => 1, city => 1, isp => 1 },
+      });
+    };
+    if (!$self->{geodb}) {
+      dbg("plugin disabled: $@");
+      $self->{urilocalbl_disabled} = 1;
+      return 0;
     }
-    $self->{use_geoip2} = 0;
-    # need GeoIP C library 1.6.3 and GeoIP perl API 1.4.4 or later to avoid messages leaking - Bug 7153
-    my $gic_wanted = version->parse('v1.6.3');
-    my $gic_have = version->parse(Geo::IP->lib_version());
-    my $gip_wanted = version->parse('v1.4.4');
-    my $gip_have = version->parse($Geo::IP::VERSION);
-
-    # this code burps an ugly message if it fails, but that's redirected elsewhere
-    my $flags = 0;
-    eval '$flags = Geo::IP::GEOIP_SILENCE' if ($gip_wanted >= $gip_have);
-
-    if ($flags && $gic_wanted >= $gic_have) {
-      $self->{geoip} = Geo::IP->new(GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE | $flags);
-      $self->{geoisp} = Geo::IP->open_type(GEOIP_ISP_EDITION, GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE | $flags);
-    } else {
-      open(OLDERR, ">&STDERR");
-      open(STDERR, ">", "/dev/null");
-      $self->{geoip} = Geo::IP->new(GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE);
-      $self->{geoisp} = Geo::IP->open_type(GEOIP_ISP_EDITION, GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE);
-      open(STDERR, ">&OLDERR");
-      close(OLDERR);
-    }
-  $db_info = sub { return "Geo::IP " . ($self->{geoip}->database_info || '?') };
-  } else {
-    dbg("No GeoIP module available");
-    return 0;
   }
+  my $geodb = $self->{geodb};
+
+  my $test = $pms->get_current_eval_rule_name();
+  my $rule = $pms->{conf}->{urilocalbl}->{$test};
 
-  my %uri_detail = %{ $permsg->get_uri_detail_list() };
-  my $test = $permsg->{current_rule_name}; 
-  my $rule = $permsg->{conf}->{uri_local_bl}->{$test};
+  dbg("running $test");
 
-  my %hit_tests;
-  my $got_hit = 0;
   my @addrs;
   my $IP_ADDRESS = IP_ADDRESS;
-  
-  if ( defined $self->{geoip} ) {
-    dbg("check: uri_local_bl evaluating rule %s using database %s\n", $test, $db_info->());
-  } else {
-    dbg("check: uri_local_bl evaluating rule %s\n", $test);
-  }
-
-  while (my ($raw, $info) = each %uri_detail) {
 
+  foreach my $info (values %{$pms->get_uri_detail_list()}) {
     next unless $info->{hosts};
 
     # look for W3 links only
-    next unless (defined $info->{types}->{a});
+    next unless defined $info->{types}->{a};
 
-    while (my($host, $domain) = each %{$info->{hosts}}) {
-
-      # skip if the domain name was matched
-      if (exists $rule->{exclusions} && exists $rule->{exclusions}->{$domain}) {
-        dbg("check: uri_local_bl excludes %s as *.%s\n", $host, $domain);
-        next;
-      }
-
-      if($host !~ /^$IP_ADDRESS$/) {
-       # this would be best cached from prior lookups
-       @addrs = gethostbyname($host);
-       # convert to string values address list
-       @addrs = map { inet_ntoa($_); } @addrs[4..$#addrs];
+    my %hosts = %{$info->{hosts}}; # evade hash reset by copy
+    HOST: while (my($host, $domain) = each %hosts) {
+      if (defined $rule->{exclusions}->{$domain}) {
+        dbg("excluded $host, domain $domain matches");
+        next HOST;
+      }
+
+      if ($host !~ /^$IP_ADDRESS$/o) {
+        # this would be best cached from prior lookups
+        # TODO async extract_metadata lookup
+        @addrs = gethostbyname($host);
+        # convert to string values address list
+        @addrs = map { inet_ntoa($_); } @addrs[4..$#addrs];
+        if (@addrs) {
+          dbg("$host IP-addresses: ".join(', ', @addrs));
+        } else {
+          dbg("$host failed to resolve IP-addresses");
+        }
       } else {
-       @addrs = ($host);
+        @addrs = ($host);
       }
 
-      dbg("check: uri_local_bl %s addrs %s\n", $host, join(', ', @addrs));
+      next HOST unless @addrs;
 
-      for my $ip (@addrs) {
-        # skip if the address was matched
-        if (exists $rule->{exclusions} && exists $rule->{exclusions}->{$ip}) {
-          dbg("check: uri_local_bl excludes %s(%s)\n", $host, $ip);
-          next;
+      foreach my $ip (@addrs) {
+        if (defined $rule->{exclusions}->{$ip}) {
+          dbg("excluded $host, ip $ip matches");
+          next HOST;
         }
+      }
 
-        if (exists $rule->{countries}) {
-          dbg("check: uri_local_bl countries %s\n", join(' ', sort keys %{$rule->{countries}}));
-
-          if ( $self->{use_geoip2} == 1 ) {
-            my $country = $self->{geoip}->country( ip => $ip );
-            my $country_rec = $country->country();
-            $cc = $country_rec->iso_code();
+      if (defined $rule->{countries}) {
+        my $testcc = join(',', sort keys %{$rule->{countries}});
+        dbg("checking $host for countries: $testcc");
+        foreach my $ip (@addrs) {
+          my $cc = $geodb->get_country($ip);
+          if (defined $rule->{countries}->{$cc}) {
+            dbg("$host ($ip) country $cc - HIT");
+            $pms->test_log("Host: $host in country $cc");
+            return 1; # hit
           } else {
-            $cc = $self->{geoip}->country_code_by_addr($ip);
+            dbg("$host ($ip) country $cc - no match");
           }
-
-          dbg("check: uri_local_bl host %s(%s) maps to %s\n", $host, $ip, (defined $cc ? $cc : "(undef)"));
-
-          # handle there being no associated country (yes, there are holes in
-          # the database).
-          next unless defined $cc;
-
-          # not in blacklist
-          next unless (exists $rule->{countries}->{$cc});
-
-          dbg("check: uri_block_cc host %s(%s) matched\n", $host, $ip);
-
-          if (would_log('dbg', 'rules') > 1) {
-            dbg("check: uri_block_cc criteria for $test met");
-          }
-      
-          $permsg->test_log("Host: $host in $cc");
-          $hit_tests{$test} = 1;
-
-          # reset hash
-          keys %uri_detail;
         }
+      }
 
-        if (exists $rule->{continents}) {
-          dbg("check: uri_local_bl continents %s\n", join(' ', sort keys %{$rule->{continents}}));
-
-          if ( $self->{use_geoip2} == 1 ) {
-            my $country = $self->{geoip}->country( ip => $ip );
-            my $cont_rec = $country->continent();
-            $cont = $cont_rec->{code};
+      if (defined $rule->{continents}) {
+        my $testcont = join(',', sort keys %{$rule->{continents}});
+        dbg("checking $host for continents: $testcont");
+        foreach my $ip (@addrs) {
+          my $cc = $geodb->get_continent($ip);
+          if (defined $rule->{continents}->{$cc}) {
+            dbg("$host ($ip) continent $cc - HIT");
+            $pms->test_log("Host: $host in continent $cc");
+            return 1; # hit
           } else {
-            $cc = $self->{geoip}->country_code_by_addr($ip);
-            $cont = $self->{geoip}->continent_code_by_country_code($cc);
-          }
-          
-          dbg("check: uri_local_bl host %s(%s) maps to %s\n", $host, $ip, (defined $cont ? $cont : "(undef)"));
-
-          # handle there being no associated continent (yes, there are holes in
-          # the database).
-          next unless defined $cont;
-
-          # not in blacklist
-          next unless (exists $rule->{continents}->{$cont});
-
-          dbg("check: uri_block_cont host %s(%s) matched\n", $host, $ip);
-
-          if (would_log('dbg', 'rules') > 1) {
-            dbg("check: uri_block_cont criteria for $test met");
+            dbg("$host ($ip) continent $cc - no match");
           }
-
-          $permsg->test_log("Host: $host in $cont");
-          $hit_tests{$test} = 1;
-
-          # reset hash
-          keys %uri_detail;
         }
+      }
 
-        if (exists $rule->{isps}) {
-          dbg("check: uri_local_bl isps %s\n", join(' ', map { '"' . $_ . '"'; } sort keys %{$rule->{isps}}));
-
-          if ( $self->{use_geoip2} == 1 ) {
-            $isp = $self->{geoisp}->isp(ip => $ip);
-          } else {
-            $isp = $self->{geoisp}->isp_by_name($ip);
-          }
-
-          dbg("check: uri_local_bl isp %s(%s) maps to %s\n", $host, $ip, (defined $isp ? '"' . $isp . '"' : "(undef)"));
-
-          # handle there being no associated country
-          next unless defined $isp;
-
-          # not in blacklist
-          next unless (exists $rule->{isps}->{$isp});
-
-          dbg("check: uri_block_isp host %s(%s) matched\n", $host, $ip);
-
-          if (would_log('dbg', 'rules') > 1) {
-            dbg("check: uri_block_isp criteria for $test met");
+      if (defined $rule->{isps}) {
+        if ($geodb->can('isp')) {
+          my $testisp = join(', ', map {"\"$_\""} sort values %{$rule->{isps}});
+          dbg("checking $host for isps: $testisp");
+
+          foreach my $ip (@addrs) {
+            my $isp = $geodb->get_isp($ip);
+            next unless defined $isp;
+            my $ispkey = uc($isp); $ispkey =~ s/\s+//gs;
+            if (defined $rule->{isps}->{$ispkey}) {
+              dbg("$host ($ip) isp \"$isp\" - HIT");
+              $pms->test_log("Host: $host in isp $isp");
+              return 1; # hit
+            } else {
+              dbg("$host ($ip) isp $isp - no match");
+            }
           }
-      
-          $permsg->test_log("Host: $host in \"$isp\"");
-          $hit_tests{$test} = 1;
-
-          # reset hash
-          keys %uri_detail;
+        } else {
+          dbg("skipping ISP check, GeoDB database not loaded");
         }
+      }
 
-        if (exists $rule->{cidr}) {
-          dbg("check: uri_block_cidr list %s\n", join(' ', $rule->{cidr}->list_range()));
-
-          next unless ($rule->{cidr}->find($ip));
-
-          dbg("check: uri_block_cidr host %s(%s) matched\n", $host, $ip);
-
-          if (would_log('dbg', 'rules') > 1) {
-            dbg("check: uri_block_cidr criteria for $test met");
+      if (defined $rule->{cidr}) {
+        my $testcidr = join(' ', $rule->{cidr}->list_range());
+        dbg("checking $host for cidrs: $testcidr");
+
+        foreach my $ip (@addrs) {
+          if ($rule->{cidr}->find($ip)) {
+            dbg("$host ($ip) matches cidr - HIT");
+            $pms->test_log("Host: $host in cidr");
+            return 1; # hit
+          } else {
+            dbg("$host ($ip) not matching cidr");
           }
-
-          $permsg->test_log("Host: $host as $ip");
-          $hit_tests{$test} = 1;
-
-          # reset hash
-          keys %uri_detail;
         }
       }
     }
-    # cycle through all tests hitted by the uri
-    while((my $test_ok) = each %hit_tests) {
-      $permsg->got_hit($test_ok);
-      $got_hit = 1;
-    }
-    if($got_hit == 1) {
-      return 1;
-    } else {
-      keys %hit_tests;
-    }
   }
 
-  dbg("check: uri_local_bl %s no match\n", $test);
-
   return 0;
 }
 
 1;
-

Added: spamassassin/trunk/t/data/geodb/GeoIP2-City.mmdb
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/geodb/GeoIP2-City.mmdb?rev=1841681&view=auto
==============================================================================
Binary file - no diff available.

Propchange: spamassassin/trunk/t/data/geodb/GeoIP2-City.mmdb
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: spamassassin/trunk/t/data/geodb/GeoIP2-Country.mmdb
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/geodb/GeoIP2-Country.mmdb?rev=1841681&view=auto
==============================================================================
Binary file - no diff available.

Propchange: spamassassin/trunk/t/data/geodb/GeoIP2-Country.mmdb
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: spamassassin/trunk/t/data/geodb/GeoIP2-ISP.mmdb
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/geodb/GeoIP2-ISP.mmdb?rev=1841681&view=auto
==============================================================================
Binary file - no diff available.

Propchange: spamassassin/trunk/t/data/geodb/GeoIP2-ISP.mmdb
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: spamassassin/trunk/t/data/geodb/GeoIPCity.dat
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/geodb/GeoIPCity.dat?rev=1841681&view=auto
==============================================================================
Binary file - no diff available.

Propchange: spamassassin/trunk/t/data/geodb/GeoIPCity.dat
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: spamassassin/trunk/t/data/geodb/GeoIPISP.dat
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/geodb/GeoIPISP.dat?rev=1841681&view=auto
==============================================================================
Binary file - no diff available.

Propchange: spamassassin/trunk/t/data/geodb/GeoIPISP.dat
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: spamassassin/trunk/t/data/geodb/create_GeoIP2-City.pl
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/geodb/create_GeoIP2-City.pl?rev=1841681&view=auto
==============================================================================
--- spamassassin/trunk/t/data/geodb/create_GeoIP2-City.pl (added)
+++ spamassassin/trunk/t/data/geodb/create_GeoIP2-City.pl Sat Sep 22 13:40:58 2018
@@ -0,0 +1,134 @@
+#!/usr/bin/perl
+
+use MaxMind::DB::Writer::Tree;
+
+my %types = (
+  accuracy_radius => 'uint32',
+  autonomous_system_number => 'uint32',
+  is_in_european_union => 'uint32',
+  autonomous_system_organization => 'utf8_string',
+  city => 'map',
+  continent => 'map',
+  country => 'map',
+  en => 'utf8_string',
+  geoname_id => 'uint32',
+  iso_code => 'utf8_string',
+  code => 'utf8_string',
+  isp => 'utf8_string',
+  latitude => 'double',
+  location => 'map',
+  longitude => 'double',
+  names => 'map',
+  organization => 'utf8_string',
+  registered_country => 'map',
+  time_zone => 'utf8_string',
+  subdivisions => ['array', 'map'],
+);
+
+my $tree = MaxMind::DB::Writer::Tree->new(
+  database_type => 'GeoIP2-City',
+  description => { en => 'SpamAssassin test data' },
+  ip_version => 6,
+  record_size => 28,
+  map_key_type_callback => sub { $types{ $_[0] } },
+);
+
+$tree->insert_network(
+    '8.8.8.8/32' => {
+          'country' => {
+                         'iso_code' => 'US',
+                         'is_in_european_union' => 0,
+                         'names' => {
+                                      'en' => 'United States',
+                                    },
+                         'geoname_id' => 6252001
+                       },
+          'continent' => {
+                           'names' => {
+                                        'en' => 'North America',
+                                      },
+                           'geoname_id' => 6255149,
+                           'code' => 'NA'
+                         },
+          'subdivisions' => [
+                              {
+                                'names' => {
+                                             'en' => 'United States',
+                                           },
+                                'geoname_id' => 6269131,
+                                'iso_code' => 'USA'
+                              }
+                            ],
+          'city' => {
+                      'names' => {
+                                   'en' => 'New York',
+                                 },
+                      'geoname_id' => 5128581
+                    },
+          'registered_country' => {
+                                    'iso_code' => 'US',
+                                    'geoname_id' => 6252001,
+                                    'names' => {
+                                                 'en' => 'United States'
+                                               }
+                                  },
+          'location' => {
+                          'latitude' => '43.0003',
+                          'longitude' => '-75.4999',
+                          'time_zone' => 'America/New_York',
+                          'accuracy_radius' => 10
+                        }
+    },
+);
+$tree->insert_network(
+    '2001:4860:4860::8888/128' => {
+          'country' => {
+                         'iso_code' => 'US',
+                         'is_in_european_union' => 0,
+                         'names' => {
+                                      'en' => 'United States',
+                                    },
+                         'geoname_id' => 6252001
+                       },
+          'continent' => {
+                           'names' => {
+                                        'en' => 'North America',
+                                      },
+                           'geoname_id' => 6255149,
+                           'code' => 'NA'
+                         },
+          'subdivisions' => [
+                              {
+                                'names' => {
+                                             'en' => 'United States',
+                                           },
+                                'geoname_id' => 6269131,
+                                'iso_code' => 'USA'
+                              }
+                            ],
+          'city' => {
+                      'names' => {
+                                   'en' => 'New York',
+                                 },
+                      'geoname_id' => 5128581
+                    },
+          'registered_country' => {
+                                    'iso_code' => 'US',
+                                    'geoname_id' => 6252001,
+                                    'names' => {
+                                                 'en' => 'United States'
+                                               }
+                                  },
+          'location' => {
+                          'latitude' => '43.0003',
+                          'longitude' => '-75.4999',
+                          'time_zone' => 'America/New_York',
+                          'accuracy_radius' => 10
+                        }
+    },
+);
+
+open my $fh, '>:raw', 'GeoIP2-City.mmdb';
+$tree->write_tree($fh);
+close $fh;
+

Added: spamassassin/trunk/t/data/geodb/create_GeoIP2-Country.pl
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/geodb/create_GeoIP2-Country.pl?rev=1841681&view=auto
==============================================================================
--- spamassassin/trunk/t/data/geodb/create_GeoIP2-Country.pl (added)
+++ spamassassin/trunk/t/data/geodb/create_GeoIP2-Country.pl Sat Sep 22 13:40:58 2018
@@ -0,0 +1,78 @@
+#!/usr/bin/perl
+
+use MaxMind::DB::Writer::Tree;
+
+my %types = (
+  code => 'utf8_string',
+  continent => 'map',
+  country => 'map',
+  en => 'utf8_string',
+  geoname_id => 'uint32',
+  iso_code => 'utf8_string',
+  names => 'map',
+  registered_country => 'map',
+);
+
+my $tree = MaxMind::DB::Writer::Tree->new(
+  database_type => 'GeoIP2-Country',
+  description => { en => 'SpamAssassin test data' },
+  ip_version => 6,
+  record_size => 28,
+  map_key_type_callback => sub { $types{ $_[0] } },
+);
+
+$tree->insert_network(
+    '8.8.8.8/32' => {
+        'continent' => {
+            'code' => 'NA',
+            'geoname_id' => 6255149,
+            'names' => {
+                'en' => 'North America',
+            },
+        },
+        'country' => {
+            'iso_code' => 'US',
+            'geoname_id' => 6252001,
+            'names' => {
+                'en' => 'United States',
+            },
+        },
+        'registered_country' => {
+            'iso_code' => 'US',
+            'geoname_id' => 6252001,
+            'names' => {
+                'en' => 'United States',
+            },
+        },
+    },
+);
+$tree->insert_network(
+    '2001:4860:4860::8888/128' => {
+        'continent' => {
+            'code' => 'NA',
+            'geoname_id' => 6255149,
+            'names' => {
+                'en' => 'North America',
+            },
+        },
+        'country' => {
+            'iso_code' => 'US',
+            'geoname_id' => 6252001,
+            'names' => {
+                'en' => 'United States',
+            },
+        },
+        'registered_country' => {
+            'iso_code' => 'US',
+            'geoname_id' => 6252001,
+            'names' => {
+                'en' => 'United States',
+            },
+        },
+    },
+);
+
+open my $fh, '>:raw', 'GeoIP2-Country.mmdb';
+$tree->write_tree($fh);
+close $fh;
+

Added: spamassassin/trunk/t/data/geodb/create_GeoIP2-ISP.pl
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/geodb/create_GeoIP2-ISP.pl?rev=1841681&view=auto
==============================================================================
--- spamassassin/trunk/t/data/geodb/create_GeoIP2-ISP.pl (added)
+++ spamassassin/trunk/t/data/geodb/create_GeoIP2-ISP.pl Sat Sep 22 13:40:58 2018
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+
+use MaxMind::DB::Writer::Tree;
+
+my %types = (
+  isp => 'utf8_string',
+  autonomous_system_organization => 'utf8_string',
+  organization => 'utf8_string',
+  autonomous_system_number => 'uint32',
+);
+
+my $tree = MaxMind::DB::Writer::Tree->new(
+  database_type => 'GeoIP2-ISP',
+  description => { en => 'SpamAssassin test data' },
+  ip_version => 6,
+  record_size => 28,
+  map_key_type_callback => sub { $types{ $_[0] } },
+);
+
+$tree->insert_network(
+    '8.8.8.8/32' => {
+        'isp' => 'Level 3 Communications',
+        'autonomous_system_organization' => 'GOOGLE - Google LLC, US',
+        'organization' => 'Google',
+        'autonomous_system_number' => 15169,
+    },
+);
+$tree->insert_network(
+    '2001:4860:4860::8888/128' => {
+        'isp' => 'Level 3 Communications',
+        'autonomous_system_organization' => 'GOOGLE - Google LLC, US',
+        'organization' => 'Google',
+        'autonomous_system_number' => 15169,
+    },
+);
+
+open my $fh, '>:raw', 'GeoIP2-ISP.mmdb';
+$tree->write_tree($fh);
+close $fh;
+

Added: spamassassin/trunk/t/data/geodb/create_GeoIPCity.README
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/geodb/create_GeoIPCity.README?rev=1841681&view=auto
==============================================================================
--- spamassassin/trunk/t/data/geodb/create_GeoIPCity.README (added)
+++ spamassassin/trunk/t/data/geodb/create_GeoIPCity.README Sat Sep 22 13:40:58 2018
@@ -0,0 +1,16 @@
+
+Instructions:
+
+git clone https://github.com/mteodoro/mmutils.git
+cd mmutils
+virtualenv env
+source env/bin/activate
+pip install -r requirements.txt
+
+python -c 'exec("import sys,socket\nfor ip in sys.argv[1:]:\n print ip, int(socket.inet_aton(ip).encode(\"hex\"),16)\n")' 8.8.8.8
+
+echo 'startIP,endIP,country,region,city,postalCode,latitude,longitude,metroCode,areaCode
+134744072,134744072,"US","CA","Redwood City","94063",37.4914,-122.2110,807,650' >GeoIPCity.csv
+
+python csv2dat.py -w GeoIPCity.dat mmcity GeoIPCity.csv
+

Added: spamassassin/trunk/t/data/geodb/create_GeoIPISP.README
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/geodb/create_GeoIPISP.README?rev=1841681&view=auto
==============================================================================
--- spamassassin/trunk/t/data/geodb/create_GeoIPISP.README (added)
+++ spamassassin/trunk/t/data/geodb/create_GeoIPISP.README Sat Sep 22 13:40:58 2018
@@ -0,0 +1,17 @@
+
+Instructions:
+
+git clone https://github.com/mteodoro/mmutils.git
+cd mmutils
+virtualenv env
+source env/bin/activate
+pip install -r requirements.txt
+
+python -c 'exec("import sys,socket\nfor ip in sys.argv[1:]:\n print ip, int(socket.inet_aton(ip).encode(\"hex\"),16)\n")' 8.8.8.8
+
+echo 'startIP,endIP,isp
+0,0,"SpamAssassin test data"
+134744072,134744072,"Level 3 Communications"' >GeoIPISP.csv
+
+python csv2dat.py -w GeoIPISP.dat mmisp GeoIPISP.csv
+

Modified: spamassassin/trunk/t/data/spam/relayUS.eml
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/data/spam/relayUS.eml?rev=1841681&r1=1841680&r2=1841681&view=diff
==============================================================================
--- spamassassin/trunk/t/data/spam/relayUS.eml (original)
+++ spamassassin/trunk/t/data/spam/relayUS.eml Sat Sep 22 13:40:58 2018
@@ -1,7 +1,7 @@
-Received: from mail-wm0-f66.google.com (mail-wm0-f66.google.com [74.125.82.66])
+Received: from google-public-dns-a.google.com (google-public-dns-a.google.com [8.8.8.8])
 	by in.example.com (Postfix) with ESMTPS
 	for <te...@example.com>; Wed, 18 Jul 2018 21:12:22 +0200 (CEST)
-Received: by mail-wm0-f66.google.com with SMTP id f21-v6so3811271wmc.5
+Received: by google-public-dns-a.google.com with SMTP id f21-v6so3811271wmc.5
         for <te...@example.com>; Wed, 18 Jul 2018 12:12:22 -0700 (PDT)
 From: <te...@gmail.com>
 To: test@example.com

Modified: spamassassin/trunk/t/relaycountry_fast.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/relaycountry_fast.t?rev=1841681&r1=1841680&r2=1841681&view=diff
==============================================================================
--- spamassassin/trunk/t/relaycountry_fast.t (original)
+++ spamassassin/trunk/t/relaycountry_fast.t Sat Sep 22 13:40:58 2018
@@ -28,8 +28,8 @@ loadplugin Mail::SpamAssassin::Plugin::R
 ");
 
 tstprefs ("
-        dns_available no
-        country_db_type Fast
+        geodb_module Fast
+
         add_header all Relay-Country _RELAYCOUNTRY_
         ");
 

Modified: spamassassin/trunk/t/relaycountry_geoip.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/relaycountry_geoip.t?rev=1841681&r1=1841680&r2=1841681&view=diff
==============================================================================
--- spamassassin/trunk/t/relaycountry_geoip.t (original)
+++ spamassassin/trunk/t/relaycountry_geoip.t Sat Sep 22 13:40:58 2018
@@ -15,13 +15,10 @@ use lib '.'; use lib 't';
 use SATest; sa_t_init("relaycountry");
 
 use constant HAS_GEOIP => eval { require Geo::IP; };
-use constant HAS_GEOIP_CONF => eval { Geo::IP->new(Geo::IP::GEOIP_STANDARD); };
 
 use Test::More;
 
 plan skip_all => "Geo::IP not installed" unless HAS_GEOIP;
-plan skip_all => "Geo::IP not configured" unless HAS_GEOIP_CONF;
-
 plan tests => 2;
 
 # ---------------------------------------------------------------------------
@@ -31,8 +28,9 @@ loadplugin Mail::SpamAssassin::Plugin::R
 ");
 
 tstprefs ("
-        dns_available no
-        country_db_type GeoIP
+        geodb_module GeoIP
+        geoip_search_path data/geodb
+
         add_header all Relay-Country _RELAYCOUNTRY_
         ");
 

Modified: spamassassin/trunk/t/relaycountry_geoip2.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/relaycountry_geoip2.t?rev=1841681&r1=1841680&r2=1841681&view=diff
==============================================================================
--- spamassassin/trunk/t/relaycountry_geoip2.t (original)
+++ spamassassin/trunk/t/relaycountry_geoip2.t Sat Sep 22 13:40:58 2018
@@ -16,21 +16,9 @@ use SATest; sa_t_init("relaycountry");
 
 use constant HAS_GEOIP2 => eval { require GeoIP2::Database::Reader; };
 
-# TODO: get the list from RelayCountry.pm / geoip2_default_db_path
-use constant HAS_GEOIP2_DB => eval {
-  -f "/usr/local/share/GeoIP/GeoIP2-Country.mmdb" or
-  -f "/usr/share/GeoIP/GeoIP2-Country.mmdb" or
-  -f "/var/lib/GeoIP/GeoIP2-Country.mmdb" or
-  -f "/usr/local/share/GeoIP/GeoLite2-Country.mmdb" or
-  -f "/usr/share/GeoIP/GeoLite2-Country.mmdb" or
-  -f "/var/lib/GeoIP/GeoLite2-Country.mmdb"
-};
-
 use Test::More;
 
 plan skip_all => "GeoIP2::Database::Reader not installed" unless HAS_GEOIP2;
-plan skip_all => "GeoIP2 database not found from default locations" unless HAS_GEOIP2_DB;
-
 plan tests => 2;
 
 # ---------------------------------------------------------------------------
@@ -40,8 +28,9 @@ loadplugin Mail::SpamAssassin::Plugin::R
 ");
 
 tstprefs ("
-        dns_available no
-        country_db_type GeoIP2
+        geodb_module GeoIP2
+        geoip_search_path data/geodb
+
         add_header all Relay-Country _RELAYCOUNTRY_
         ");
 

Added: spamassassin/trunk/t/urilocalbl_fast.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/urilocalbl_fast.t?rev=1841681&view=auto
==============================================================================
--- spamassassin/trunk/t/urilocalbl_fast.t (added)
+++ spamassassin/trunk/t/urilocalbl_fast.t Sat Sep 22 13:40:58 2018
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+
+BEGIN {
+  if (-e 't/test_dir') { # if we are running "t/rule_tests.t", kluge around ...
+    chdir 't';
+  }
+
+  if (-e 'test_dir') {            # running from test directory, not ..
+    unshift(@INC, '../blib/lib');
+    unshift(@INC, '../lib');
+  }
+}
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("urilocalbl");
+
+use constant HAS_COUNTRY_FAST => eval { require IP::Country::Fast; };
+
+use Test::More;
+
+plan skip_all => "No GeoDB module could be loaded" unless HAS_COUNTRY_FAST;
+#plan skip_all => "Net tests disabled"          unless conf_bool('run_net_tests');
+plan tests => 3;
+
+# ---------------------------------------------------------------------------
+
+tstpre ("
+loadplugin Mail::SpamAssassin::Plugin::URILocalBL
+");
+
+%patterns = (
+  q{ X_URIBL_USA } => 'USA',
+  q{ X_URIBL_NA } => 'north America',
+);
+
+tstlocalrules ("
+  geodb_module Fast
+
+  uri_block_cc X_URIBL_USA us
+  describe X_URIBL_USA uri located in USA
+  
+  uri_block_cont X_URIBL_NA na
+  describe X_URIBL_NA uri located in north America
+");
+
+ok sarun ("-L -t < data/spam/relayUS.eml", \&patterns_run_cb);
+ok_all_patterns();

Propchange: spamassassin/trunk/t/urilocalbl_fast.t
------------------------------------------------------------------------------
    svn:executable = *

Modified: spamassassin/trunk/t/urilocalbl_geoip.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/urilocalbl_geoip.t?rev=1841681&r1=1841680&r2=1841681&view=diff
==============================================================================
--- spamassassin/trunk/t/urilocalbl_geoip.t (original)
+++ spamassassin/trunk/t/urilocalbl_geoip.t Sat Sep 22 13:40:58 2018
@@ -15,13 +15,11 @@ use lib '.'; use lib 't';
 use SATest; sa_t_init("urilocalbl");
 
 use constant HAS_GEOIP => eval { require Geo::IP; };
-use constant HAS_GEOIP_CONF => eval { Geo::IP->new(GEOIP_MEMORY_CACHE | GEOIP_CHECK_CACHE); };
 
 use Test::More;
 
 plan skip_all => "Geo::IP not installed" unless HAS_GEOIP;
-plan skip_all => "Geo::IP not configured" unless HAS_GEOIP_CONF;
-plan tests => 3;
+plan tests => 4;
 
 # ---------------------------------------------------------------------------
 
@@ -32,16 +30,22 @@ loadplugin Mail::SpamAssassin::Plugin::U
 %patterns = (
   q{ X_URIBL_USA } => 'USA',
   q{ X_URIBL_NA } => 'north America',
+  q{ X_URIBL_ISP } => 'Level 3 Communications',
 );
 
-tstlocalrules (q{
-  dns_available no
+tstlocalrules ("
+  geodb_module GeoIP
+  geoip_search_path data/geodb
+
   uri_block_cc X_URIBL_USA us
   describe X_URIBL_USA uri located in USA
   
   uri_block_cont X_URIBL_NA na
   describe X_URIBL_NA uri located in north America
-});
 
-ok sarun ("-t < data/spam/relayUS.eml", \&patterns_run_cb);
+  uri_block_isp X_URIBL_ISP \"Level 3 Communications\"
+  describe X_URIBL_ISP isp is Level 3 Communications
+");
+
+ok sarun ("-L -t < data/spam/relayUS.eml", \&patterns_run_cb);
 ok_all_patterns();

Added: spamassassin/trunk/t/urilocalbl_geoip2.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/urilocalbl_geoip2.t?rev=1841681&view=auto
==============================================================================
--- spamassassin/trunk/t/urilocalbl_geoip2.t (added)
+++ spamassassin/trunk/t/urilocalbl_geoip2.t Sat Sep 22 13:40:58 2018
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+
+BEGIN {
+  if (-e 't/test_dir') { # if we are running "t/rule_tests.t", kluge around ...
+    chdir 't';
+  }
+
+  if (-e 'test_dir') {            # running from test directory, not ..
+    unshift(@INC, '../blib/lib');
+    unshift(@INC, '../lib');
+  }
+}
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("urilocalbl");
+
+use constant HAS_GEOIP => eval { require GeoIP2::Database::Reader; };
+
+use Test::More;
+
+plan skip_all => "GeoIP2::Database::Reader not installed" unless HAS_GEOIP;
+#plan skip_all => "Net tests disabled"          unless conf_bool('run_net_tests');
+plan tests => 4;
+
+# ---------------------------------------------------------------------------
+
+tstpre ("
+loadplugin Mail::SpamAssassin::Plugin::URILocalBL
+");
+
+%patterns = (
+  q{ X_URIBL_USA } => 'USA',
+  q{ X_URIBL_NA } => 'north America',
+  q{ X_URIBL_ISP } => 'Level 3 Communications',
+);
+
+tstlocalrules ("
+  geodb_module GeoIP2
+  geoip_search_path data/geodb
+
+  uri_block_cc X_URIBL_USA us
+  describe X_URIBL_USA uri located in USA
+  
+  uri_block_cont X_URIBL_NA na
+  describe X_URIBL_NA uri located in north America
+
+  uri_block_isp X_URIBL_ISP \"Level 3 Communications\"
+  describe X_URIBL_ISP isp is Level 3 Communications
+");
+
+ok sarun ("-L -t < data/spam/relayUS.eml", \&patterns_run_cb);
+ok_all_patterns();

Propchange: spamassassin/trunk/t/urilocalbl_geoip2.t
------------------------------------------------------------------------------
    svn:executable = *