PostFWD

How-To: Rate Limit Outbound Mail In Postfix Using PostFWD On CentOS 6.X

PostFWD v1.0+ (we will install v1.3.5)

Postfix v2.5+ (we will install v2.6.6)

CentOS 6.x (we are working in 6.8 x64)

You may also need things such as nc (netcat), telnet, and various Perl modules (detailed later) 

Install Postfix

Postfix is a strong, reliable and extremely common SMTP server. CentOS 6 comes preinstalled with Postfix, but to use PostFWD you need to ensure you are running a version higher than 2.5.

Find out using ‘rpm’:

[root@server]# rpm -qa | grep postfix postfix-2.6.6-6.el6_7.1.x86_64

Or use ‘yum’:

[root@server]# yum info postfix

Once installed, if for some reason you were using sendmail as your default MTA (Mail Transfer Agent), you’ll need to change this to postfix using ‘alternatives’:

[root@server]# alternatives --set mta /usr/sbin/postfix

Check you are running a valid version of Postfix:

[root@server]# postconf mail_version mail_version = 2.6.6

Ensure Postfix starts on a system reboot:

[root@server]# chkconfig postfix on

Configure Postfix

Configuring Postfix is a rather open ended task, and will depend on what you are using the SMTP server for. If you have come this far, you likely already have a Postfix configuration, or you are simply using it to relay mails for a specific application. Either way, you should look to set some of the most basic Postfix configuration options in ‘/etc/postfix/main.cf’:

myhostname = Set as the mail servers FQDN/hostname mydomain = The domain name of the mail server myorigin = Usually the same as $mydomain inet_interfaces = Set to all to listen on all network interfaces mydestination = $myhostname, localhost, $mydomain mynetworks = 127.0.0.0/8, /32 relay_domains = $mydestination home_mailbox = Maildir/

If you are relaying from a specific location/server, you will of course need to adjust how you do this. This How-To is not a Postfix/SMTP Server configuration guide. It is a PostFWD integration guide to Postfix.

Install PostFWD

PostFWD, or Postfix Firewall Daemon, is a daemonized process that acts as a check policy service for postfix. It has a customisable rule-set that it applies dynamically to any and all mail that Postfix sees, we’ll touch more on that later. It’s very powerful, and offers several mail handling features that would otherwise not be possible in Postfix alone (or any other MTA for that matter).

We need version 1.0 or higher, so grab the tarball from postfwd.org, and run through some initial setup steps:

[root@server]# cd /usr/local [root@server]# wget http://postfwd.org/postfwd-1.35.tar.gz  [root@server]# tar -xvzf postfwd-1.35.tar.gz [root@server]# mv postfwd-1.35 postfwd [root@server]# cp /usr/local/postfwd/etc/postfwd.cf /etc/postfix/ [root@server]# cp /usr/local/postfwd/bin/postfwd-script.sh /etc/init.d/postfwd [root@server]# chkconfig postfwd on [root@server]# service postfwd start

Woah there, it’s not that easy.. As the PostFWD documentation states quite adamantly, this will not work (or start) without a couple of Perl modules installed.

[root@server]# yum -y install perl perl-CPAN perl-prefork gcc

You’ll need to do the rest in ‘cpan’

[root@server]# cpan cpan[1]> install Net::Server::Daemonize ... cpan[1]> install Net::Server::Multiplex ... cpan[1]> install Net::Server::DNS ...

Once all of the Perl modules (and Perl) are installed, it’d be a great idea to issue a yum update, and reboot the system. Now you are ready to continue and configure PostFWD.

In terms of configuration, the world is your oyster with PostFWD. As the name suggests, it is essentially a firewall for your mail server, it can allow, drop, defer, reject silently, rate limit, rule match by message character counts, body sizes, send frequency, or a combination of any number of these factors.. Want to stop users x, y and z from sending more than 200Mb’s worth of attachments in a 12 hour period? No problem.

In this specific example, we want to rate limit (rather aggressively) all outbound mail to a specific domain. Specifically we don’t want to be sending any more than 10 emails every 30 minutes. Mails sent after this limit is reached will get rejected permanently. Mails within that limit can send at any frequency (unlike the stock implementation of rate limiting within postfix itself, where 10 emails in 30 minutes limit would delay ALL mails, and send 1 mail every 3 minutes, sending ALL mails eventually. In this scenario, that is not helpful.) 

Check everything’s working:

At this point it’s a good sanity prod to check if everything is up and listening on the ports you expect them to be. Use netstat to have a look at the two ports in question, you should see something strikingly similar to the below.

