Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
linux:apache:sendmail_python [2017/01/23 15:24]
vondra
linux:apache:sendmail_python [2017/02/21 14:27] (current)
tomsa [Code]
Line 2: Line 2:
 Sendmail wrapper limiting amount of emails sent by php Sendmail wrapper limiting amount of emails sent by php
 ===== Installation ===== ===== Installation =====
- * Copy the script below into /​usr/​sbin/​safe_sendmail +  * <code bash>pip install psutils</​code>​ 
- * Put the path to script into sendmail_path variable in php.ini +  ​* ​ Copy the script below into /​usr/​sbin/​safe_sendmail 
- * Database and log files will be created in /tmp/safe_sendmail.db and /tmp/​safe_sendmail.log respectively +   Put the path to script into sendmail_path variable in php.ini ​or into /​etc/​php5/​fpm/​pool.d/​www.conf if using php-fpm 
- * Timewindow (default 1 hour) and treshold (default 200 emails) parameters are declared in script+  <​code>​php_admin_value[sendmail_path] = /​usr/​sbin/​safe_sendmail</​code>​ 
 +  *  ​Database and log files will be created in workdir defined in script ("/usr/local/​safe_sendmail/") this dir should be writable for everyone (limited access could lead to unexpected bahaviour) 
 +   Timewindow (default 1 hour) and treshold (default 200 emails) parameters are declared in script 
 +  * For deeper inspection of mail traffic uncomment logging lines at the end (logging the mail body)
  
 ===== Testing ===== ===== Testing =====
- * Put following content in /var/www/​testmail.txt:<​code file>+  ​* Put following content in /tmp/​testmail.txt: ​(few blank lines may be needed at the end of the file)<code file>
 To: firma@starlab.cz To: firma@starlab.cz
 Subject: This is a test message subject Subject: This is a test message subject
Line 17: Line 20:
  
  
-<​code>​ +</code> 
- * Run:<​code bash>for i in `seq 1 1000`; do cat /var/www/​testmail.txt | safe_sendmail firma@starlab.cz ;  done</​code>​ +  * Run:<​code bash>for i in `seq 1 300`; do cat /tmp/​testmail.txt | safe_sendmail firma@starlab.cz ;  done</​code>​ 
- * Observe log file:<​code bash>​tail -f /tmp/​safe_sendmail</​code>​+  * Observe log file:<​code bash>​tail -f /usr/local/​safe_sendmail/​safe_sendmail.log</​code>​ 
 +  * After the treshold number of emails script refuses to send more emails.
  
 ===== Code ===== ===== Code =====
 <code python> <code python>
 #​!/​usr/​bin/​env python #​!/​usr/​bin/​env python
 + 
 import os import os
 import sqlite3 import sqlite3
Line 33: Line 37:
 import subprocess import subprocess
 import shlex import shlex
 +import psutil 
 + 
 logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
 logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
  
 +work_dir = "/​usr/​local/​safe_sendmail"​
 + 
 # create a file handler # create a file handler
-logfile = '/tmp/safe_sendmail.log'​+logfile = os.path.join(work_dir, ​'​safe_sendmail.log'​)
 handler = logging.FileHandler(logfile) handler = logging.FileHandler(logfile)
 handler.setLevel(logging.DEBUG) handler.setLevel(logging.DEBUG)
Line 44: Line 51:
 handler.setFormatter(formatter) handler.setFormatter(formatter)
 logger.addHandler(handler) logger.addHandler(handler)
 + 
 sendmail_bin = '/​usr/​sbin/​sendmail -t -i' sendmail_bin = '/​usr/​sbin/​sendmail -t -i'
-db_file = '/tmp/safe_sendmail.db'​ +db_file = os.path.join(work_dir, ​'​safe_sendmail.db'​) 
 + 
 # Number of mails in given amount of minutes to stop the sending # Number of mails in given amount of minutes to stop the sending
 threshold = 200 threshold = 200
 time_scope = 60 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(): def check_db():
     with sqlite3.connect(db_file) as conn:     with sqlite3.connect(db_file) as conn:
Line 59: Line 72:
         is_table = len(c.fetchall())         is_table = len(c.fetchall())
         if is_table == 0:         if is_table == 0:
-            c.execute("​CREATE TABLE mails (date text, address_to text)"​)+            c.execute("​CREATE TABLE mails (date text, address_to text, spamcount integer)")
             conn.commit()             conn.commit()
     return True     return True
- +  
 + 
 def send_mail(mail):​ def send_mail(mail):​
     logger.debug('​Sending mail!\n%s'​ % mail)     logger.debug('​Sending mail!\n%s'​ % mail)
Line 77: Line 90:
         logger.exception("​Unable to send mail")         logger.exception("​Unable to send mail")
         return False         return False
- +  
 + 
 if __name__ == "​__main__":​ if __name__ == "​__main__":​
 +    parent_process = psutil.Process(os.getppid())
 +    grandparent_process = psutil.Process(parent_process.ppid())
     check_db()     check_db()
     mail = sys.stdin.read()     mail = sys.stdin.read()
Line 88: Line 103:
     logger.debug("​Datetime:​ %s" % timestamp)     logger.debug("​Datetime:​ %s" % timestamp)
     past = timestamp - dt.timedelta(minutes=time_scope)     past = timestamp - dt.timedelta(minutes=time_scope)
-    ​with sqlite3.connect(db_file) as conn:+        ​with sqlite3.connect(db_file) as conn:
         c = conn.cursor()         c = conn.cursor()
-        c.execute('​INSERT INTO mails VALUES(?,?)', (timestamp, address)+        c.execute('​SELECT spamcount FROM mails ORDER BY date DESC LIMIT 1') 
-        ​conn.commit()+        ​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,))         c.execute('​SELECT COUNT(*) FROM mails where date > ?', (past,))
-        count_since_past = c.fetchone()[0]+        count_since_past = c.fetchone()[0] ​+ 1
         logger.debug("​Count since %s %d" % (timestamp, count_since_past))         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 count_since_past < threshold:
             if send_mail(mail):​             if send_mail(mail):​
Line 101: Line 121:
                 sys.exit(2)                 sys.exit(2)
         else:         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))             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)             sys.exit(1)
 +
 </​code>​ </​code>​
  
 
linux/apache/sendmail_python.1485181457.txt.gz · Last modified: 2017/01/23 15:24 by vondra