2019-09-14 20:01:53 +02:00
#!/usr/bin/python3
2019-01-29 01:11:12 +02:00
import smtplib
import os
2021-01-28 16:48:59 +02:00
import sys
2023-04-26 10:37:20 +02:00
import MySQLdb
2019-09-13 20:14:30 +02:00
from email . mime . multipart import MIMEMultipart
from email . mime . text import MIMEText
from email . utils import COMMASPACE , formatdate
2019-01-29 01:11:12 +02:00
import jinja2
from jinja2 import Template
import json
import redis
import time
2019-02-05 11:38:28 +02:00
import html2text
2019-02-06 10:21:54 +02:00
import socket
2019-01-29 01:11:12 +02:00
2021-01-28 16:48:59 +02:00
pid = str ( os . getpid ( ) )
pidfile = " /tmp/quarantine_notify.pid "
2019-01-29 01:11:12 +02:00
2021-01-28 16:48:59 +02:00
if os . path . isfile ( pidfile ) :
print ( " %s already exists, exiting " % ( pidfile ) )
sys . exit ( )
2019-01-29 01:11:12 +02:00
2021-01-28 16:48:59 +02:00
pid = str ( os . getpid ( ) )
f = open ( pidfile , ' w ' )
f . write ( pid )
f . close ( )
try :
2020-10-27 22:34:02 +02:00
2019-01-29 01:11:12 +02:00
while True :
try :
2021-01-28 16:48:59 +02:00
r = redis . StrictRedis ( host = ' redis ' , decode_responses = True , port = 6379 , db = 0 )
r . ping ( )
2019-01-29 01:11:12 +02:00
except Exception as ex :
2019-09-13 20:14:30 +02:00
print ( ' %s - trying again... ' % ( ex ) )
2019-01-29 01:11:12 +02:00
time . sleep ( 3 )
else :
break
2021-01-28 16:48:59 +02:00
time_now = int ( time . time ( ) )
2021-04-16 20:38:09 +02:00
mailcow_hostname = os . environ . get ( ' MAILCOW_HOSTNAME ' )
2021-01-28 16:48:59 +02:00
max_score = float ( r . get ( ' Q_MAX_SCORE ' ) or " 9999.0 " )
if max_score == " " :
max_score = 9999.0
def query_mysql ( query , headers = True , update = False ) :
while True :
try :
2023-04-26 10:37:20 +02:00
cnx = MySQLdb . connect ( user = os . environ . get ( ' DBUSER ' ) , password = os . environ . get ( ' DBPASS ' ) , database = os . environ . get ( ' DBNAME ' ) , charset = " utf8mb4 " , collation = " utf8mb4_general_ci " )
2021-01-28 16:48:59 +02:00
except Exception as ex :
print ( ' %s - trying again... ' % ( ex ) )
time . sleep ( 3 )
2019-01-29 01:11:12 +02:00
else :
2021-01-28 16:48:59 +02:00
break
cur = cnx . cursor ( )
cur . execute ( query )
if not update :
result = [ ]
columns = tuple ( [ d [ 0 ] for d in cur . description ] )
for row in cur :
if headers :
result . append ( dict ( list ( zip ( columns , row ) ) ) )
else :
result . append ( row )
cur . close ( )
cnx . close ( )
return result
else :
cnx . commit ( )
cur . close ( )
cnx . close ( )
2019-01-29 01:11:12 +02:00
2021-01-28 16:48:59 +02:00
def notify_rcpt ( rcpt , msg_count , quarantine_acl , category ) :
if category == " add_header " : category = " add header "
meta_query = query_mysql ( ' SELECT SHA2(CONCAT(id, qid), 256) AS qhash, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = " %s " AND score < %f AND (action = " %s " OR " all " = " %s " ) ' % ( rcpt , max_score , category , category ) )
print ( " %s : %d of %d messages qualify for notification " % ( rcpt , len ( meta_query ) , msg_count ) )
if len ( meta_query ) == 0 :
return
msg_count = len ( meta_query )
if r . get ( ' Q_HTML ' ) :
try :
template = Template ( r . get ( ' Q_HTML ' ) )
except :
print ( " Error: Cannot parse quarantine template, falling back to default template. " )
with open ( ' /templates/quarantine.tpl ' ) as file_ :
template = Template ( file_ . read ( ) )
else :
2019-01-31 23:18:32 +02:00
with open ( ' /templates/quarantine.tpl ' ) as file_ :
template = Template ( file_ . read ( ) )
2021-01-28 16:48:59 +02:00
html = template . render ( meta = meta_query , username = rcpt , counter = msg_count , hostname = mailcow_hostname , quarantine_acl = quarantine_acl )
text = html2text . html2text ( html )
count = 0
while count < 15 :
count + = 1
try :
server = smtplib . SMTP ( ' postfix ' , 590 , ' quarantine ' )
server . ehlo ( )
msg = MIMEMultipart ( ' alternative ' )
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 ] )
msg [ ' Subject ' ] = r . get ( ' Q_SUBJ ' ) or " Spam Quarantine Notification "
msg [ ' Date ' ] = formatdate ( localtime = True )
text_part = MIMEText ( text , ' plain ' , ' utf-8 ' )
html_part = MIMEText ( html , ' html ' , ' utf-8 ' )
msg . attach ( text_part )
msg . attach ( html_part )
msg [ ' To ' ] = str ( rcpt )
bcc = r . get ( ' Q_BCC ' ) or " "
redirect = r . get ( ' Q_REDIRECT ' ) or " "
text = msg . as_string ( )
if bcc == ' ' :
if redirect == ' ' :
server . sendmail ( msg [ ' From ' ] , str ( rcpt ) , text )
else :
server . sendmail ( msg [ ' From ' ] , str ( redirect ) , text )
2020-07-04 19:31:44 +02:00
else :
2021-01-28 16:48:59 +02:00
if redirect == ' ' :
server . sendmail ( msg [ ' From ' ] , [ str ( rcpt ) ] + [ str ( bcc ) ] , text )
else :
server . sendmail ( msg [ ' From ' ] , [ str ( redirect ) ] + [ str ( bcc ) ] , text )
server . quit ( )
for res in meta_query :
query_mysql ( ' UPDATE quarantine SET notified = 1 WHERE id = " %d " ' % ( res [ ' id ' ] ) , update = True )
r . hset ( ' Q_LAST_NOTIFIED ' , record [ ' rcpt ' ] , time_now )
break
except Exception as ex :
server . quit ( )
print ( ' %s ' % ( ex ) )
time . sleep ( 3 )
2019-01-29 01:11:12 +02:00
2021-01-28 16:48:59 +02: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 01:11:12 +02:00
2021-01-28 16:48:59 +02:00
for record in records :
attrs = ' '
attrs_json = ' '
time_trans = {
" hourly " : 3600 ,
" daily " : 86400 ,
" weekly " : 604800
}
try :
last_notification = int ( r . hget ( ' Q_LAST_NOTIFIED ' , record [ ' rcpt ' ] ) )
if last_notification > time_now :
print ( ' Last notification is > time now, assuming never ' )
last_notification = 0
except Exception as ex :
print ( ' Could not determine last notification for %s , assuming never ' % ( record [ ' rcpt ' ] ) )
2019-02-05 01:00:22 +02:00
last_notification = 0
2021-01-28 16:48:59 +02:00
attrs_json = query_mysql ( ' SELECT attributes FROM mailbox WHERE username = " %s " ' % ( record [ ' rcpt ' ] ) )
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 ' ) )
if attrs [ ' quarantine_notification ' ] not in ( ' hourly ' , ' daily ' , ' weekly ' ) :
continue
2022-03-02 12:17:08 +02:00
if last_notification == 0 or ( last_notification + time_trans [ attrs [ ' quarantine_notification ' ] ] ) < = time_now :
2021-01-28 16:48:59 +02:00
print ( " Notifying %s : Considering %d new items in quarantine (policy: %s ) " % ( record [ ' rcpt ' ] , record [ ' counter ' ] , attrs [ ' quarantine_notification ' ] ) )
notify_rcpt ( record [ ' rcpt ' ] , record [ ' counter ' ] , record [ ' quarantine_acl ' ] , attrs [ ' quarantine_category ' ] )
finally :
2022-09-19 19:29:55 +02:00
os . unlink ( pidfile )