[root@server]# netstat -anpl | grep ':10040|:25' tcp        0      0 127.0.0.1:10040             0.0.0.0:*                   LISTEN      10181/postfwd.pid tcp        0      0 0.0.0.0:25                  0.0.0.0:*                   LISTEN      10278/master tcp        0      0 :::25                       :::*                        LISTEN      10278/master [root@server]#

If you don’t see the above, it means one of both of the services are either not running, or not able to bind to their respective ports, check the services are running, check things like SELinux aren’t stopping applications from binding to ports, check messages or your other syslog locations for evidence of problems.

Configuring PostFWD:

Earlier on, you copied postfwd.cf into /etc/postfix. It’s time to configure that with your rules. We are going to be defining just one, to rate limit as described above, but you will likely want a lot more, and also a catch-all style rule to be able to match “everything else”. Remember that our example was built on a custom internal mail server that has one specific task to do.

In this example, the only parts of the pre-supplied postfwd.cf we keep are the following:

[root@server]# cat /etc/postfix/postfwd.cf ## ## Definitions ## # Whitelists &&TRUSTED_NETS {         client_address=127.0.0.1/32 ## ## Ruleset ## ########################################################################## #Rate Limit TO: domain.com - 10 messages in 1800 seconds (30mins) id=ratelimit001         recipient_domain==domain.com         action=rate(recipient_domain/10/1800/421 4.7.1 - Sorry, exceeded 10 messages in 30 minutes.) ##########################################################################  # Whitelists &&TRUSTED_NETS {         client_address=127.0.0.1/32 ## ## Ruleset ## ########################################################################## #Rate Limit TO: domain.com - 10 messages in 1800 seconds (30mins) id=ratelimit001         recipient_domain==domain.com         action=rate(recipient_domain/10/1800/421 4.7.1 - Sorry, exceeded 10 messages in 30 minutes.) ##########################################################################

Note our rate limiting rule, the syntax is fairly straight forward. Define the recipient domain, give it the ‘rate’ action, and then tell it how many messages to limit, in what time frame, and then what triggered action happens if it is met. For us, we chose to reply with a 421 4.7.1 SMTP reply, thus rejecting the inbound RCPT command from the mail server.

Once you have your rule in place, check that PostFWD parses it correctly:

[root@server]# /usr/local/postfwd/sbin/postfwd -f /etc/postfix/postfwd.cf -C Rule   0: id->"ratelimit001"; action->"rate(recipient_domain/10/1800/421 4.7.1 - Sorry, exceeded 10 messages in 30 minutes.)"; recipient_domain->"==;domain.com"

Great!

Trigger the rate limit manually to see how PostFWD replies to it:

PostFWD comes with a “sample request” file that you can pipe into PostFWD to see how it reacts to differing rules. Modify the following file enough to suit your rate limit criteria

/usr/local/postfwd/tools/request.sample

Now throw that sample request at PostFWD using netcat (you may need to install this with ‘yum install nc’).

[root@server]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample action=DUNNO

The action “DUNNO”, although worrying at first, is actually the desired outcome. PostFWD doesn’t know what to do with the message, so it states “DUNNO” back to Postfix and lets the message pass. Keep firing that command until you hit your rate limit.

[root@server]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample action=DUNNO [root@server]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample action=DUNNO [root@server]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample action=421 4.7.1 - Sorry, exceeded 10 messages in 30 minutes.

BINGO! We hit the rate limit (I’ve excluded pointless command repetition from this guide). You can see that as soon as the rate limit is hit, PostFWD applies our own custom action that we set earlier. 421 4.7.1, message rejected. Now we just need to make that happen automatically, and with Postfix.

Integration with Postfix

The integration of PostFWD into Postfix is realtively simple. For this example, we are going to be adding PostFWD as a check_policy_service server for postfix to look up against. As we are specifically filtering on the recipient domain, I am going to add this to the “smtpd_recipient_restrictions” section of Postfix. This section may or may not exist already in your Postfix’s main.cf. 

Open /etc/postfix/main.cf and add or amend the following:

smtpd_recipient_restrictions =        check_policy_service inet:127.0.0.1:10040        permit_mynetworks        reject_unauth_destination 127.0.0.1:10040_time_limit = 3600

The key to note here, is that the check_policy_service is ABOVE items such as permit_mynetworks. For us, localhost is a trusted net (see the config earlier on), our mails that we wish to rate limit are also from localhost, so if permit_mynetworks comes first, the messages would be forever passed and sent, as Postfix would never bother checking with PostFWD via the check_policy_service (it stops processing after a successful OK reply).

