This is an old revision of the document!


Python sendmail wrapper

Sendmail wrapper limiting amount of emails sent by php

Installation

  • Copy the script below into /usr/sbin/safe_sendmail
  • Put the path to script into sendmail_path variable in php.ini
  • Database and log files will be created in /tmp/safe_sendmail.db and /tmp/safe_sendmail.log respectively
  • Timewindow (default 1 hour) and treshold (default 200 emails) parameters are declared in script

Testing

  • Put following content in /var/www/testmail.txt: (few blank lines may be needed at the end of the file)
    To: firma@starlab.cz
    Subject: This is a test message subject
    X-PHP-Originating-Script: 0:index.php
     
    This is a test message body
  • Run:
    for i in `seq 1 300`; do cat /var/www/testmail.txt | safe_sendmail firma@starlab.cz ;  done
  • Observe log file:
    tail -f /tmp/safe_sendmail
  • After the treshold number of emails script refuses to send more emails.

Code

#!/usr/bin/env python
 
import os
import sqlite3
import sys
import logging
import re
import datetime as dt
import subprocess
import shlex
 
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
 
# create a file handler
logfile = '/tmp/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 = '/tmp/safe_sendmail.db'
 
# Number of mails in given amount of minutes to stop the sending
threshold = 200
time_scope = 60
 
 
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)")
            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__":
    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('INSERT INTO mails VALUES(?,?)', (timestamp, address))
        conn.commit()
        c.execute('SELECT COUNT(*) FROM mails where date > ?', (past,))
        count_since_past = c.fetchone()[0]
        logger.debug("Count since %s %d" % (timestamp, count_since_past))
        if count_since_past < threshold:
            if send_mail(mail):
                sys.exit(0)
            else:
                sys.exit(2)
        else:
            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)
 
linux/apache/sendmail_python.1485181773.txt.gz · Last modified: 2017/01/23 15:29 by vondra