qpsmtpd Wiki


You are here: start » plugins » spam » geo_blacklist_whitelist


You are currently not logged in! Enter your authentication credentials below to log in. You need to have cookies enabled to log in.


You don't have an account yet? Just get one: Register

Forgotten your password? Get a new one: Set new password

Plug-in Summary

Plug-in name: geo_blacklist_whitelist
Info: block connections based on geo-lookups
Author: Marc Sebastian Pelzer
Email: not shown
Compatibility: 3.x
Download: http://search.cpan.org/~mpelzer/


=head1 NAME
geo_blacklist_whitelist Version 1.2 by Marc Sebastian Pelzer http://search.cpan.org/~mpelzer/
This plugin allowes you to block incoming connections from specific countries
and/or cities. You can define a blacklist and a whitelist file in the config/
directory of your qpsmtpd installation. These file must be called 'geo_blacklist'
and 'geo_whitelist'. Entries in the blacklist will be overwritten by entries in
the whitelist. So, for example, to just receive emails from senders within your
country, put a *.* entry into the blacklist file and a *.IT (for Italy) or *.US
(for the USA) into the whitelist file.
City names must be a wildcard (*) if you mean 'every city' or the concrete city
name, like 'Seattle' or 'seattle' or 'SEATTLE' (city names are case insensitive).
The country must be specified as a 2 letter ISO 3166 code or as wildcard (*) if you
mean 'every country'. See 
(or google for 'ISO 3166') for a list of all ISO 3166 codes (the column 'A 2' is what we want).
This plugin is using the Geo-IP database from mindmax.com
(http://www.maxmind.com/app/ip-location). They offer a licensed (more accurate) version
and also a free (lite) version of their GEO country & city database. You can get
the free version from here (download the binary database and put it
somewhere where your qpsmtpd user can access & read it):
The database is updated every month; so you maybe want to set-up a cronjob
which updates the database automatically every month, like:
0 4 3 * * 	smtp	wget -T 60 -t 3 -q -O - http://www.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz | gunzip -c > /home/smtpd/GeoIPCity.dat
You need the Geo-IP C libraries installed. Get them here:
Or, if you have a Debian Linux running, by simpliy typing:
apt-get install libgeoip-dev libgeoip1
You also need the Perl Module Geo::IP installed. Get it via:
perl -MCPAN -e shell
install Geo::IP
or as HTTP download from CPAN:
Put a geo_blacklist_whitelist entry into your config/plugins file - somewhere in the upper section.
If you're using the "nodialup" plug-in (which I highly recommend), then you should load this plug-in
after the nodialup because it's less "expensive" (just using RegExp instead of doing IO and database look-ups).
Example "config/plugins" entry:
geo_blacklist_whitelist	/home/smtpd/GeoCity.dat active
This plugin should be called with two parameters. The first parameter has to be the full path + filename
of your GeoCity.dat database file (downloaded from mindmax.com). Make sure that your qpsmtpd user can
access and read the file!
As second parameter you can add a "active", which means that this plugin should become into production
and really deny connection bases on your black/whitelist defitions. I recommend that you run this plugin like this
for the start to see what happens and to fine-tune your black/whitelists files:
geo_blacklist_whitelist	/home/smtpd/GeoCity.dat
and then do a grep 'geo_blacklist_whitelist' on your qpsmtpd debug logfile in order to see it working. If you dont
specify the "active" parameter, the plugin will run in a "dry-mode" - which means that is does not DENY any connection
but just send some debug output instead.
If you want to reject connections from un-locatable peers, you could add an entry
into the geo_blacklist file. By default, we let this connections pass through.
Example config/geo_blacklist config file:
*.*			# no mails from anyone - will be overwritten by entries in geo_whitelist
*.CN			# no mails from China
*.KR			# no mails from Korea
*.TW			# no mails from Taiwan
Texas.US		# no mails from Texas
Berlin.*		# no mails from any city in the world called Berlin
*.UNKNOWN		# no mails from un-locatable connections; by default we let this connections pass through
Example config/geo_whitelist config file:
*.IT			# anyone from Italy
New York.US		# anyone from New York
Moscow.RU		# people from Moscow can mail me
Beijing.CN		# i want mails from Beijing in China
Take a look at the top 10 most spamming countries; maybe it helps you to define your blacklisting :)
use Geo::IP;
sub register {
	my ($self, $qp, @args) = @_;
	# check if we've been called with a path to the database file
	if (-r $args[0] && $args[0] =~ m!\.dat$!i) {
      $self->{_GEOCITY_DATABASE} = $args[0];
	  $self->log(LOGINFO, "OK. Using Geo-City-Database '". $args[0] . "'");
	} else {
		die "Bad argument - need a full path and filename to your Geo-City-Database .dat file!";
	# check if we should run in production mode or not
	if (grep (/active/, @args)) {
		$self->log(LOGINFO, "Running in ACTIVE mode. This means that we reject connections based on the defined rules!");
		$self->{_MODE} = 'active';
	} else {
		$self->log(LOGINFO, "Running in DRY-RUN mode. This means that we do not reject any connections but instead print some debug messages what would happen.");
		$self->{_MODE} = 'dry-run';
sub hook_connect {
    my ($self, $transaction) = @_;
    my ($g, $r, $element, $status, $match, $rule, $ci, $co);
	$status = '';
    my $remote_ip = $self->qp->connection->remote_ip;
    my $remote_host = $self->qp->connection->remote_host;
    my @geo_whitelist = $self->qp->config("geo_whitelist");
    my @geo_blacklist = $self->qp->config("geo_blacklist");
	$g = Geo::IP->open($self->{_GEOCITY_DATABASE}, GEOIP_STANDARD);
	$r = $g->record_by_addr($remote_ip);
	# check wether we can resolve the city name and country code or set some default values
        my $city = '*';
        my $country_code = 'UNKNOWN';
        my $country_name = 'Unknown';
        if ($remote_ip eq '') {
                 $city = '*';
                 $country_code = 'LOCAL';
                 $country_name = 'Local';
        } else {
	        eval { $city = $r->city || '*' };
	        eval { $country_code = $r->country_code || 'UNKNOWN' };
	        eval { $country_name = $r->country_name || 'Unknown' };
	# first, check if the current city + country is blacklisted
	my $counter = 0;
	foreach $element (@geo_blacklist) {
		($ci, $co) = ($element =~ m!^\s*([^\.]+)\.([^\s\#]+)!);
		if ((lc($ci) eq lc($city) || $ci eq '*') && (lc($co) eq lc($country_code) || $co eq '*')) {
			$self->log(LOGDEBUG, "geo_blacklist: given city '$city' and country_code '$country_code' ($country_name) matching rule in line $counter '$element'");
			$match = $counter;
			$status = 'forbidden';
	# then check if it's whitelisted somehow - this overwites a former
	# blacklist entry
	$counter = 0;
	foreach $element (@geo_whitelist) {
		($ci, $co) = ($element =~ m!^\s*([^\.]+)\.([^\s\#]+)!);
		if ((lc($ci) eq lc($city) || $ci eq '*') && (lc($co) eq lc($country_code) || $co eq '*')) {
			$self->log(LOGDEBUG, "geo_whitelist: given city '$city' and country_code '$country_code' ($country_name) matching rule in line $counter '$element'");
			$status = '';
	if ($status eq 'forbidden') {
		$self->log(LOGINFO, "Remote peers ($remote_ip / $remote_host) city '$city' and country_code '$country_code' ($country_name) is blacklisted (matched line $match). We DENY this connection.");
		if ($self->{_MODE} eq "active") {
			return (DENY, "Sorry, not allowed.");
	} else {
		$self->log(LOGINFO, "Remote peers ($remote_ip / $remote_host) city '$city' and country_code '$country_code' ($country_name) is allowed to pass through.");
	return DECLINED;