And that’s it.. Restart postFWD, and then restart Postfix (PostFWD should always be up before Postfix), and you’re good to go. Rate Limit events are logged to /var/log/maillog, along with all other successful or not mail operations. You’ll want to tail this log for a while to see if anything’s going wrong.

Testing:

A nice and controlled way of testing with actual mail is to telnet into Postfix from the system itself.

Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. 220 mailtest1.vooservers.com ESMTP Postfix HELO mail.domain.com 250 monitoringtest.vooservers.com MAIL FROM: test@domain.com 250 2.1.0 Ok RCPT TO: test@domain.com 250 2.1.5 Ok data 354 End data with <CR><LF>.<CR><LF> message goes here . 250 2.0.0 Ok: queued as 5BECA21C21 quit 221 2.0.0 Bye Connection closed by foreign host. [root@server]#

This connects to the SMTP server (postfix), HELO’s as a mail server, defines a FROM: address, defines and TO: address, inputs some message body data, and then quits after the message is queued in postfix. Everything in yellow is text you have to type in.

You can repeat this until you hit your rate limit, tail the maillog in another screen whilst you do this, you’ll see Postfix happily relay all the mail up until you hit your defined rate limit, PostFWD will then step in and reply with the 421 message back to your telnet session. You’ll never get a chance to input a TO: address or any message body data. Perfect.

Summary;

So to recap, we:

The possibilities with PostFWD are extremely numerous, I’d recommend anyone embarking on this to check out the full documentation of both Postfix and PostFWD. Something that proved invaluable to me at times during our configuration and testing of this (and multiple other PostFWD instances).

            ----------------------------X---------------------------------------------

This document is geared towards using Policyd (cluebringer) on Ubuntu to limit mail with Postfix.

As the name implies, Policyd is a policy server for MTAs. From their website:

Policyd v2 (codenamed “cluebringer”) is a multi-platform policy server for popular MTAs. This policy daemon is designed mostly for large scale mail hosting environments. The main goal is to implement as many spam combating and email compliance features as possible while at the same time maintaining the portability, stability and performance required for mission critical email hosting of today. Most of the ideas and methods implemented in Policyd v2 stem from Policyd v1 as well as the authors’ long time involvement in large scale mail hosting industry.

Even if you don’t have a “large scale mail hosting environment”, you can still greatly benefit from Policyd.

Currently, I only use the Quota module with Policyd, which allows for limiting the rate of mail based on its attributes.

This document is written for Ubuntu 12.04 LTS Server. Older versions of Ubuntu (10.04 at least), used the older version of policyd, which will not be covered.

Table of Contents 

What this guide is

This guide mostly covers installing the software and getting it setup on your server. I haven’t gone into detail on how to actually create policies and such, as that can be figured out with the web interface.

Ubuntu comes with a cluebringer package in the repos, but it’s fairly outdated and doesn’t really do most of what you actually need to do to get it functional (the database setup, the web interface setup, and the Postfix implementation). That said, this guide will walk through installing it from source.

Install the prerequisites

You need:

If you want the web interface, you’ll also need:

Download the source

Visit http://devlabs.linuxassist.net/projects/policyd/files

Download the latest of the 2.0.x branch.

Once downloaded, extract it.

Setup SQL

Create a database for it. If using MySQL:

CREATE DATABASE policyd; GRANT ALL ON policyd TO 'cluebringer'@'localhost' IDENTIFIED BY 'secret';

Now go to the source directory and to the “database” directory.

If you’re using MySQL: there’s one modification:

#sed -i -e 's/Type=InnoDB/Engine=InnoDB/g' convert-tsql

Run the following:

for i in  core.tsql access_control.tsql quotas.tsql amavis.tsql checkhelo.tsql checkspf.tsql greylisting.tsql do   ./convert-tsql mysql $i done > policyd.mysql

Substitute ‘mysql’ for pgsql or sqlite. Make sure you put core.tsql FIRST in the for loop!

Now load the SQL into your database. If you’re using MySQL:

#mysql -u root -p policyd < policyd.mysql

Setup Config Files

#mkdir /etc/cluebringer

Copy the cluebringer.conf file from the source directory to the path you just created (/etc/cluebringer)

Install Program

Copy the program to the correct locations:

#cp -r cbp /usr/local/lib/policyd-2.0/ #cp cbpadmin /usr/local/bin/ #cp cbpolicyd /usr/local/sbin/

