added GeoIP2 support, partial ASN support

This commit is contained in:
Matt Simerson 2014-11-05 00:23:31 -08:00
parent 9862cdc042
commit c61dbb5734
3 changed files with 114 additions and 22 deletions

View File

@ -30,8 +30,9 @@ WriteMakefile(
# 'DBIx::Simple' => 0, # log2sql # 'DBIx::Simple' => 0, # log2sql
# modules that cause Travis build tests to fail # modules that cause Travis build tests to fail
# 'Mail::SpamAssassin' => 0, # 'Mail::SpamAssassin' => 0,
# 'Geo::IP' => 0, # 'GeoIP2' => 2,
# 'Math::Complex' => 0, # geodesic distance in Geo::IP # 'Geo::IP' => 1,
# 'Math::Complex' => 0, # geodesic distance in geoip
# 'Mail::SPF' => 0, # 'Mail::SPF' => 0,
}, },
ABSTRACT => 'Flexible smtpd daemon written in Perl', ABSTRACT => 'Flexible smtpd daemon written in Perl',

View File

@ -85,6 +85,8 @@ This plugin does not update the GeoIP databases. You may want to.
=head1 CHANGES =head1 CHANGES
2014-06 - Matt Simerson - added GeoIP2 support
2012-06 - Matt Simerson - added GeoIP City support, continent, distance 2012-06 - Matt Simerson - added GeoIP City support, continent, distance
2012-05 - Matt Simerson - added geoip_country_name note, added tests 2012-05 - Matt Simerson - added geoip_country_name note, added tests
@ -126,30 +128,75 @@ sub register {
my $loaded = 0; my $loaded = 0;
eval 'use GeoIP2'; eval 'use GeoIP2::Database::Reader';
if ($@) { if (!$@) {
warn "could not load GeoIP2"; warn "using GeoIP2";
$self->log(LOGERROR, "could not load GeoIP2"); $self->log(LOGINFO, "using GeoIP2");
eval 'use Geo::IP'; eval {
if ($@) { $self->{_geoip2_city} = GeoIP2::Database::Reader->new(
warn "could not load Geo::IP"; file => $self->{_args}{db_dir} . '/GeoLite2-City.mmdb',
$self->log(LOGERROR, "could not load Geo::IP"); );
return; };
} eval {
$self->{_geoip2_country} = GeoIP2::Database::Reader->new(
file => $self->{_args}{db_dir} . '/GeoLite2-Country.mmdb',
);
};
$self->register_hook('connect', 'geoip2_lookup');
return;
} }
warn "could not load GeoIP2";
$self->log(LOGERROR, "could not load GeoIP2");
eval 'use Geo::IP';
if ($@) {
warn "could not load Geo::IP";
$self->log(LOGERROR, "could not load Geo::IP");
return;
}
$self->open_geoip_db();
# Note that opening the GeoIP DB only in register has caused problems before: # Note that opening the GeoIP DB only in register has caused problems before:
# https://github.com/smtpd/qpsmtpd/commit/29ea9516806e9a8ca6519fcf987dbd684793ebdd#plugins/ident/geoip # https://github.com/smtpd/qpsmtpd/commit/29ea9516806e9a8ca6519fcf987dbd684793ebdd#plugins/ident/geoip
# Opening the DB anew for every connection is horribly inefficient. # Opening the DB anew for every connection is horribly inefficient.
# Instead, attempt to reopen upon connect if the DB connection fails. # Instead, attempt to reopen upon connect if the DB connection fails.
$self->open_geoip_db();
$self->init_my_country_code(); $self->init_my_country_code();
$self->register_hook('connect', 'geoip_lookup'); $self->register_hook('connect', 'geoip_lookup');
} }
sub geoip2_lookup {
my $self = shift;
my $ip = $self->qp->connection->remote_ip;
if ($self->{_geoip2_city}) {
my $city_rec = $self->{_geoip2_city}->city(ip => $ip);
if ($city_rec) {
$self->qp->connection->notes('geoip_country', $city_rec->country->iso_code());
$self->qp->connection->notes('geoip_country_name', $city_rec->country->name());
$self->qp->connection->notes('geoip_continent', $city_rec->continent->code());
$self->qp->connection->notes('geoip_city', $city_rec->city->name());
$self->qp->connection->notes('geoip_asn', $city_rec->traits->autonomous_system_number());
return DECLINED;
}
}
if ($self->{_geoip2_country}) {
my $country_rec = $self->{_geoip2_country}->country(ip => $ip);
if ($country_rec) {
$self->qp->connection->notes('geoip_country', $country_rec->country->iso_code());
$self->qp->connection->notes('geoip_country_name', $country_rec->country->name());
$self->qp->connection->notes('geoip_continent', $country_rec->continent->code());
};
}
return DECLINED;
}
sub geoip_lookup { sub geoip_lookup {
my $self = shift; my $self = shift;
@ -200,10 +247,15 @@ sub open_geoip_db {
# save the handles in different locations # save the handles in different locations
my $db_dir = $self->{_args}{db_dir}; my $db_dir = $self->{_args}{db_dir};
foreach my $db (qw/ GeoIPCity GeoLiteCity /) { foreach my $db (qw/ GeoIPCity GeoLiteCity /) {
if (-f "$db_dir/$db.dat") { next if !-f "$db_dir/$db.dat";
$self->log(LOGDEBUG, "using db $db"); $self->log(LOGDEBUG, "using db $db");
$self->{_geoip_city} = Geo::IP->open("$db_dir/$db.dat"); $self->{_geoip_city} = Geo::IP->open("$db_dir/$db.dat");
} }
foreach my $db (qw/ GeoIPASNum GeoIPASNumv6 /) {
next if !-f "$db_dir/$db.dat";
$self->log(LOGDEBUG, "using db $db");
$self->{$db} = Geo::IP->open("$db_dir/$db.dat");
} }
# can't think of a good reason to load country if city data is present # can't think of a good reason to load country if city data is present
@ -287,6 +339,26 @@ sub get_continent_gc {
return $self->{_geoip_record}->continent_code(); return $self->{_geoip_record}->continent_code();
} }
sub set_asn {
my ($self) = @_;
my $remote_ip = $self->qp->connection->remote_ip;
return if ! $self->{GeoIPASNum};
my $asn = $self->{GeoIPASNum}
? $self->get_asn_gc($remote_ip)
: $self->{_geoip}->asn_by_addr($remote_ip);
$asn or return;
$self->qp->connection->notes('geoip_asn', $asn);
return $asn;
}
sub get_asn_gc {
my $self = shift;
return if !$self->{GeoIPASNum};
return $self->{GeoIPASNum}->asn_by_addr();
}
sub set_city_gc { sub set_city_gc {
my $self = shift; my $self = shift;
return if !$self->{_geoip_record}; return if !$self->{_geoip_record};

View File

@ -9,6 +9,13 @@ use Qpsmtpd::Constants;
sub register_tests { sub register_tests {
my $self = shift; my $self = shift;
eval 'use GeoIP2::Database::Reader';
if ( !$@ ) {
warn "using GeoIP2\n";
$self->register_test('test_geoip2_lookup');
return;
}
eval 'use Geo::IP'; eval 'use Geo::IP';
if ( $@ ) { if ( $@ ) {
warn "could not load Geo::IP\n"; warn "could not load Geo::IP\n";
@ -22,6 +29,19 @@ sub register_tests {
$self->register_test('test_set_country_name'); $self->register_test('test_set_country_name');
$self->register_test('test_set_continent'); $self->register_test('test_set_continent');
$self->register_test('test_set_distance'); $self->register_test('test_set_distance');
$self->register_test('test_set_isp');
};
sub test_geoip2_lookup {
my $self = shift;
$self->qp->connection->remote_ip('24.24.24.24');
cmp_ok( $self->geoip2_lookup(), '==', DECLINED, "exit code");
cmp_ok( $self->connection->notes('geoip_country'), 'eq', 'US', "24.24.24.24 is in the US");
cmp_ok( $self->connection->notes('geoip_country_name'), 'eq', 'United States', "24.24.24.24 is in the United States");
cmp_ok( $self->connection->notes('geoip_continent'), 'eq', 'NA', "24.24.24.24 is in NA");
cmp_ok( $self->connection->notes('geoip_city'), 'eq', 'Deer Park', "24.24.24.24 is in Deer Park");
}; };
sub test_geoip_lookup { sub test_geoip_lookup {
@ -30,7 +50,7 @@ sub test_geoip_lookup {
$self->qp->connection->remote_ip('24.24.24.24'); $self->qp->connection->remote_ip('24.24.24.24');
cmp_ok( $self->geoip_lookup(), '==', DECLINED, "exit code"); cmp_ok( $self->geoip_lookup(), '==', DECLINED, "exit code");
cmp_ok( $self->connection->notes('geoip_country'), 'eq', 'US', "note"); cmp_ok( $self->connection->notes('geoip_country'), 'eq', 'US', "24.24.24.24 is in the US");
}; };
sub test_geoip_load_db { sub test_geoip_load_db {
@ -74,10 +94,10 @@ sub test_set_country_code {
$self->qp->connection->remote_ip('24.24.24.24'); $self->qp->connection->remote_ip('24.24.24.24');
$cc = $self->set_country_code(); $cc = $self->set_country_code();
cmp_ok( $cc, 'eq', 'US', "$cc"); cmp_ok( $cc, 'eq', 'US', "set_country_code result is $cc");
my $note = $self->connection->notes('geoip_country'); my $note = $self->connection->notes('geoip_country');
cmp_ok( $note, 'eq', 'US', "note has: $cc"); cmp_ok( $note, 'eq', 'US', "set_country_code set note to $cc");
}; };
sub test_set_country_name { sub test_set_country_name {
@ -144,4 +164,3 @@ sub test_set_distance {
ok( 1, "no distance data"); ok( 1, "no distance data");
} }
}; };