sub load_geoip()

in lib/Mail/SpamAssassin/GeoDB.pm [416:627]


sub load_geoip {
  my ($self, $geodb_opts) = @_;
  my ($db, $dbapi, $ok);
  my ($gic_wanted, $gic_have, $gip_wanted, $gip_have);
  my ($flags, $fix_stderr, $can_ipv6);

  # Warn about fatal errors if this module was specifically requested
  my $errwarn = ($geodb_opts->{module}||'') eq 'geoip';

  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';
    1;
  } or do {
    my $err = $@;
    $err =~ s/ at .*//s;
    $err = "geodb: Geo::IP module load failed: $err";
    $errwarn ? warn("$err\n") : dbg($err);
    return (undef, undef);
  };

  my %path;
  foreach my $dbtype (@geoip_types) {
    # 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};
    # skip if not needed
    next if $geodb_opts->{wanted} && !$geodb_opts->{wanted}->{$dbtype};
    # only autosearch if no absolute path given
    if (!defined $geodb_opts->{dbs}->{$dbtype}) {
      # Try some default locations
      PATHS_GEOIP: foreach my $p (@{$geodb_opts->{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_opts->{dbs}->{$dbtype}) {
        dbg("geodb: GeoIP: $dbtype database requested, but not found: ".
          $geodb_opts->{dbs}->{$dbtype});
        next;
      }
      $path{$dbtype} = $geodb_opts->{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}) {
      my $err = $@;
      $err =~ s/ at .*//s;
      dbg("geodb: GeoIP: database $path{$dbtype} load failed: $err");
    } else {
      dbg("geodb: GeoIP: loaded $dbtype from $path{$dbtype}");
      $ok = 1;
    }
  }
  if ($fix_stderr) {
    open(STDERR, ">&OLDERR");
    close(OLDERR);
  }

  if (!$ok) {
    warn("geodb: GeoIP requested, but no databases could be loaded\n") if $errwarn;
    return (undef, undef)
  }

  # 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] =~ IS_IPV4_ADDRESS) {
      $city = $_[0]->{db}->{city}->record_by_addr($_[1]);
    } elsif ($_[0]->{db}->{city_v6}) {
      $city = $_[0]->{db}->{city_v6}->record_by_addr_v6($_[1]);
    }
    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;
  };
  $dbapi->{city_v6} = $dbapi->{city} if $db->{city_v6};

  # country()
  $db->{country} and $dbapi->{country} = sub {
    my $res = {};
    my $country;
    eval {
      if ($_[1] =~ IS_IPV4_ADDRESS) {
        $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 || 'XX';
    $res->{continent} = $country_to_continent{$country} || 'XX';
    return $res;
  };
  $dbapi->{country_v6} = $dbapi->{country} if $db->{country_v6};

  # isp()
  $db->{isp} and $dbapi->{isp} = sub {
    my $res = {};
    my $isp;
    eval {
      if ($_[1] =~ IS_IPV4_ADDRESS) {
        $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;
  };

  # asn()
  $db->{asn} and $dbapi->{asn} = sub {
    my $res = {};
    my $asn;
    eval {
      if ($_[1] =~ IS_IPV4_ADDRESS) {
        $asn = $_[0]->{db}->{asn}->isp_by_addr($_[1]);
      } else {
        # TODO?
        return $res;
      }
      1;
    };
    if (!defined $asn || $asn !~ /^((?:AS)?\d+)(?:\s+(.+))?/) {
      dbg("geodb: GeoIP asn query failed for $_[1]");
      return $res;
    };
    $res->{asn} = $1;
    $res->{asn_organization} = $2 if defined $2;
    return $res;
  };

  return ($db, $dbapi);
}