You’ll also need an init script. Mine’s just a modified version of one the Ubuntu package comes with:

postfix-cluebringer.init

Download that and move it to /etc/init.d/postfix-cluebringer. Make sure it’s owned by root and #chmod 755

That sources a defaults file:

postfix-cluebringer.default

Download that and move it to /etc/default/postfix-cluebringer. Give it root ownership and

#chmod 644

Edit it as needed.

Finally, create a user/group:

#useradd -d /etc/cluebringer -u 125 cluebringer

Configure it

Open up /etc/cluebringer/cluebringer.conf in an editor.

The items in this file are pretty self-explanatory. If in doubt, leave things as default.

Set the user and group variables to cluebringer

You also might want to set log_mail to mail@syslog:native for now to see log entries in your mail.log

Towards the end of the file, you’ll need to edit the variables under [database] with the appropriate values for the database you created.

Don’t worry about firing it up and breaking mail – there aren’t any rules that do anything by default.

Start the daemon

You should be able to start the daemon at this point.

#/etc/init.d/postfix-cluebringer start

Watch /var/log/cbpolicyd.log for output.

Install WebUI

This is optional, but highly recommended. Otherwise, you’ll have to configure the software via SQL.

Copy the webui directory to your desired location. For example:

#cp -r webui /var/www/cluebringer

To make things easy, I modify the file includes/config.php and replace everything with:

<?phprequire_once("/etc/cluebringer/cluebringer-webui.conf");?>

Then, create the file /etc/cluebringer/cluebringer-webui.conf with the following contents:

<?php# mysql:host=xx;dbname=yyy # pgsql:host=xx;dbname=yyy # sqlite:////full/unix/path/to/file.db?mode=0666 # #$DB_DSN="sqlite:////tmp/cluebringer.sqlite"; #$DB_DSN="pgsql:host=xx;dbname=yyy"; #$DB_DSN="mysql:host=xx;dbname=yyy"; #$DB_DSN="_DBC_DBTYPE_:host=_DBC_DBSERVER_;dbname=_DBC_DBNAME_"; $DB_DSN="mysql:host=localhost;dbname=policyd";$DB_USER="cluebringer";$DB_PASS="secret";?>

Edit that accordingly with the variables you used to setup your database.

Now you need a config for your web server. Nothing crazy here. Look at my examples if you need to:

Apache vhost (SSL+LDAP auth)

Nginx (SSL+auth)

Integrate with Postfix

You need to tell Postfix about it. The INSTALL file says add:

check_policy_service inet:127.0.0.1:10031

in BOTH smtpd_recipient_restrictions and smtpd_end_of_data_restrictions

Wherever you add it, make sure you put it before other permit lines as appropriate. For example, if you want to rate limit outgoing mail, you might want to place it before permit_mynetworks

Re[start|load] postfix and watch mail.log for errors connecting to 10031!

Completion

You should be done at this point and able to navigate to the webui.

Creating policies

If you explore the web interface, you should pick up on how to add rules. It comes with a few defaults to give you an idea.

First, you’ll want to navigate to Policies -> Groups and change the values for internal_domains and internal_ips

You do that by selecting the item and choosing Members from the action drop-down menu. From there, you’ll see the default items. You can change them by selecting the item and choosing Change from the action drop-down menu.

One thing to watch for is that adding entries will be disabled by default. Once you add one, you’ll need to Change it and enable it if you want to use it.

For limiting the number of messages coming in/going out, you’ll look under the Quotasmodule.

Here, you’ll create polices such as Limit sender@domain messages on the Default Inbound policy. Then, you’ll add a Limit to that policy. So, create a policy and save it, select the policy and choose Limits from the action drop-down. Add a number of messages for the time period you defined, save it. Then, make sure you Change the limit after saving and enable it. You’ll also have to enable the quota you created.

In SQL, it looks like this:

mysql> select * from policyd.quotas; +----+----------+---------------------+-----------------------+--------+---------+------+----------------------------------------------------------+----------+ | ID | PolicyID | Name                | Track                 | Period | Verdict | Data | Comment                                                  | Disabled | +----+----------+---------------------+-----------------------+--------+---------+------+----------------------------------------------------------+----------+ |  6 |        3 | Limit incoming user | Sender:user@domain    |   3600 | REJECT  | 501  | Limit incoming mail from a user to 700 messages per hour |        0 | |  7 |        3 | Limit incoming IP   | SenderIP:/32          |   3600 | REJECT  | 699  |                                                          |        0 | |  8 |        2 | Limit outgoing      | Sender:user@domain    |   3600 | REJECT  | 300  | Limit outgoing to 300 per hour                           |        0 | |  9 |        3 | Incoming to user    | Recipient:user@domain |   3600 | REJECT  | 499  |                                                          |        0 | +----+----------+---------------------+-----------------------+--------+---------+------+----------------------------------------------------------+----------+

