Postfix knows a very nice feature called cidr-maps. This helps to block larger ip-ranges in a very comfortable way. You can read more about it in the postfix manual
You might want to read this as well. I found a neat way to add a timestamp which updates on every sucessful SELECT
For example:
192.168.205.0/27 reject
which then could be integrated into postfix config like the following
smtpd_client_restrictions = ... cidr:/etc/postfix/blacklist ...
note: cidr-maps are no hash maps, therefore postmap
command is NOT needed
Now it's not so an easy job to „bring“ cidr-maps into a MySql database. That's because IP/networks in the form of 192.168.205.0/27 would be saved as strings in the tables. And that would be quite a bad idea in the view of performance. Database operations (e.g. search) are not really fast on TEXT
or VARCHAR
datatypes. Especially if used on a higher frequented mailserver, string searches do everything but scale well
Luckily every ip address can also be converted into a INT
datatype. Databases work very fast on non-string (e.g. numerical) values. Furthermore it's possible to add primary keys and/or unique keys on numeric columns. UNIQUE
helps avoiding double entries in the table
First of all a suitable MySql table has to be created
CREATE TABLE `blacklist` ( `network` int(10) unsigned NOT NULL, `broadcast` int(10) unsigned NOT NULL, UNIQUE KEY `network` (`network`), UNIQUE KEY `broadcast` (`broadcast`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
very important: it is a must to set the columns to unsigned
integer values. The default value of MySql is signed
. If you use a signed
integer the ip addresses where the first octet is higher than 127 could NOT be saved
To fill this table with the content of an existing cidr-map one need a tool which can determine the first (network
) and the last ip (broadcast
) of a cidr-notation. For my little script I used ipcalc utility and some lines of bash.
The following script parses a cidr-map file and get's the network
and broadcast
portion from which the MySQL INSERT
statements are generated.
#!/bin/bash for i in $(cat /etc/postfix/block.cidr | awk '{print $1}') ; do IP=$(ipcalc $i | grep 'Address' | awk '{print $2}') SUB=$(ipcalc $i | grep 'Broadcast' | awk '{print $2}') # this check is necessary because a host in notation # 192.168.205.16/32 does NOT have a # in this case network and broadcast (mysql rows) will be set to identical values [ "x$SUB" = 'x' ] && SUB=$IP echo "INSERT INTO \`blacklist\` (\`network\`,\`broadcast\` ) VALUES (INET_ATON('$IP'),INET_ATON('$SUB')) ON DUPLICATE KEY UPDATE \`active\`=1;" >> /root/cidr2mysql.sql done
in /root/cidr2mysql
there will be the MySql INSERT
statements (in the file each statement is on on line)
INSERT INTO `blacklist` (`network`,`broadcast` ) VALUES ( INET_ATON('192.168.205.0'), INET_ATON('192.168.205.31') ) ON DUPLICATE KEY UPDATE active`=1;
INET_ATON
function from MySql is used to convert network
and broadcast
into integers.
You can check this directly on the MySql console
mysql> select inet_aton('192.168.205.0'),inet_aton('192.168.205.31'); +----------------------------+-----------------------------+ | inet_aton('192.168.205.0') | inet_aton('192.168.205.31') | +----------------------------+-----------------------------+ | 3232288000 | 3232288031 | +----------------------------+-----------------------------+ 1 row in set (0.01 sec)
INET_ATON
computes the values with the following function:
192×256^3 + 168×256^2 + 205×256 + 0
192×256^3 + 168×256^2 + 205×256 + 31
Next a small postfix configfile is needed to tell postfix about the database details (e.g. host, user, password, table …). The name of the file is not important but it should be placed in the default postfix config-directory /etc/postfix
on most Linux. In my case /etc/postfix/mysql-blacklist.cf
hosts = MYSQL_HOST user = MYSQL_USER password = MYSQL_PASSWORT dbname = MYSQL_DATENBANK query = SELECT 'reject' FROM `blacklist` WHERE inet_aton('%s') BETWEEN `network` AND `broadcast`
Change the values to fit your enviroment
Before you insert the new restriction it's advisable to check that postfix can connect to the database and gets the expected data. To do this postmap
command can be helpful
postmap -q 'IP.ADDR.IN.BLOCK' mysql:/etc/postfix/mysql.blacklist.cf
change the IP to one that is for sure in your table.
Then add the „map“ to the postfix config in /etc/postfix/main.cf
smtpd_client_restrictions = ... mysql:/etc/postfix/mysql-blacklist.cf ...
and service postfix restart
Last word: Postfix config/maps in MySql is nice but also has a huge disadvantage. If the database goes south (for whatever reason ever) then postfix would reject EVERY mail with a temporary (450) error. For a testing enviroment this might be acceptable but for production that is a no-go Single Point of Failure.
So I suggest eighter to use as MySql Master/Slave replication or even better a MySql-cluster (ndb cluster enviroment
The main difference between replication and the cluster is that a replication is ALWAYS asynchronus whereas a cluster is truly synchron. With replication there is a small (depending on the enviroment also bigger) timewindow where the Master already commited the change but the Slave has not comitted yet. For example due to high loads on the Slave which delay the local commit. So in that timeframe the data ARE NOT constistent between Master and Slave, which can be potentialy bad.
To be honest in most environments where Master and Slave are in the same subnet the timeframe is very very very small. So with local master/slave a replication is surely a good thing.
There is one disadvantage more, which imho is „worse“ than the tiny little timeframe:
A slave can never write to the master. That means all write operations depend on the availability of the master. If the master goes south nothing can be written to the database anymore until the master is up again or an administrator manually intervened.
Here comes the beauty of a ndb-cluster. It's like a RAID1 for MySql via TCP. NDB consists of several nodes. We have management-nodes which do what the name say, they manage the cluster. Then there are the storage-nodes which hold the database data. And finally we have the api-nodes which interact with user applications (e.g. a webpage or a postfix). Ideally a setup consists of at least two managment-nodes and two storage nodes. So even if one management-node and one storage-node goes down the cluster is still fully operational.
api-nodes can be connected as many as you want and in contrast to replication api-nodes (slave in replication) can write to the storage-nodes. So a cluster is truly bi-directional. In the moment a client (e.g. api) commits to the database, the commit is instantly visable for all other members of the cluster. There is not the slightest timeframe for inconsistent data