2019-09-15 02:01:53 +08:00
#!/usr/bin/python3
2019-01-29 07:11:12 +08:00
import smtplib
import os
import mysql . connector
2019-09-14 02:14:30 +08:00
from email . mime . multipart import MIMEMultipart
from email . mime . text import MIMEText
from email . utils import COMMASPACE , formatdate
2019-01-29 07:11:12 +08:00
import cgi
import jinja2
from jinja2 import Template
import json
import redis
import time
2019-02-05 17:38:28 +08:00
import html2text
2019-02-06 16:21:54 +08:00
import socket
2019-01-29 07:11:12 +08:00
while True :
try :
r = redis . StrictRedis ( host = ' redis ' , decode_responses = True , port = 6379 , db = 0 )
r . ping ( )
except Exception as ex :
2019-09-14 02:14:30 +08:00
print ( ' %s - trying again... ' % ( ex ) )
2019-01-29 07:11:12 +08:00
time . sleep ( 3 )
else :
break
time_now = int ( time . time ( ) )
2020-10-28 04:34:02 +08:00
max_score = float ( r . get ( ' Q_MAX_SCORE ' ) or " 9999.0 " )
if max_score == " " :
max_score = 9999.0
2019-01-29 07:11:12 +08:00
def query_mysql ( query , headers = True , update = False ) :
while True :
try :
2019-09-15 02:07:18 +08:00
cnx = mysql . connector . connect ( unix_socket = ' /var/run/mysqld/mysqld.sock ' , user = ' __DBUSER__ ' , passwd = ' __DBPASS__ ' , database = ' __DBNAME__ ' , charset = " utf8 " )
2019-01-29 07:11:12 +08:00
except Exception as ex :
2019-09-14 02:14:30 +08:00
print ( ' %s - trying again... ' % ( ex ) )
2019-01-29 07:11:12 +08:00
time . sleep ( 3 )
else :
break
cur = cnx . cursor ( )
cur . execute ( query )
if not update :
result = [ ]
2019-09-14 02:14:30 +08:00
columns = tuple ( [ d [ 0 ] for d in cur . description ] )
2019-01-29 07:11:12 +08:00
for row in cur :
2019-02-01 03:53:08 +08:00
if headers :
2019-09-14 02:14:30 +08:00
result . append ( dict ( list ( zip ( columns , row ) ) ) )
2019-01-29 07:11:12 +08:00
else :
result . append ( row )
cur . close ( )
cnx . close ( )
return result
else :
cnx . commit ( )
cur . close ( )
cnx . close ( )
2019-02-06 16:21:54 +08:00
def notify_rcpt ( rcpt , msg_count , quarantine_acl ) :
2020-10-28 04:34:02 +08:00
meta_query = query_mysql ( ' SELECT SHA2(CONCAT(id, qid), 256) AS qhash, id, subject, score, sender, created FROM quarantine WHERE notified = 0 AND rcpt = " %s " AND score < %f ' % ( rcpt , max_score ) )
2019-01-29 07:11:12 +08:00
if r . get ( ' Q_HTML ' ) :
try :
template = Template ( r . get ( ' Q_HTML ' ) )
except :
2019-09-14 02:14:30 +08:00
print ( " Error: Cannot parse quarantine template, falling back to default template. " )
2019-02-01 05:18:32 +08:00
with open ( ' /templates/quarantine.tpl ' ) as file_ :
template = Template ( file_ . read ( ) )
2019-01-29 07:11:12 +08:00
else :
with open ( ' /templates/quarantine.tpl ' ) as file_ :
template = Template ( file_ . read ( ) )
2020-05-04 13:49:30 +08:00
html = template . render ( meta = meta_query , username = rcpt , counter = msg_count , hostname = socket . gethostname ( ) , quarantine_acl = quarantine_acl )
2019-02-05 17:38:28 +08:00
text = html2text . html2text ( html )
2019-01-29 07:11:12 +08:00
count = 0
while count < 15 :
2020-06-04 17:37:43 +08:00
count + = 1
2019-01-29 07:11:12 +08:00
try :
2019-02-01 05:18:32 +08:00
server = smtplib . SMTP ( ' postfix ' , 590 , ' quarantine ' )
2019-01-29 07:11:12 +08:00
server . ehlo ( )
msg = MIMEMultipart ( ' alternative ' )
2019-09-15 02:48:13 +08:00
msg_from = r . get ( ' Q_SENDER ' ) or " quarantine@localhost "
# Remove non-ascii chars from field
msg [ ' From ' ] = ' ' . join ( [ i if ord ( i ) < 128 else ' ' for i in msg_from ] )
2019-01-29 07:11:12 +08:00
msg [ ' Subject ' ] = r . get ( ' Q_SUBJ ' ) or " Spam Quarantine Notification "
msg [ ' Date ' ] = formatdate ( localtime = True )
2019-02-01 04:45:58 +08:00
text_part = MIMEText ( text , ' plain ' , ' utf-8 ' )
html_part = MIMEText ( html , ' html ' , ' utf-8 ' )
2019-01-29 07:11:12 +08:00
msg . attach ( text_part )
msg . attach ( html_part )
msg [ ' To ' ] = str ( rcpt )
2020-01-29 17:30:06 +08:00
bcc = r . get ( ' Q_BCC ' ) or " "
2020-07-05 01:31:44 +08:00
redirect = r . get ( ' Q_REDIRECT ' ) or " "
2019-01-29 07:11:12 +08:00
text = msg . as_string ( )
2020-05-27 02:03:40 +08:00
if bcc == ' ' :
2020-07-05 01:31:44 +08:00
if redirect == ' ' :
server . sendmail ( msg [ ' From ' ] , str ( rcpt ) , text )
else :
server . sendmail ( msg [ ' From ' ] , str ( redirect ) , text )
2020-05-27 02:03:40 +08:00
else :
2020-07-05 01:31:44 +08:00
if redirect == ' ' :
server . sendmail ( msg [ ' From ' ] , [ str ( rcpt ) ] + [ str ( bcc ) ] , text )
else :
server . sendmail ( msg [ ' From ' ] , [ str ( redirect ) ] + [ str ( bcc ) ] , text )
2019-01-29 07:11:12 +08:00
server . quit ( )
for res in meta_query :
2020-10-28 04:34:02 +08:00
query_mysql ( ' UPDATE quarantine SET notified = 1 WHERE id = " %d " ' % ( res [ ' id ' ] ) , update = True )
2019-01-29 07:11:12 +08:00
r . hset ( ' Q_LAST_NOTIFIED ' , record [ ' rcpt ' ] , time_now )
break
except Exception as ex :
2019-05-17 01:44:14 +08:00
server . quit ( )
2019-09-14 02:14:30 +08:00
print ( ' %s ' % ( ex ) )
2019-01-29 07:11:12 +08:00
time . sleep ( 3 )
2020-10-28 04:34:02 +08:00
records = query_mysql ( ' SELECT IFNULL(user_acl.quarantine, 0) AS quarantine_acl, count(id) AS counter, rcpt FROM quarantine LEFT OUTER JOIN user_acl ON user_acl.username = rcpt WHERE notified = 0 AND score < %f AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt ' % ( max_score ) )
2019-01-29 07:11:12 +08:00
for record in records :
2019-02-05 07:00:22 +08:00
attrs = ' '
attrs_json = ' '
try :
last_notification = int ( r . hget ( ' Q_LAST_NOTIFIED ' , record [ ' rcpt ' ] ) )
if last_notification > time_now :
2019-09-14 02:14:30 +08:00
print ( ' Last notification is > time now, assuming never ' )
2019-02-05 07:00:22 +08:00
last_notification = 0
except Exception as ex :
2019-09-14 02:14:30 +08:00
print ( ' Could not determine last notification for %s , assuming never ' % ( record [ ' rcpt ' ] ) )
2019-02-05 07:00:22 +08:00
last_notification = 0
2019-01-29 07:11:12 +08:00
attrs_json = query_mysql ( ' SELECT attributes FROM mailbox WHERE username = " %s " ' % ( record [ ' rcpt ' ] ) )
2019-11-09 17:01:43 +08:00
attrs = attrs_json [ 0 ] [ ' attributes ' ]
if isinstance ( attrs , str ) :
# if attr is str then just load it
attrs = json . loads ( attrs )
else :
# if it's bytes then decode and load it
attrs = json . loads ( attrs . decode ( ' utf-8 ' ) )
2019-02-05 07:00:22 +08:00
if attrs [ ' quarantine_notification ' ] not in ( ' hourly ' , ' daily ' , ' weekly ' , ' never ' ) :
2019-09-14 02:14:30 +08:00
print ( ' Abnormal quarantine_notification value ' )
2019-02-05 07:00:22 +08:00
continue
2019-01-29 07:11:12 +08:00
if attrs [ ' quarantine_notification ' ] == ' hourly ' :
2019-01-29 19:13:26 +08:00
if last_notification == 0 or ( last_notification + 3600 ) < time_now :
2020-10-28 04:34:02 +08:00
print ( " Notifying %s : Considering %d new items in quarantine " % ( record [ ' rcpt ' ] , record [ ' counter ' ] ) )
2019-02-06 16:21:54 +08:00
notify_rcpt ( record [ ' rcpt ' ] , record [ ' counter ' ] , record [ ' quarantine_acl ' ] )
2019-01-29 07:11:12 +08:00
elif attrs [ ' quarantine_notification ' ] == ' daily ' :
2019-01-29 19:13:26 +08:00
if last_notification == 0 or ( last_notification + 86400 ) < time_now :
2020-10-28 04:34:02 +08:00
print ( " Notifying %s : Considering %d new items in quarantine " % ( record [ ' rcpt ' ] , record [ ' counter ' ] ) )
2019-02-06 16:21:54 +08:00
notify_rcpt ( record [ ' rcpt ' ] , record [ ' counter ' ] , record [ ' quarantine_acl ' ] )
2019-01-29 07:11:12 +08:00
elif attrs [ ' quarantine_notification ' ] == ' weekly ' :
2019-01-29 19:13:26 +08:00
if last_notification == 0 or ( last_notification + 604800 ) < time_now :
2020-10-28 04:34:02 +08:00
print ( " Notifying %s : Considering %d new items in quarantine " % ( record [ ' rcpt ' ] , record [ ' counter ' ] ) )
2019-02-06 16:21:54 +08:00
notify_rcpt ( record [ ' rcpt ' ] , record [ ' counter ' ] , record [ ' quarantine_acl ' ] )