mysql> select * from policyd.quotas_limits; +----+----------+--------------+--------------+---------+----------+ | ID | QuotasID | Type         | CounterLimit | Comment | Disabled | +----+----------+--------------+--------------+---------+----------+ |  7 |        6 | MessageCount |          501 |         |        0 | |  8 |        7 | MessageCount |          699 |         |        0 | |  9 |        8 | MessageCount |          300 |         |        0 | | 10 |        9 | MessageCount |          499 |         |        0 | +----+----------+--------------+--------------+---------+----------+

So in a period of 3600 seconds (1 hour), I’m limiting incoming mail from a single sender to 501 messages, and REJECTing them beyond that.

In this example, we’re also limiting outgoing messages from a single user to 300 messages in a 1 hour period.

Once you get a policy, and ensured it’s enabled (and all its components are enabled), watch mail.log for output (tail -f mail.log | grep cbpolicy)

Be aware of the whole “chain” to policyd. As you can see above, a quota doesn’t do any good without a limit set, and both need to be enabled. A quota also won’t do anything if it isn’t associated with a policy. Polices are determined by their members (e.g. matching IP addresses).

        ---------------------------X----------------------------------------------

Postfix & PostFWD Integration - CentOS 6.8

Greetings all, this may be a long post, but bear with me. Most of the info is just code and command line snippets.

Essentially, I needed a way to be able to agressively rate limit the number of mails that were being sent TO a specific domain (the whole domain), any mail that exceeded the rate limit needs to be rejected of dropped. Not delayed of deferred. 

The main issue? I cannot use Exim or any other friendly MTA. I MUST use postfix, so here we are, with Postfix and PostFWD, and a couple of issues.

Code:

Some versions.. Postfix v2.6.6 PostFWD v1.3.5 CentOS 6.8 x64

So to begin with, I verify that my rate limit rule exists in postfwd and is interpreted correctly from postfwd.cf in the postfix directory (it is):

Code:

[root@monitoringtest ~]# /usr/local/postfwd/sbin/postfwd -f /etc/postfix/postfwd.cf -C Rule   0: id->"davelimit001"; action->"rate(recipient_domain/3/1800/421 4.7.1 - Sorry, exceeded 3 messages in 30 minutes.)"; recipient_domain->"==;dave-byrne.co.uk" [root@monitoringtest ~]#

The above rate limits all outbound mail destined for anything @dave-byrne.co.uk to just 3emails within a 30minute window. The domain is my own for testing, but in production, this will rate limit messages bound for an external Email to SMS gateway.

A quick check to ensure Postfix and PostFWD are up and listening (they are):

Code:

[root@monitoringtest ~]# netstat -anpl | grep ':10040\|:25' tcp        0      0 127.0.0.1:10040             0.0.0.0:*                   LISTEN      4093/postfwd.pid tcp        0      0 0.0.0.0:25                  0.0.0.0:*                   LISTEN      4190/master tcp        0      0 :::25                       :::*                        LISTEN      4190/master [root@monitoringtest ~]#

I then fire sample requests at the PostFWD server listening internally on port 10040. you can see PostFWD passes (with its DUNNO action) 3 mails, before applying the rate limit to the 4th and rejecting it with a 421. Perfect. Now to just make Postfix use PostFWD!

Code:

[root@monitoringtest ~]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample action=DUNNO [root@monitoringtest ~]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample action=DUNNO [root@monitoringtest ~]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample action=DUNNO [root@monitoringtest ~]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample action=421 4.7.1 - Sorry, exceeded 3 messages in 30 minutes. [root@monitoringtest ~]#

An excerpt from /var/log/maillog showing the rate limit applying to the 4th message above

Code:

Oct 19 17:15:47 monitoringtest postfwd[25933]: [RULES] rule=0, id=davelimit001, client=root@monitoringtest.co.uk[168.100.1.7],  sender=<root@monitoringtest.co.uk>, recipient=<admin@dave-byrne.co.uk>, helo=<dave-byrne.co.uk>, proto=ESMTP, state=RCPT, rate=rate/4/21.67s, delay=0.00s,  hits=davelimit001, action=421 4.7.1 - Sorry, exceeded 3 messages in 30 minutes.

