Sendmail wrapper limiting amount of emails sent by php
pip install psutils
php_admin_value[sendmail_path] = /usr/sbin/safe_sendmail
To: firma@starlab.cz Subject: This is a test message subject X-PHP-Originating-Script: 0:index.php This is a test message body
for i in `seq 1 300`; do cat /tmp/testmail.txt | safe_sendmail firma@starlab.cz ; done
tail -f /usr/local/safe_sendmail/safe_sendmail.log
#!/usr/bin/env python import os import sqlite3 import sys import logging import re import datetime as dt import subprocess import shlex import psutil logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) work_dir = "/usr/local/safe_sendmail" # create a file handler logfile = os.path.join(work_dir, 'safe_sendmail.log') handler = logging.FileHandler(logfile) handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) sendmail_bin = '/usr/sbin/sendmail -t -i' db_file = os.path.join(work_dir, 'safe_sendmail.db') # Number of mails in given amount of minutes to stop the sending threshold = 200 time_scope = 60 notification_mail = """To: firma@starlab.cz Subject: Alert! Too many outgoing emails from server {server_name} Safe sendmail script started filtering messages on server {server_name}. Threshold reached. """.format(server_name='webhosting.starlab.cz') def check_db(): with sqlite3.connect(db_file) as conn: c = conn.cursor() c.execute("SELECT name FROM sqlite_master WHERE name='mails'") is_table = len(c.fetchall()) if is_table == 0: c.execute("CREATE TABLE mails (date text, address_to text, spamcount integer)") conn.commit() return True def send_mail(mail): logger.debug('Sending mail!\n%s' % mail) command = shlex.split(sendmail_bin) try: process = subprocess.Popen(command, stdin=subprocess.PIPE) process.communicate(mail) if process.returncode == 0: return True else: return False except: logger.exception("Unable to send mail") return False if __name__ == "__main__": parent_process = psutil.Process(os.getppid()) grandparent_process = psutil.Process(parent_process.ppid()) check_db() mail = sys.stdin.read() address_regex = re.compile(u"To: (?P<mail_address>\S+)", re.UNICODE) address = address_regex.match(mail.replace('\n', ' ')).group('mail_address') timestamp = dt.datetime.now() logger.debug("Address: %s" % address) logger.debug("Datetime: %s" % timestamp) past = timestamp - dt.timedelta(minutes=time_scope) with sqlite3.connect(db_file) as conn: c = conn.cursor() c.execute('SELECT spamcount FROM mails ORDER BY date DESC LIMIT 1') last_spam_count = c.fetchone()[0] if last_spam_count is None: last_spam_count = 0 c.execute('SELECT COUNT(*) FROM mails where date > ?', (past,)) count_since_past = c.fetchone()[0] + 1 logger.debug("Count since %s %d" % (timestamp, count_since_past)) c.execute('INSERT INTO mails VALUES(?,?,?)', (timestamp, address, count_since_past)) conn.commit() if count_since_past < threshold: if send_mail(mail): sys.exit(0) else: sys.exit(2) else: if last_spam_count < threshold: send_mail(notification_mail) # logger.info("Parent: {}".format(parent_process.cmdline())) # uncomment to get parent's cmdline # logger.info("Grandparent: {}".format(grandparent_process.cmdline())) # uncomment to get grandparent's cmdline # logger.info(mail) # uncomment to get raw input (mail content and headers) logger.warning("Unable to send mail to address %s - quota exceeded! (%d mails sent in last %d minutes - limit %s)" % (address, count_since_past, time_scope, threshold)) sys.exit(1)