From ca710f1de336ea328f20683b6765c1ec4181763d Mon Sep 17 00:00:00 2001 From: Trevor Batley Date: Sat, 7 Sep 2024 20:26:07 +1000 Subject: [PATCH] initial commit of file from CVS for smeserver-geoip on Sat Sep 7 20:26:07 AEST 2024 --- .gitignore | 4 + Makefile | 21 + README.md | 19 +- contriborbase | 1 + createlinks | 70 +++ .../db/configuration/defaults/geoip/status | 1 + .../db/configuration/defaults/geoip/type | 1 + .../defaults/qpsmtpd/BadCountries | 1 + .../db/configuration/defaults/qpsmtpd/GeoIP | 1 + .../actions/smeserver-geopip-download-action | 2 + .../templates/etc/GeoIP.conf/10default | 12 + .../templates/etc/GeoIP.conf/20databasestore | 9 + .../e-smith/templates/etc/GeoIP.conf/25server | 7 + .../e-smith/templates/etc/GeoIP.conf/30proxy | 8 + .../etc/GeoIP.conf/35PreserveFileTimes | 8 + .../e-smith/templates/etc/GeoIP.conf/40lock | 6 + .../templates/etc/crontab/91_Update_Geoip_db | 3 + .../config/badcountries/10badcountries | 7 + .../config/peers/0/18check_badcountries | 1 + .../config/plugins/18check_badcountries | 6 + root/usr/bin/geocity.pl | 26 + root/usr/bin/geocountry.pl | 22 + root/usr/bin/geoiplook | 6 + .../share/qpsmtpd/plugins/check_badcountries | 595 ++++++++++++++++++ smeserver-geoip.spec | 198 ++++++ 25 files changed, 1033 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 contriborbase create mode 100644 createlinks create mode 100644 root/etc/e-smith/db/configuration/defaults/geoip/status create mode 100644 root/etc/e-smith/db/configuration/defaults/geoip/type create mode 100644 root/etc/e-smith/db/configuration/defaults/qpsmtpd/BadCountries create mode 100644 root/etc/e-smith/db/configuration/defaults/qpsmtpd/GeoIP create mode 100644 root/etc/e-smith/events/actions/smeserver-geopip-download-action create mode 100644 root/etc/e-smith/templates/etc/GeoIP.conf/10default create mode 100644 root/etc/e-smith/templates/etc/GeoIP.conf/20databasestore create mode 100644 root/etc/e-smith/templates/etc/GeoIP.conf/25server create mode 100644 root/etc/e-smith/templates/etc/GeoIP.conf/30proxy create mode 100644 root/etc/e-smith/templates/etc/GeoIP.conf/35PreserveFileTimes create mode 100644 root/etc/e-smith/templates/etc/GeoIP.conf/40lock create mode 100644 root/etc/e-smith/templates/etc/crontab/91_Update_Geoip_db create mode 100644 root/etc/e-smith/templates/var/service/qpsmtpd/config/badcountries/10badcountries create mode 120000 root/etc/e-smith/templates/var/service/qpsmtpd/config/peers/0/18check_badcountries create mode 100644 root/etc/e-smith/templates/var/service/qpsmtpd/config/plugins/18check_badcountries create mode 100755 root/usr/bin/geocity.pl create mode 100755 root/usr/bin/geocountry.pl create mode 100644 root/usr/bin/geoiplook create mode 100644 root/usr/share/qpsmtpd/plugins/check_badcountries create mode 100644 smeserver-geoip.spec diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbb3a13 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.rpm +*.log +*spec-20* +*.tar.gz diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..05283fa --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +# Makefile for source rpm: smeserver-geoip +# $Id: Makefile,v 1.1 2020/10/06 13:45:08 brianr Exp $ +NAME := smeserver-geoip +SPECFILE = $(firstword $(wildcard *.spec)) + +define find-makefile-common +for d in common ../common ../../common ; do if [ -f $$d/Makefile.common ] ; then if [ -f $$d/CVS/Root -a -w $$/Makefile.common ] ; then cd $$d ; cvs -Q update ; fi ; echo "$$d/Makefile.common" ; break ; fi ; done +endef + +MAKEFILE_COMMON := $(shell $(find-makefile-common)) + +ifeq ($(MAKEFILE_COMMON),) +# attept a checkout +define checkout-makefile-common +test -f CVS/Root && { cvs -Q -d $$(cat CVS/Root) checkout common && echo "common/Makefile.common" ; } || { echo "ERROR: I can't figure out how to checkout the 'common' module." ; exit -1 ; } >&2 +endef + +MAKEFILE_COMMON := $(shell $(checkout-makefile-common)) +endif + +include $(MAKEFILE_COMMON) diff --git a/README.md b/README.md index e05422e..ecf8ed1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,18 @@ -# smeserver-geoip +# smeserver-geoip -SMEServer Koozali developed git repo for smeserver-geoip smecontribs \ No newline at end of file +SMEServer Koozali developed git repo for smeserver-geoip smecontribs + +## Wiki +
https://wiki.koozali.org/GeoIP +
https://wiki.koozali.org/Xt_geoip +
https://wiki.koozali.org/Qpsmtpd:ident/geoip +
https://wiki.koozali.org/Xt_geoip/fr + +## Bugzilla +Show list of outstanding bugs: [here](https://bugs.koozali.org/buglist.cgi?component=smeserver-geoip&product=SME%20Contribs&query_format=advanced&limit=0&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=CONFIRMED) + +## Description + +
*This description has been generated by an LLM AI system and cannot be relied on to be fully correct.* +*Once it has been checked, then this comment will be deleted* +
diff --git a/contriborbase b/contriborbase new file mode 100644 index 0000000..9b7fd51 --- /dev/null +++ b/contriborbase @@ -0,0 +1 @@ +contribs10 diff --git a/createlinks b/createlinks new file mode 100644 index 0000000..2d04b2b --- /dev/null +++ b/createlinks @@ -0,0 +1,70 @@ +#! /usr/bin/perl -w + +use esmith::Build::CreateLinks qw(:all); + +#-------------------------------------------------- +# actions for geoip-update event +#-------------------------------------------------- + + +#for my $event (qw(geoip-update)) +#{ +#templates2events("/var/service/qpsmtpd/config/badcountries", $event); +#event_link("smeserver-geopip-download-action", $event, "10"); +#} + + + +my $event = "geoip-update"; + +foreach (qw( + /var/service/qpsmtpd/config/badcountries + /etc/GeoIP.conf + /etc/crontab + /var/service/qpsmtpd/config/peers/0 + )) +{ + templates2events("$_", qw( + geoip-update + smeserver-geoip-update + )); +} + +event_link("smeserver-geopip-download-action", $event, "10"); + +event_link("smeserver-geopip-download-action", 'smeserver-geoip-update', "10"); + +#-------------------------------------------------- +# actions for email-update event +#-------------------------------------------------- + +#for my $event (qw( +# bootstrap-console-save +# email-update)) + +#{ +#templates2events("/var/service/qpsmtpd/config/badcountries", $event); +#} + +#my $event = "email-update"; + +foreach (qw( + /var/service/qpsmtpd/config/badcountries + /etc/GeoIP.conf + /etc/crontab + /var/service/qpsmtpd/config/peers/0 + )) + +{ + templates2events("$_", qw( + post-upgrade + bootstrap-console-save + console-save + email-update + )); +} + +event_services('smeserver-geoip-update', 'qpsmtpd' => 'restart', 'sqpsmtpd' => 'restart') ; + +safe_symlink("../../plugins/18check_badcountries", "root//etc/e-smith/templates/var/service/qpsmtpd/config/peers/0/18check_badcountries"); + diff --git a/root/etc/e-smith/db/configuration/defaults/geoip/status b/root/etc/e-smith/db/configuration/defaults/geoip/status new file mode 100644 index 0000000..86981e6 --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/geoip/status @@ -0,0 +1 @@ +enabled diff --git a/root/etc/e-smith/db/configuration/defaults/geoip/type b/root/etc/e-smith/db/configuration/defaults/geoip/type new file mode 100644 index 0000000..24e1098 --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/geoip/type @@ -0,0 +1 @@ +service diff --git a/root/etc/e-smith/db/configuration/defaults/qpsmtpd/BadCountries b/root/etc/e-smith/db/configuration/defaults/qpsmtpd/BadCountries new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/qpsmtpd/BadCountries @@ -0,0 +1 @@ + diff --git a/root/etc/e-smith/db/configuration/defaults/qpsmtpd/GeoIP b/root/etc/e-smith/db/configuration/defaults/qpsmtpd/GeoIP new file mode 100644 index 0000000..86981e6 --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/qpsmtpd/GeoIP @@ -0,0 +1 @@ +enabled diff --git a/root/etc/e-smith/events/actions/smeserver-geopip-download-action b/root/etc/e-smith/events/actions/smeserver-geopip-download-action new file mode 100644 index 0000000..5dd2591 --- /dev/null +++ b/root/etc/e-smith/events/actions/smeserver-geopip-download-action @@ -0,0 +1,2 @@ +#!/bin/bash +/usr/bin/geoipupdate diff --git a/root/etc/e-smith/templates/etc/GeoIP.conf/10default b/root/etc/e-smith/templates/etc/GeoIP.conf/10default new file mode 100644 index 0000000..c7f981a --- /dev/null +++ b/root/etc/e-smith/templates/etc/GeoIP.conf/10default @@ -0,0 +1,12 @@ +# The following AccountID and LicenseKey are required placeholders. +# For geoipupdate versions earlier than 2.5.0, use UserId here instead of AccountID. +#AccountID 0 +AccountID { $geoip{AccountID} ||'0' } +LicenseKey { $geoip{LicenseKey} ||'000000000000'} + +# Include one or more of the following edition IDs: +# * GeoLite2-City - GeoLite 2 City +# * GeoLite2-Country - GeoLite2 Country +# For geoipupdate versions earlier than 2.5.0, use ProductIds here instead of EditionIDs. +EditionIDs GeoLite2-City GeoLite2-Country +#ProductIds GeoLite2-City GeoLite2-Country diff --git a/root/etc/e-smith/templates/etc/GeoIP.conf/20databasestore b/root/etc/e-smith/templates/etc/GeoIP.conf/20databasestore new file mode 100644 index 0000000..1b34a47 --- /dev/null +++ b/root/etc/e-smith/templates/etc/GeoIP.conf/20databasestore @@ -0,0 +1,9 @@ +# The remaining settings are OPTIONAL. + +# The directory to store the database files. Defaults to /usr/share/GeoIP +# DatabaseDirectory /usr/share/GeoIP +{ + my $DatabaseDirectory = $geoip{DatabaseDirectory} || '/usr/share/GeoIP'; + $OUT ="DatabaseDirectory $DatabaseDirectory"; + +} diff --git a/root/etc/e-smith/templates/etc/GeoIP.conf/25server b/root/etc/e-smith/templates/etc/GeoIP.conf/25server new file mode 100644 index 0000000..49d6e85 --- /dev/null +++ b/root/etc/e-smith/templates/etc/GeoIP.conf/25server @@ -0,0 +1,7 @@ +# The server to use. Defaults to "updates.maxmind.com". +# Host updates.maxmind.com +{ + my $GEOserver = $geoip{Host} || 'updates.maxmind.com'; + $OUT ="Host $GEOserver"; + +} diff --git a/root/etc/e-smith/templates/etc/GeoIP.conf/30proxy b/root/etc/e-smith/templates/etc/GeoIP.conf/30proxy new file mode 100644 index 0000000..03d79a0 --- /dev/null +++ b/root/etc/e-smith/templates/etc/GeoIP.conf/30proxy @@ -0,0 +1,8 @@ +# The proxy host name or IP address. You may optionally specify a +# port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080 +# will be used. +# Proxy 127.0.0.1:8888 + +# The user name and password to use with your proxy server. +# ProxyUserPassword username:password + diff --git a/root/etc/e-smith/templates/etc/GeoIP.conf/35PreserveFileTimes b/root/etc/e-smith/templates/etc/GeoIP.conf/35PreserveFileTimes new file mode 100644 index 0000000..64c5c5d --- /dev/null +++ b/root/etc/e-smith/templates/etc/GeoIP.conf/35PreserveFileTimes @@ -0,0 +1,8 @@ +# Whether to preserve modification times of files downloaded from the server. +# Defaults to "0". +# PreserveFileTimes 0 +{ + my $FT = $geoip{PreserveFileTimes} || '0'; + $OUT ="PreserveFileTimes $FT"; + +} diff --git a/root/etc/e-smith/templates/etc/GeoIP.conf/40lock b/root/etc/e-smith/templates/etc/GeoIP.conf/40lock new file mode 100644 index 0000000..975bfb4 --- /dev/null +++ b/root/etc/e-smith/templates/etc/GeoIP.conf/40lock @@ -0,0 +1,6 @@ +# The lock file to use. This ensures only one geoipupdate process can run at a +# time. +# Note: Once created, this lockfile is not removed from the filesystem. +# Defaults to ".geoipupdate.lock" under the DatabaseDirectory. +# LockFile /usr/share/GeoIP/.geoipupdate.lock + diff --git a/root/etc/e-smith/templates/etc/crontab/91_Update_Geoip_db b/root/etc/e-smith/templates/etc/crontab/91_Update_Geoip_db new file mode 100644 index 0000000..1561a6e --- /dev/null +++ b/root/etc/e-smith/templates/etc/crontab/91_Update_Geoip_db @@ -0,0 +1,3 @@ +# Updating the GeoIP database monthly on the 5th at 0:00h. +0 0 10 * * root /usr/bin/geoipupdate + diff --git a/root/etc/e-smith/templates/var/service/qpsmtpd/config/badcountries/10badcountries b/root/etc/e-smith/templates/var/service/qpsmtpd/config/badcountries/10badcountries new file mode 100644 index 0000000..4e47db1 --- /dev/null +++ b/root/etc/e-smith/templates/var/service/qpsmtpd/config/badcountries/10badcountries @@ -0,0 +1,7 @@ +{ + my @badcountries = split /[,:]/, ${qpsmtpd}{BadCountries} || ''; + + return "# No BadCountries are defined" unless (scalar @badcountries); + + return join "\n", @badcountries; +} diff --git a/root/etc/e-smith/templates/var/service/qpsmtpd/config/peers/0/18check_badcountries b/root/etc/e-smith/templates/var/service/qpsmtpd/config/peers/0/18check_badcountries new file mode 120000 index 0000000..740d1b9 --- /dev/null +++ b/root/etc/e-smith/templates/var/service/qpsmtpd/config/peers/0/18check_badcountries @@ -0,0 +1 @@ +../../plugins/18check_badcountries \ No newline at end of file diff --git a/root/etc/e-smith/templates/var/service/qpsmtpd/config/plugins/18check_badcountries b/root/etc/e-smith/templates/var/service/qpsmtpd/config/plugins/18check_badcountries new file mode 100644 index 0000000..cd91d08 --- /dev/null +++ b/root/etc/e-smith/templates/var/service/qpsmtpd/config/plugins/18check_badcountries @@ -0,0 +1,6 @@ +{ + return "# geoip disabled" unless (${qpsmtpd}{GeoIP} eq "enabled"); + + "check_badcountries"; +} + diff --git a/root/usr/bin/geocity.pl b/root/usr/bin/geocity.pl new file mode 100755 index 0000000..03bd18b --- /dev/null +++ b/root/usr/bin/geocity.pl @@ -0,0 +1,26 @@ +#!/usr/bin/perl + +use GeoIP2::Database::Reader; + +# GeoLite2-City.mmdb +if ( $ARGV[0] eq '-h' || $ARGV[0] eq '-help' || $ARGV[0] eq '' ) { + help(); + exit; +} + +my $reader = GeoIP2::Database::Reader->new( + file => '/usr/share/GeoIP/GeoLite2-City.mmdb', + locales => [ 'en', 'de', ] +); + +my $city = $reader->city( ip => $ARGV[0] ); + +my $city_rec = $city->city(); +print "City: ", $city_rec->name(), "\n"; + +my $country = $city->country(); +print "Country: ", $country->iso_code(), "\n"; + +sub help { + print "Usage: ./geoipcity.pl 1.2.3.4\n"; +} diff --git a/root/usr/bin/geocountry.pl b/root/usr/bin/geocountry.pl new file mode 100755 index 0000000..1206fc3 --- /dev/null +++ b/root/usr/bin/geocountry.pl @@ -0,0 +1,22 @@ +#!/usr/bin/perl + +use GeoIP2::Database::Reader; + +if ( $ARGV[0] eq '-h' || $ARGV[0] eq '-help' || $ARGV[0] eq '' ) { + help(); + exit; +} + +my $reader = GeoIP2::Database::Reader->new( + file => '/usr/share/GeoIP/GeoLite2-Country.mmdb', + locales => [ 'en', 'de', ] +); + +my $country = $reader->country( ip => $ARGV[0] ); +my $country_rec = $country->country(); + +print "Country ", $country_rec->iso_code(), "\n"; + +sub help { + print "Usage: ./geoipcountry.pl 1.2.3.4\n"; +} diff --git a/root/usr/bin/geoiplook b/root/usr/bin/geoiplook new file mode 100644 index 0000000..3a6a1c1 --- /dev/null +++ b/root/usr/bin/geoiplook @@ -0,0 +1,6 @@ +#!/bin/bash +for var in "$@" +do +/usr/bin/mmdblookup --file /usr/share/GeoIP/GeoLite2-Country.mmdb --ip $1 country iso_code |cut -d\" -f2| tr -d '\n' +echo "" +done diff --git a/root/usr/share/qpsmtpd/plugins/check_badcountries b/root/usr/share/qpsmtpd/plugins/check_badcountries new file mode 100644 index 0000000..2dc2685 --- /dev/null +++ b/root/usr/share/qpsmtpd/plugins/check_badcountries @@ -0,0 +1,595 @@ +#!perl -w + +=head1 NAME + +geoip - provide geographic information about mail senders. + +=head1 SYNOPSIS + +Use MaxMind's GeoIP databases and the GeoIP2 or Geo::IP perl modules to report +geographic information about incoming connections. + +=head1 DESCRIPTION + +Save geographic information about the sender in the following connection notes: + + geoip_country - 2 char country code + geoip_country_name - english name of country + geoip_continent - 2 char continent code + geoip_city - english name of city + geoip_distance - distance in kilometers + geoip_asn - network number + +And adds entries like this to your logs: + + (connect) ident::geoip: NA, US, United States, 1319 km + (connect) ident::geoip: AS, IN, India, 13862 km + (connect) ident::geoip: fail: no results + (connect) ident::geoip: NA, CA, Canada, 2464 km + (connect) ident::geoip: NA, US, United States, 2318 km + (connect) ident::geoip: AS, PK, Pakistan, 12578 km + (connect) ident::geoip: AS, TJ, Tajikistan, 11965 km + (connect) ident::geoip: EU, AT, Austria, 8745 km + (connect) ident::geoip: AS, IR, Iran, Islamic Republic of, 12180 km + (connect) ident::geoip: EU, BY, Belarus, 9030 km + (connect) ident::geoip: AS, CN, China, 11254 km + (connect) ident::geoip: NA, PA, Panama, 3163 km + +Calculating the distance has three prerequsites: + + 1. The MaxMind city database (free or subscription) + 2. The Math::Complex perl module + 3. The IP address of this mail server (see CONFIG) + +Other plugins can utilize the geographic notes to alter the +connection, reject, greylist, etc. + +=head1 CONFIG + +The following options can be appended in this plugins config/plugins entry. + +(With the free v2 'Lite' databases you can only access Countries and Cities) + +=head2 distance + +Enables geodesic distance calculation. Will calculate the distance "as the +crow flies" from the remote mail server. Accepts a single argument, the IP +address to calculate the distance from. This will typically be the public +IP of your mail server. + + ident/geoip [ distance 192.0.1.5 ] + +Default: none. (no distance calculations) + +=head2 too_far + +Assign negative karma to connections further than this many km. + +Default: none + +=head2 db_dir + +The path to the GeoIP database directory. + + ident/geoip [ db_dir /etc/GeoIP ] + +Default: /usr/local/share/GeoIP + +=head2 add_headers + +Add message headers with GeoIP data + + ident/geoip [ add_headers (true|false) ] + +Default: true + +=head1 LIMITATIONS + +The distance calculations are more concerned with being fast than accurate. +The MaxMind location data is collected from whois and is of limited accuracy. +MaxMind offers more accurate data for a fee. + +For distance calculations, the earth is considered a perfect sphere. In +reality, it is not. Accuracy should be within 1%. + +This plugin does not update the GeoIP databases. You may want to. + +=head1 CHANGES + +2019-10 - JP Pialasse - reintroduce block country functionality with geoip v1 + +2019-01 - John Crisp - modify to work correctly with mailstats + +2019-01 - JP Pialasse - make it compatible with old v1 + improve log level + +2018-06 - John Crisp - modify to work with SME server + +2014-06 - Matt Simerson - added GeoIP2 support + +2012-06 - Matt Simerson - added GeoIP City support, continent, distance + +2012-05 - Matt Simerson - added geoip_country_name note, added tests + +=head1 SEE ALSO + +MaxMind: http://www.maxmind.com/ + +Databases: http://geolite.maxmind.com/download/geoip/database + +It may become worth adding support for Geo::IPfree, which uses another +data source: http://software77.net/geo-ip/ + +=head1 ACKNOWLEDGEMENTS + +Based on qpsmtpd ident/geoip plugin +Originally by Doug Kruhm + +MaxMind - the packager and distributor of the free GeoIP data + +Stevan Bajic, the DSPAM author, who suggested SNARE, which describes using +geodesic distance to determine spam probability. The research paper on SNARE +can be found here: +http://smartech.gatech.edu/bitstream/handle/1853/25135/GT-CSE-08-02.pdf + +=cut + +use strict; +use warnings; + +use Qpsmtpd::Constants; + +#use GeoIP2; # eval'ed in register() +#use Geo::IP; # eval loaded if GeoIP2 doesn't +#use Math::Trig; # eval'ed in set_distance_gc + +sub register { + my ($self, $qp, @args) = @_; + + $self->log(LOGERROR, "Bad arguments") if @args % 2; + $self->{_args} = {@args}; + $self->{_args}{db_dir} ||= '/usr/share/GeoIP'; + + $self->load_geoip() or return; + my $enabled = $self->{_args}{add_headers}; + $enabled = 'true' if ! defined $enabled; + return if $enabled =~ /false/i; + $self->register_hook( data_post => 'add_headers' ); +} + +sub load_geoip { + my ( $self ) = @_; + $self->load_geoip2() and return 1; + $self->load_geoip1() and return 1; #only search v1 if v2 not available + return 0; +} + +sub load_geoip1 { + my $self = shift; + + 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: +# https://github.com/smtpd/qpsmtpd/commit/29ea9516806e9a8ca6519fcf987dbd684793ebdd#plugins/ident/geoip +# Opening the DB anew for every connection is horribly inefficient. +# Instead, attempt to reopen upon connect if the DB connection fails. + $self->init_my_country_code(); + + $self->register_hook('connect', 'geoip_lookup'); + return 1; +} + +sub load_geoip2 { + my $self = shift; + + eval 'use GeoIP2::Database::Reader'; + if ($@) { + $self->log(LOGERROR, "could not load GeoIP2"); + return; + } + +# warn "Using GeoIP2." +# . " ASN data is not currently available using the GeoIP2 module!\n"; + + eval { + $self->{_geoip2_city} = GeoIP2::Database::Reader->new( + file => $self->{_args}{db_dir} . '/GeoLite2-City.mmdb', + ); + }; + if ($@) { + $self->log(LOGERROR, "unable to load GeoLite2-City.mmdb"); + } + + eval { + $self->{_geoip2_country} = GeoIP2::Database::Reader->new( + file => $self->{_args}{db_dir} . '/GeoLite2-Country.mmdb', + ); + }; + if ($@) { + $self->log(LOGERROR, "unable to load GeoLite2-Country.mmdb"); + } + + if ($self->{_geoip2_city} || $self->{_geoip2_country}) { + $self->register_hook('connect', 'geoip2_lookup'); + return 1; + } + + return; +} + +sub add_headers { + my ( $self, $txn ) = @_; + for my $h (qw( Country Continent City ASN )) { + my $note = lc "geoip_$h"; + next if ! $self->connection->notes($note); + $txn->header->delete("X-GeoIP-$h"); + $txn->header->add( "X-GeoIP-$h", $self->connection->notes($note), 0 ); + } + return DECLINED; +} + +sub geoip2_lookup { + my $self = shift; + + my $ip = $self->qp->connection->remote_ip; + return DECLINED if $self->is_localhost($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()); + #my $city = $self->qp->connection->notes('geoip_city'); + #warn ("At City: $city"); + # 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()); + #my $country = $country_rec->country->iso_code(); + #warn ("At country: $country"); + }; + } + + # City Information in case we want to play later + my $city = $self->qp->connection->notes('geoip_city') // ''; + #warn ("At City (check): $city"); + $self->log(LOGNOTICE, "GeoIP RemoteIP: $ip"); + + if ($city eq '') { + $self->log(LOGNOTICE, "GeoIP City: NA"); + } + else { + $self->log(LOGNOTICE, "GeoIP City: $city"); + } + + my $country = $self->qp->connection->notes('geoip_country'); + # Returns DECLINED if there are no countries found above + return DECLINED unless $country; + $self->log(LOGNOTICE, "GeoIP Country: $country"); + + if ( $self->qp->config("badcountries") ) { + my @badcountries = $self->qp->config("badcountries"); + for (@badcountries) { + my ($pattern, $response) = split /\s+/, $_, 2; + #my $whitelisthost = $connection->notes('whitelisthost'); + my $whitelisthost = $self->qp->connection->notes('whitelisthost'); + if ($whitelisthost) { + $self->log(LOGNOTICE, "Country $country Pattern $pattern Whitehost $whitelisthost RemoteIP $ip"); + $self->log(LOGNOTICE, "Geoip whitelisthost found $whitelisthost"); + return OK; + } + else { + return (DENY, "Country is on Blocked List") if ($country eq $pattern); + } + } + } + return DECLINED; +} + + +sub geoip_lookup { + my $self = shift; + + my $ip = $self->qp->connection->remote_ip; + return DECLINED if $self->is_localhost($ip); + + # reopen the DB if Geo::IP failed due to DB update + $self->open_geoip_db(); + + my $c_code = $self->set_country_code() or do { + $self->log(LOGINFO, "skip, no results"); + return DECLINED; + }; + + $self->set_asn(); + + my $c_name = $self->set_country_name(); + my ($city, $continent_code, $distance) = ''; + + if ($self->{_my_country_code}) { + $continent_code = $self->set_continent($c_code); + $city = $self->set_city_gc(); + $distance = $self->set_distance_gc(); + } + + my @msg_parts; + if ($continent_code && $continent_code ne '--') { + push @msg_parts, $continent_code; + }; + push @msg_parts, $c_code if $c_code; + + #push @msg_parts, $c_name if $c_name; + push @msg_parts, $city if $city; + if ($distance) { + push @msg_parts, "\t$distance km"; + if ($self->{_args}{too_far} && $distance > $self->{_args}{too_far}) { + $self->adjust_karma(-1); + } + } + $self->log(LOGINFO, join(", ", @msg_parts)); + + my $country = $self->qp->connection->notes('geoip_country'); + # Returns DECLINED if there are no countries found above + return DECLINED unless $country; + $self->log(LOGNOTICE, "GeoIP Country: $country"); + + if ( $self->qp->config("badcountries") ) { + my @badcountries = $self->qp->config("badcountries"); + for (@badcountries) { + my ($pattern, $response) = split /\s+/, $_, 2; + #my $whitelisthost = $connection->notes('whitelisthost'); + my $whitelisthost = $self->qp->connection->notes('whitelisthost'); + if ($whitelisthost) { + $self->log(LOGNOTICE, "Country $country Pattern $pattern Whitehost $whitelisthost RemoteIP $ip"); + $self->log(LOGNOTICE, "Geoip whitelisthost found $whitelisthost"); + return OK; + } + else { + return (DENY, "Country is on Blocked List") if ($country eq $pattern); + } + } + } + + return DECLINED; +} + +sub open_geoip_db { + my $self = shift; + + # this might detect if the DB connection failed. If not, this is where + # to add more code to do it. + return if (defined $self->{_geoip_city} || defined $self->{_geoip}); + + # The methods for using GeoIP work differently for the City vs Country DB + # save the handles in different locations + my $db_dir = $self->{_args}{db_dir}; + foreach my $db (qw/ GeoIPCity GeoLiteCity /) { + next if !-f "$db_dir/$db.dat"; + $self->log(LOGDEBUG, "using db $db"); + $self->{_geoip_city} = Geo::IP->open("$db_dir/$db.dat"); + last if $self->{_geoip_city}; + } + warn "Missing GeoIP City data!\n" if ! $self->{_geoip_city}; + + if (-f "$db_dir/GeoIPASNum.dat") { + $self->log(LOGDEBUG, "using GeoIPASNum"); + $self->{GeoIPASNum} = Geo::IP->open("$db_dir/GeoIPASNum.dat"); + } + warn "Missing GeoIP ASN data!\n" if ! $self->{GeoIPASNum}; + + if (-f "$db_dir/GeoIPASNumv6.dat") { + $self->log(LOGDEBUG, "using GeoIPASNumv6"); + $self->{GeoIPASNumv6} = Geo::IP->open("$db_dir/GeoIPASNumv6.dat"); + warn "Missing GeoIP ASN IPV6 data!\n" if ! $self->{GeoIPASNum}; + } + + # can't think of a good reason to load country if city data is present + if (!$self->{_geoip_city}) { + $self->log(LOGDEBUG, "using default db"); + eval { $self->{_geoip} = Geo::IP->new(); }; # loads default Country DB + if (!$self->{_geoip}) { + my $err = $@ || 'Unknown error'; + warn "Missing GeoIP Country data:$err\n"; + } + } +} + +sub init_my_country_code { + my $self = shift; + my $ip = $self->{_args}{distance} or return; + $self->{_my_country_code} = $self->get_country_code($ip); +} + +sub set_country_code { + my $self = shift; + my $ip = $self->qp->connection->remote_ip; + my $code = $self->get_country_code($ip) or return; + $self->qp->connection->notes('geoip_country', $code); + return $code; +} + +sub get_country_code { + my $self = shift; + my $ip = shift || $self->qp->connection->remote_ip; + if ($self->{_geoip_city}) { + return $self->get_country_code_gc($ip); + } + if ($self->{_geoip}) { + return $self->{_geoip}->country_code_by_addr($ip); + } + return undef; +} + +sub get_country_code_gc { + my $self = shift; + my $ip = shift || $self->qp->connection->remote_ip; + $self->{_geoip_record} = $self->{_geoip_city}->record_by_addr($ip) + or return; + return $self->{_geoip_record}->country_code; +} + +sub set_country_name { + my $self = shift; + my $ip = $self->qp->connection->remote_ip; + my $name = $self->get_country_name($ip) or return; + $self->qp->connection->notes('geoip_country_name', $name); + return $name; +} + +sub get_country_name { + my $self = shift; + my $ip = shift || $self->qp->connection->remote_ip; + if ($self->{_geoip_city}) { + return $self->get_country_name_gc($ip); + } + if ($self->{_geoip}) { + return $self->{_geoip}->country_name_by_addr($ip); + } + return undef; +} + +sub get_country_name_gc { + my $self = shift; + return if !$self->{_geoip_record}; + return $self->{_geoip_record}->country_name(); +} + +sub set_continent { + my ($self, $country_code) = @_; + return if !$country_code; + my $continent = $self->get_continent($country_code) or return; + $self->qp->connection->notes('geoip_continent', $continent); + return $continent; +} + +sub get_continent { + my ($self, $country_code) = @_; + return if !$country_code; + if ($self->{_geoip_city}) { + return $self->get_continent_gc(); + } + if ($self->{_geoip}) { + return $self->{_geoip}->continent_code_by_country_code($country_code); + } + return undef; +} + +sub get_continent_gc { + my $self = shift; + return if !$self->{_geoip_record}; + return $self->{_geoip_record}->continent_code(); +} + +sub set_asn { + my ($self, $ip) = @_; + $ip ||= $self->qp->connection->remote_ip; + + if ($self->is_ipv6($ip)) { + return $self->set_asn_ipv6($ip); + } + return if ! $self->{GeoIPASNum}; + return if ! $self->{GeoIPASNum}->can('name_by_addr');# prior Geo-IP 1.39 should use org_by_addr + + my $asn = $self->{GeoIPASNum}->name_by_addr($ip) or return; + if ('AS' eq substr($asn, 0, 2)) { + $asn = substr($asn, 2); + } + $self->qp->connection->notes('geoip_asn', $asn); + return $asn; +} + +sub set_asn_ipv6 { + my ($self, $ip) = @_; + $ip ||= $self->qp->connection->remote_ip; + + return if ! $self->{GeoIPASNumv6}; + + my $asn = $self->{GeoIPASNumv6}->name_by_addr_v6($ip) or return; + $self->qp->connection->notes('geoip_asn', $asn); + return $asn; +} + +sub set_city_gc { + my $self = shift; + return if !$self->{_geoip_record}; + my $city = $self->{_geoip_record}->city() or return; + $self->qp->connection->notes('geoip_city', $city); + return $city; +} + +sub set_distance_gc { + my $self = shift; + return if !$self->{_geoip_record}; + + my ($self_lat, $self_lon) = $self->get_my_lat_lon() or return; + my ($sender_lat, $sender_lon) = $self->get_sender_lat_lon() or return; + + eval 'use Math::Trig qw(great_circle_distance deg2rad)'; + if ($@) { + $self->log(LOGERROR, + "can't calculate distance, Math::Trig not installed"); + return; + } + + # Notice the 90 - latitude: phi zero is at the North Pole. + sub NESW { deg2rad($_[0]), deg2rad(90 - $_[1]) } + my @me = NESW($self_lon, $self_lat); + my @sender = NESW($sender_lon, $sender_lat); + my $km = great_circle_distance(@me, @sender, 6378); + $km = sprintf("%.0f", $km); + + $self->qp->connection->notes('geoip_distance', $km); + + #$self->log( LOGINFO, "distance $km km"); + return $km; +} + +sub get_my_lat_lon { + my $self = shift; + return if !$self->{_geoip_city}; + + if ($self->{_latitude} && $self->{_longitude}) { + return $self->{_latitude}, $self->{_longitude}; # cached + } + + my $ip = $self->{_args}{distance} or return; + my $record = $self->{_geoip_city}->record_by_addr($ip) or do { + $self->log(LOGERROR, "no record for my Geo::IP location"); + return; + }; + + $self->{_latitude} = $record->latitude(); + $self->{_longitude} = $record->longitude(); + + if (!$self->{_latitude} || !$self->{_longitude}) { + $self->log(LOGNOTICE, "could not get my lat/lon"); + } + return $self->{_latitude}, $self->{_longitude}; +} + +sub get_sender_lat_lon { + my $self = shift; + + my $lat = $self->{_geoip_record}->latitude(); + my $lon = $self->{_geoip_record}->longitude(); + if (!$lat || !$lon) { + $self->log(LOGNOTICE, "could not get sender lat/lon"); + return; + } + return $lat, $lon; +} diff --git a/smeserver-geoip.spec b/smeserver-geoip.spec new file mode 100644 index 0000000..0e0d439 --- /dev/null +++ b/smeserver-geoip.spec @@ -0,0 +1,198 @@ +%define name smeserver-geoip +%define version 1.2 +%define release 19 + +Summary: SME Server geoip plugin +Name: %{name} +Version: %{version} +Release: %{release}%{?dist} +License: GPL +Group: Email +Source: %{name}-%{version}.tar.xz +Packager: Doug Kruhm +BuildRoot: /var/tmp/%{name}-%{version}-%{release}-buildroot +BuildArchitectures: noarch +Requires: perl-GeoIP2 +Requires: libmaxminddb >= 1.1.1 +Requires: libmaxminddb-devel >= 1.1.1 +Requires: geoipupdate +Requires: geolite2-country +Requires: geolite2-city +# for legacy +Requires: perl-Geo-IP +Requires: GeoIP >= 1.6.5 +# +Provides: smeserver-geoip2 = %{version}-%{release} +PRovides: smeserver-geoip-legacy = %{version}-%{release} +BuildRequires: e-smith-devtools >= 1.13.1-03 + +%description +The GEOIP plugin lets us know which country our mail server is receiving mail from. If we're receiving too much spam from a particular country, this will help track it down and then use that info to reject connections from that country. This ends up taking the load off our servers. + +CHANGE THE CONFIG DB: config setprop qpsmtpd BadCountries (i.e. config setprop qpsmtpd BadCountries BR) +SIGNAL EVENT: signal-event email-update + +#---------------------------------------------------- +#%package -n smeserver-geoip-legacy +#Summary: SME Server geoip plugin v1 +#Group: Email +#Requires: perl-Geo-IP +#Requires: GeoIP >= 1.6.5 +#Requires: smeserver-geoip = %{version}-%{release} +#BuildRequires: e-smith-devtools >= 1.13.1-03 +#%description -n smeserver-geoip-legacy +#Package to use the legacy plugin. DB are not updated since 2018 +#The GEOIP plugin lets us know which country our mail server is receiving mail from. If we're receiving too much spam from a particular country, this will help track it down and then use that info to reject connections from that country. This ends up taking the load off our servers. +#---------------------------------------------------- + +%changelog +* Sat Sep 07 2024 cvs2git.sh aka Brian Read 1.2-19.sme +- Roll up patches and move to git repo [SME: 12338] + +* Sat Sep 07 2024 BogusDateBot +- Eliminated rpmbuild "bogus date" warnings due to inconsistent weekday, + by assuming the date is correct and changing the weekday. + Thu Jun 16 2007 --> Thu Jun 14 2007 or Sat Jun 16 2007 or Thu Jun 21 2007 or .... + +* Thu Mar 18 2021 Brian Read 1.2-18.sme +- Add expand template for the qpsmtpd peers [SME: 11023] + +* Sun Mar 14 2021 Jean-Philippe Pialasse 1.2-17.sme +- merge legacy with main as we have few packages still using legacy [SME: 11023] + those are php*-pecl-geoip proftpd bind-libs* bind-utils. + +* Sat Mar 13 2021 Jean-Philipe Pialasse 1.2-16.sme +- rebuild for SME10 [SME: 11023] + make geoip2 default + create geoip-legacy package with old geoip1 stuffs + smeserver-geoip(-legacy)-update events + +* Tue Oct 06 2020 Brian Read 1.2-15.sme +- Import to SME10 tree [SME: 11023] + +* Wed Jan 22 2020 John Crisp 1.2-14.sme +- Change template from EditionID to ProductID +- fix 20databasestore has a trailing tilde + +* Tue Jan 21 2020 Jean-Philipe Pialasse 1.2-13.sme +- add support for mmdblookup [SME: 10740] +- add support for download with AccountID and LicenseKey [SME: 10859] + +* Sun Oct 20 2019 Jean-Philipe Pialasse 1.2-10.sme +- fix country not logged if no badcountries defined [SME: 10815] + +* Fri Oct 18 2019 Jean-Philipe Pialasse 1.2-9.sme +- revert blocking country for geoipv1 qpsmtpd plugin [SME: 10820] + +* Thu Jan 24 2019 Jean-Philipe Pialasse 1.2-8.sme +- make smeserver-geoip2 requires smeserver-geoip [SME: 10691] + this will avoid having both packages sharing few files. + +* Tue Jan 15 2019 John Crisp 1.2-7.sme +- Update check_badcountries to work with mailstats + +* Sat Jan 05 2019 Jean-Philipe Pialasse 1.2-6.sme +- remove crontab fragment for v1 [SME: 10691] + +* Sat Jan 05 2019 Jean-Philipe Pialasse 1.2-5.sme +- fix wrong loglevel LOGINFO instead of LOGNOTICE needed [SME: 10679] +- try first db v2 and back on v1 if not available [SME: 10691] +- workaround FATAL PLUGIN ERROR when method name_by_addr not existing priori geo-ip 1.39 [SME: 10691] + +* Fri Jan 04 2019 Jean-Philipe Pialasse 1.2-4.sme +- split smeserver-geoip into smeserver-geoip and smeserver-geoip2 for compatibility [SME: 10691] +- TODO: workaround to find for plugin compatibility + +* Mon Oct 29 2018 John Crisp 1.2-3.sme +- Fix Use of uninitialized value $city in string eq +- Fix createlinks geoip-update action + +* Sat Jun 23 2018 John Crisp 1.2-2.sme +- Fix typo in createlinks + +* Tue Jun 12 2018 John Crisp 1.2-1.sme +- Update to latest v2 DBs [SME: 9033] +- add geocity.pl and geocountry.pl test files + +* Wed Feb 15 2017 Jean-Philipe Pialasse 1.1.2-7.sme +- update requirement for GeoIP 1.6.5 [SME: 9714] + +* Thu Dec 22 2016 John Crisp 1.1.2-6 +- Use newer versions of GeoIP databases [SME: 9714] + +* Wed Sep 23 2015 John Crisp 1.1.2-5 +- Add ability to whitelist an IP [SME:8981] + +* Tue Sep 22 2015 stephane de Labrusse 1.1.2-4 +- the event geoip-update download the geoip database +- added smeserver-geoip-1.1.2.geoip-update-event.patch + +* Sun Jun 14 2015 stephane de Labrusse 1.1.2-2 +- added createlinks +- added actions and crontab to download geoip database + +* Mon Sep 03 2012 Doug Kruhm 1.0.5 +- defining $country to reduce noise if not found [SME: 5011] +- fixed misspelling in response if country is blocked [SME: 7058] +- cleaning up versioning to MAJOR-MINOR-PATCH + +* Fri May 01 2009 Doug Kruhm 1.0.0-04 +- added response to connecting server if blocked [SME: 5011] + +* Fri May 01 2009 Doug Kruhm 1.0.0-03 +- added line to reduce log noise [SME: 5011] + +* Fri May 01 2009 Doug Kruhm 1.0.0-02 +- db defaults migrated from SPEC file to rpm files [SME: 5193] +- removed %post and %postun macros [SME: 5194] +- added Requires perl-Geo-IP [SME: 1866] +- added e-smith-devtools as a requirement [SME: 1866] + +* Sat Jun 16 2007 Doug Kruhm + Thu Jun 16 2007 --> Thu Jun 14 2007 or Sat Jun 16 2007 or Thu Jun 21 2007 or .... +- 1.0.0-01 +- Original version + + +%prep +%setup +rm -rf root/var/ +# commented for legacy +#mkdir -p root/etc/e-smith/events/smeserver-geoip-legacy + +%build +perl createlinks + +%install +rm -rf $RPM_BUILD_ROOT +(cd root ; find . -depth -print | cpio -dump $RPM_BUILD_ROOT) +rm -f %{name}-%{version}-filelist +/sbin/e-smith/genfilelist $RPM_BUILD_ROOT \ + --file /usr/bin/geoiplook 'attr(0755,root,root)' \ +> %{name}-%{version}-filelist + +cat %{name}-%{version}-filelist \ +|grep -v 'etc/e-smith/events/smeserver-geoip-legacy' \ +> %{name}-%{version}-filelist-base +#cat %{name}-%{version}-filelist \ +#|grep 'etc/e-smith/events/smeserver-geoip-legacy' \ +#> %{name}-%{version}-filelist-legacy + + +%clean +rm -rf $RPM_BUILD_ROOT + +%post + +%postun + +%files -f %{name}-%{version}-filelist-base +%defattr(-,root,root) + + + +#%files -n smeserver-geoip-legacy -f %{name}-%{version}-filelist-legacy +#%defattr(-,root,root,-) + + +