So, to integrate with Postfix, I added the following to my postfix main.cf file:

Code:

[root@monitoringtest ~]# tail -n 3 /etc/postfix/main.cf 127.0.0.1:10040_time_limit   = 3600 smtpd_recipient_restrictions = permit_mynetworks,         check_policy_service inet:127.0.0.1:10040 [root@monitoringtest ~]#

This is all as per the PostFWD documentation.

I then use telnet locally to connect to Postfix and send emails to admin[at]dave-byrne.co.uk. Like so:

Code:

[root@dedweb ~]# telnet <test-server-IP-here> smtp Trying xx.xx.xx.xx... Connected to xx.xx.xx.xx. Escape character is '^]'. 220 monitoringtest.xxxxxxxxx.com ESMTP Postfix HELO dave-byrne.co.uk 250 monitoringtest.xxxxxxxxx.com MAIL FROM: root@monitoringtest.co.uk 250 2.1.0 Ok RCPT TO: admin@dave-byrne.co.uk 250 2.1.5 Ok DATA 354 End data with <CR><LF>.<CR><LF> test1. . 250 2.0.0 Ok: queued as 946B621C15

I do this 4, 5, 6 times, the 4th should have rate limited like it did when using netcat to directly fire them at PostFWD. But it doesn't, postfix just merrily goes about its business relaying the mails like it thinks it should. I can send a hundred and it wont even think about rate limiting. During this time, PostFWD prints NOTHING to the logs, it isn't hit at all, it doesn't pass anything, it doesn't block anything. Postfix isn't using PostFWD, even though its set in smtpd_recipient_restrictions as a check_policy_service.

And this is where I'm stuck. 3 days in and I'm none the wiser. Has anyone ever used PostFWD (Postfix Firewall Daemon) before successfully, with any type of rule set, regardless of rate limiting. I'm open to many suggestions, however I cannot change from postfix, I cannot change OS, and I cannot hand off to an external intermediate mail relay due to security concerns and workflow issues with what's actually being sent.

Thanks all,

Dave.

 

  #2

Immediately after asking this question, I realised that postfix recipient restrictions are executed in the order they appear in main.cf. So my

Code:

 10-19-2016, 05:12 PM

dave.b

LQ Newbie

 

Registered: Oct 2016

Posts: 2

Original Poster 

Rep: 

[root@monitoringtest ~]# tail -n 3 /etc/postfix/main.cf 127.0.0.1:10040_time_limit   = 3600 smtpd_recipient_restrictions = permit_mynetworks,          check_policy_service inet:127.0.0.1:10040 [root@monitoringtest ~]#

... Was returning a hard 'OK' at 'permit_mynetworks'. An OK will stop processing further restrictions.

I resolved my issue by placing the check_policy_service at the top of my smtp recipient restrictions. If PostFWD passes a mail, it replies with 'DUNNO' or rather 'DUNNO/OK', this passes, but continues to run further smtp restrictions.

With this in place, PostFWD was free to pass the mail that didn't trigger the rate limit, but once it did trigger the rate limit, it replied with a 421 rejection. Exactly what I wanted.

So keep in mind, the order in which you restrict, and what you are actually restricting, matters quite a lot.

Please note that this is a private internal mail server that serves one very specific purpose. Do not use this code for a production or shared mail server

------------------

ratelimit-policyd

New Features

The original forked code from bejelith/send_rate_policyd was improved with the following new features:

Installation

Recommended installation:

$ cd /opt/ $ git clone https://github.com/onlime/ratelimit-policyd.git ratelimit-policyd $ cd ratelimit-policyd $ chmod +x install.sh $ ./install.sh

Create the DB schema and user:

$ mysql -u root -p < mysql-schema.sql

GRANT USAGE ON *.* TO policyd@'localhost' IDENTIFIED BY '********'; GRANT SELECT, INSERT, UPDATE, DELETE ON policyd.* TO policyd@'localhost';

Adjust configuration options in daemon.pl:

