Limiting mail delivery from PHP websites
Tagged:  •    •    •    •    •    •  

QMail is quite a challenge to set up right, this will take you hours if you don't install it on a weekly basis. When you closed all holes from the outside to prevent acting like a open relay, there is the security on the localhost itself. It happened a few times already that a PHP script had an impromper loop condition which had a mail() call inside. This will guarantee you for having 10.000s of mails in your queue. Or think of a leak in a contact form abused by spammers.
Since PHP will call qmail-inject directly, all security measures related to SMTP will be bypassed.

To my surprise, there was little information to be found about it on the web on how to prevent this. So that's why I created a solution myselves in preventing this. It is (yet another) wrapper around the sendmail command, and it puts a row in a database table, recording which user sent something at which time. Before it sends something, it checks how many mails the same user has sent in the past hour. If the user exceeded some threshold, the script refuses to pass the mail to QMail.

The requirements for this setup are:

  • A suexec enabled Apache server with PHP running in CGI mode. Every website (read: PHP script) should run as its own user. It's beyond the scope of this document to discuss this, but this page on Apache.org is a start to get this right.
  • A working MySQL installation somewhere, preferably on the localhost.

So these are the steps to set this thing up:

MySQL
Create a database called qmaillog and a user qmaillog with enough permissions to SELECT, INSERT and DELETE.
After that has been done, create a table called log which only has two columns: user and time.

Wrapper installation

  1. Download the sendmail_accounting script below and put it in $QMAILDIR/bin. Where $QMAILDIR is, of course, the base path of your QMail installation.
  2. Set the variables at the top of the script to your situation:
    • MYSQLDB - The name of the MySQL database.
    • MYSQLUSER - The user name access to the $MYSQLDB database.
    • MYSQLPW - The password of $MYSQLUSER.
    • THRESHOLD - This defines how many emails each user is allowed to send in a period of one hour.
    • REPORTERRORSTO - When some website exceeds the $THRESHOLD, send a warning mail to this email address. If you want to supply more than one address, make sure they are separated by spaces and the whole variable value is between quotes.

PHP
Adapt the system wide php.ini file in order to change the sendmail path:

; sendmail_path = /some/path/to/sendmail

becomes

sendmail_path = /var/qmail/bin/sendmail_accounting

Then restart Apache:

apachectl restart

and your PHP scripts will pipe your mails through the sendmail_accounting wrapper first.