Sendmail-Wrapper for PHP
Years ago I wrote about the extensive sendmail wrapper with sender throttling, a pretty simple Perl script. It reliably provided throttling of the email volume per day by the sender’s original UID (user id). It also logged the pathes of scripts that sent emails directly via sendmail (e.g. via PHP’s mail() function). The main flaw in the original sendmail wrapper was security, though. As in Linux, every executable script must be readable by the user that calls it, the throttle table in MySQL was basically open and every customer could manipulate it. Every customer could raise his own throttling limit and circumvent it.
Today, I’m publishing my new sendmail-wrapper that is going to fix all the flaws of the previous version and add some nice extras. The new sendmail-wrapper is written entirely in PHP and does not require any external libraries. It is a complete rewrite and has pretty much nothing in common with the old Perl version.
- onlime/sendmail-wrapper (Github)
- Lets you monitor any mail traffic from PHP scripts
- Allows throttling (limiting) emails sent by PHP’s mail() function
- Throttle by sent email and/or recipient count per day
- Logs both to syslog and database with message metadata
- Logs common mail headers like From, To, Cc, Bcc, Subject
- Fixes Return-Path header on the fly for users who did not correctly set it
- Highly secured setup, customers cannot access the logging/throttling database
- Standalone PHP application without any external library dependencies
- Built for shared webhosting environment where PHP runs as cgi/FastCGI/suPHP
- No cronjobs required, sendmail-wrapper will reset counters automatically every day
The idea behind this secure setup
Logging is done only via syslog, no customer can read or modify syslog entries. The sendmail-wrapper is split into two components:
sendmail-throttle.php. The wrapper component will only do some syslogging and analyze some headers. It will also fix the
Return-Path header if applicable as most customers forget to correctly set it in PHP.
sendmail-wrapper.php is readable and executable by every customer. This is a strong requirement as our customers all run their script under their own UID, PHP runs as cgi/FastCGI/suPHP. The throttle component is where it gets more interesting. The
sendmail-throttle.php script can only be executed by user sendmailwrapper which is an equally unprivileged system user like every other customer user. The throttling script will NOT be world readable and it will read it’s configuration from an extra file
config.private.ini which contains the database password. The correct permissions are set by our installation script.
Most customers just forget to set a
Return-Path when they use PHP’s built-in mail() function. Even if they try to set it in the
$additional_headers variable, it will not get correctly set on most systems. The only way ist to set it via the
$additional_parameters variable, using the
-r switches. 99% of our customers simply can’t manage this, seriously!
This sendmail-wrapper is extracting the email address from the
Return-Path header, and if it doesn’t exist, it will just take the sender’s address (from the
From header) as return-path. This is actually one of the most important features of my sendmail-wrapper, even if it might get overlooked and you’re installing the wrapper because of it’s logging and throttling capabilities.
(for detailed instructions, please consult the README on Github)
Clone repository from GitHub:
$ cd /opt/ $ git clone https://github.com/onlime/sendmail-wrapper.git sendmail-wrapper
Set up system user for sendmail-wrapper:
$ adduser --system --home /no/home --no-create-home --uid 6000 --group --disabled-password --disabled-login sendmailwrapper $ adduser sendmailwrapper customers
The installer script
install.sh will correctly set up permissions and symlink the wrapper scripts:
$ cd /opt/sendmail-wrapper/ $ ./install.sh
Add the following lines to your
www-data ALL = (sendmailwrapper) NOPASSWD:/usr/sbin/sendmail-throttle [0-9]* %customers ALL = (sendmailwrapper) NOPASSWD:/usr/sbin/sendmail-throttle [0-9]*
Add/modify the following in your php.ini:
sendmail_path = /usr/sbin/sendmail-wrapper auto_prepend_file = /var/www/shared/prepend.php
Import the sendmailwrapper database schema:
$ mysql -u root -p < schema/schema.mysql.sql
Create a MySQL user with the following permissions:
GRANT USAGE ON *.* TO sendmailwrapper@'localhost' IDENTIFIED BY '********'; GRANT SELECT, INSERT, UPDATE ON sendmailwrapper.throttle TO sendmailwrapper@'localhost'; GRANT INSERT ON sendmailwrapper.messages TO sendmailwrapper@'localhost';
Default configuration can be found in
[global] defaultTZ = Europe/Zurich adminTo = email@example.com adminFrom = firstname.lastname@example.org [wrapper] sendmailCmd = "/usr/sbin/sendmail -t -i" throttleCmd ="sudo -u sendmailwrapper /usr/sbin/sendmail-throttle" throttleOn = true defaultHost = "example.com" syslogPrefix = sendmail-wrapper-php xHeaderPrefix = "X-Example-" [throttle] countMax = 1000 rcptMax = 1000 syslogPrefix = sendmail-throttle-php adminSubject = "Sendmail limit exceeded" [db] dsn = "mysql:host=localhost;dbname=sendmailwrapper" user = sendmailwrapper pass = "xxxxxxxxxxxxxxxxxxxxx"
You should not change any of the above values. Create your own
config.local.ini instead to overwrite some values, e.g.:
[global] adminTo = email@example.com adminFrom = firstname.lastname@example.org [wrapper] defaultHost = "mydomain.com" xHeaderPrefix = "X-MyCompany-"
Never put your database password in any of the above configuration files. Use another configuration file called
config.private.ini instead, e.g.:
[db] pass = "mySuper-SecurePassword/826.4287+foo"
Sample syslog output:
Mar 24 11:50:21 web1 php: sendmail-throttle-php: user=web4111 (4111:4111), rcpts=1, status=0, command=/usr/sbin/sendmail-throttle 1, count_max=1000, count_cur=3, count_tot=9, rcpt_max=1000, rcpt_cur=3, rcpt_tot=9 Mar 24 11:50:21 web1 php: sendmail-wrapper-php: uid=web4111#012, email@example.com, firstname.lastname@example.org, to="email@example.com", cc="", bcc="", subject="Just testing", site=www.example.com, client=188.8.131.52, script=/var/www/example/public_html/www/index.php, throttleStatus=0
You may access both MySQL tables directly:
throttle to tweak each system user’s limits (they will be persistent! Only counters are gettingn reset every day),
messages for detailed message header reviewing or for statistical purposes.
Author: Philip Iezzi (Pipo)
Owner of Onlime GmbH - providing quality webhosting with love. All into system engineering, Linux sysadmin, security, full stack web development, mountain biking, slacklining, dancing & feeling connected to nature.