### CONFIGURATION SECTIONmy @allowedhosts    = ('127.0.0.1', '10.0.0.1'); my $LOGFILE         = "/var/log/ratelimit-policyd.log"; my $PIDFILE         = "/var/run/ratelimit-policyd.pid"; my $SYSLOG_IDENT    = "ratelimit-policyd"; my $SYSLOG_LOGOPT   = "ndelay,pid"; my $SYSLOG_FACILITY = LOG_MAIL; chomp( my $vhost_dir = `pwd`); my $port            = 10032; my $listen_address  = '127.0.0.1'; # or '0.0.0.0'my $s_key_type      = 'email'; # domain or emailmy $dsn             = "DBI:mysql:policyd:127.0.0.1"; my $db_user         = 'policyd'; my $db_passwd       = '************'; my $db_table        = 'ratelimit'; my $db_quotacol     = 'quota'; my $db_tallycol     = 'used'; my $db_updatedcol   = 'updated'; my $db_expirycol    = 'expiry'; my $db_wherecol     = 'sender'; my $deltaconf       = 'daily'; # hourly|daily|weekly|monthlymy $defaultquota    = 1000; my $sql_getquota    = "SELECT $db_quotacol, $db_tallycol, $db_expirycol FROM $db_table WHERE $db_wherecol = ? AND $db_quotacol > 0"; my $sql_updatequota = "UPDATE $db_table SET $db_tallycol = $db_tallycol + ?, $db_updatedcol = NOW(), $db_expirycol = ? WHERE $db_wherecol = ?"; my $sql_updatereset = "UPDATE $db_table SET $db_tallycol = ?, $db_updatedcol = NOW(), $db_expirycol = ? WHERE $db_wherecol = ?"; my $sql_insertquota = "INSERT INTO $db_table ($db_wherecol, $db_quotacol, $db_tallycol, $db_expirycol) VALUES (?, ?, ?, ?)"; ### END OF CONFIGURATION SECTION

Take care of using a port higher than 1024 to run the script as non-root (our init script runs it as user "postfix").

In most cases, the default configuration should be fine. Just don't forget to paste your DB password in $db_password.

Now, start the daemon:

$ service ratelimit-policyd start

Testing

Check if the daemon is really running:

$ netstat -tl | grep 10032 tcp        0      0 localhost.localdo:10032 *:*                     LISTEN  $ cat /var/run/ratelimit-policyd.pid 30566  $ ps aux | grep daemon.pl postfix  30566  0.4  0.1 176264 19304 ?        Ssl  14:37   0:00 /opt/send_rate_policyd/daemon.pl  $ pstree -p | grep ratelimit init(1)-+-/opt/ratelimit-(11298)-+-{/opt/ratelimit-}(11300)         |                        |-{/opt/ratelimit-}(11301)         |                        |-{/opt/ratelimit-}(11302)         |                        |-{/opt/ratelimit-}(14834)         |                        |-{/opt/ratelimit-}(15001)         |                        |-{/opt/ratelimit-}(15027)         |                        |-{/opt/ratelimit-}(15058)         |                        `-{/opt/ratelimit-}(15065)

Print the cache content (in shared memory) with update statistics:

$ service ratelimit-policyd status Printing shm: Domain : Quota : Used : Expire Threads running: 6, Threads waiting: 2

Postfix Configuration

Modify the postfix data restriction class smtpd_data_restrictions like the following, /etc/postfix/main.cf:

smtpd_data_restrictions = check_policy_service inet:$IP:$PORT

sample configuration (using ratelimitpolicyd as alias as smtpd_data_restrictions does not allow any whitespace):

smtpd_restriction_classes = ratelimitpolicyd ratelimitpolicyd = check_policy_service inet:127.0.0.1:10032  smtpd_data_restrictions =         reject_unauth_pipelining,         ratelimitpolicyd,         permit

If you're sure that ratelimit-policyd is really running, restart Postfix:

$ service postfix restart

Logging

Detailed logging is written to ``/var/log/ratelimit-policyd.log```. In addition, the most important information including the counter status is written to syslog:

$ tail -f /var/log/ratelimit-policyd.log  Sat Jan 10 12:08:37 2015 Looking for demo@example.com Sat Jan 10 12:08:37 2015 07F452AC009F: client=4-3.2-1.cust.example.com[1.2.3.4], sasl_method=PLAIN, sasl_username=demo@example.com, recipient_count=1, curr_count=6/1000, status=UPDATE  $ grep ratelimit-policyd /var/log/syslog Jan 10 12:08:37 mx1 ratelimit-policyd[2552]: 07F452AC009F: client=4-3.2-1.cust.example.com[1.2.3.4], sasl_method=PLAIN, sasl_u

Configure sender rate limits to prevent spam, using cluebringer (policyd) with Postfix

This small how-to will show you how to configure cluebringer (aka policyd) to set a per-hour/per-user limit for sent mails. Note that sending to multiple recipient will count like multiple mails were sent.

This how-to is Debian-oriented but should apply to any unix operating system.

Requirements

A mail server with Postfix installed.

Installation

Install a DBMS (MySQL for instance), cluebringer, and cluebringer-webui :

apt-get install mysql-server cluebringer cluebringer-mysql cluebringer-webui

Note that cluebringer-webui will install apache as a dependency if you don’t already have a webserver.

Set-up the Cluebringer database

Get the initial database schema that correspond to your DBMS, for instance mysql :

cp /usr/share/doc/postfix-cluebringer/database/policyd-db.mysql.gz ~/ && gunzip ~/policyd-db.mysql.gz

Create the database, and populate it with the initial dump :

# cd  ~/ && mysql -u root -p mysql> CREATE DATABASE cluebringer; mysql> CREATE USER 'cluebringer'@'localhost' IDENTIFIED BY 'mypassword'; mysql> GRANT ALL PRIVILEGES ON cluebringer.* TO 'cluebringer'@'localhost'; mysql> \. policyd-db.mysql mysql> quit mysql> Bye

Note that on Debian I had to modify the dump to make it work, TYPE=InnoDB was rejected by MySQL as an invalid syntax.

Configure Cluebringer

Add your DBMS credentials to the file /etc/cluebringer/cluebringer.conf :

DSN=DBI:mysql:dbname=cluebringer;host=localhost  DB_Type=mysql DB_Host=localhost DB_Port=3306 DB_Name=cluebringer Username=cluebringer Password=mypassword

And start it :

service postfix-cluebringer start

Configure Cluebringer webui

Configure the file /etc/cluebringer/cluebringer-webui.conf with your DBMS credentials :

<?php  $DB_DSN="mysql:host=localhost;dbname=cluebringer"; $DB_USER="cluebringer"; $DB_PASS="mypassword";

Cluebringer Webui needs a web server to run. Copy the sample configuration from the package documentation :

cp /usr/share/doc/postfix-cluebringer-webui/examples/httpd/cluebringer-httpd.conf /etc/apache2/conf.d/

Restart Apache :

service apache2 restart

You may need to adjust a few things to access it from the outside. If you a really lazy, just make a ssh tunnel to access the webserver from localhost :

ssh -L 8008:localhost:80 mylogin@mymailserver

Don’t forget : you have to make this tunnel from the outside, do not run this command on server, it won’t work.

You should now be able to open http://localhost:8080/ and see your fresh new Cluebinger Webui !

Configure Cluebringer using its webui

Add a policy

Under Policies -> Main, disable Test policy (select policy and choose Action -> Change and switch Disabled to yes, validate)

Add a new policy : Action -> Add, give it a name and a description

Activate your new policy : select policy and choose Action -> Change (switch Disabled to no)

Add a new member to your policy : select it and choose Action -> Members, and then Action -> Add. Specify any as source and any as destination.

Go back to your policy, choose Action -> Members, and the select your member, do Action -> Change, and activate your new member (switch Disabled to no).

Add a quota

Under Quotas -> Configure, disable Test quotas.

Add a new quota : Choose Action -> Add

Activate your quota : switch Disabled to no.

Add a limit to your quota : select your quota, and choose Action -> Limits, then Action -> Add.

Activate your limit : switch Disabled to no.

Configure Postfix to call Cluebringer for each mail sent

Open /etc/postfix/main.cf and locate the line smtpd_sender_restrictions.

Add check_policy_service inet:127.0.0.1:10031 at the end of the line, for instance :

smtpd_sender_restrictions = check_sender_access mysql:/etc/postfix/mysql-virtual_sender.cf, check_policy_service inet:127.0.0.1:10031

If the line does not exists, simply add it.

Don’t forget to restart Postfix :

service postfix restart

Check your config

You can now send some mails to see what happens. To check if these mails are passed to Cluebringer, connect to MySQL as the cluebringer user :

# mysql -u cluebringer -p cluebringer

And execute the query :

mysql> SELECT * FROM quotas_tracking;

You should see the value LastUpdate and Counter updating when sending a mail. Note that sending to multiple recipient will count like multiple mails were sent.

Pitfalls, bleeding edges, etc

Cluebringer versions prior to 2.1.x does not support IPv6, your customers won’t be able to send any mail if they have an IPv6 connection.

Unfortunately, the Debian stable version (wheezy) provides Cluebringer 2.0.10 within its repositories, as well as the experimental release of Debian (sid). As an alternative, you should consider installing the 2.1.x experimental Cluebringer from official website instead of Debian packages from repositories.

References

     ----------------------------