RSPAMD HTTP2IMAP relay

This commit is contained in:
Dominik Chilla 2018-11-21 01:30:30 +01:00
parent 71d6b0b200
commit 05939e8732
3 changed files with 68 additions and 31 deletions

View File

@ -35,9 +35,9 @@ class Gulag:
except GulagDBException as e: except GulagDBException as e:
raise GulagException(e.message) from e raise GulagException(e.message) from e
# Iterate through all mailboxes, extract metadata
# from all unseen mails and import them into database
def import_quarmails(self): def import_quarmails(self):
# Alle Mailboxes durchiterieren und die Meta-Infos aller neuen (unseen)
# Nachrichten in die Datenbank importieren
for mailbox in self.db.get_mailboxes(): for mailbox in self.db.get_mailboxes():
imap_mb = None imap_mb = None
try: try:
@ -81,14 +81,21 @@ class Gulag:
except: except:
pass pass
x_spam_status = email.header.decode_header(msg['X-Spam-Status'])[0][0] x_spam_status = email.header.decode_header(msg['X-Spam-Status'])[0][0]
mx_queue_id = "n.a."
try:
mx_queue_id = email.header.decode_header(msg['X-Spam-QID'])[0][0]
except:
pass
r5321_rcpts = str(r5321_rcpts).lower() r5321_rcpts = str(r5321_rcpts).lower()
r5321_rcpts = r5321_rcpts.replace(" ", "") r5321_rcpts = r5321_rcpts.replace(" ", "")
r5321_rcpts = r5321_rcpts.replace("<", "")
r5321_rcpts = r5321_rcpts.replace(">", "")
# Pro Envelope-RCPT einen Eintrag in die DB schreiben. # Pro Envelope-RCPT einen Eintrag in die DB schreiben.
# Die E-Mail im IMAP-Backend existiert jedoch nur ein Mal und wird # Die E-Mail im IMAP-Backend existiert jedoch nur ein Mal und wird
# über die mailbox_id sowie die imap_uid mehrfach referenziert. # über die mailbox_id sowie die imap_uid mehrfach referenziert.
for r5321_rcpt in r5321_rcpts.split(","): for r5321_rcpt in r5321_rcpts.split(","):
quarmail_id = self.db.add_quarmail({ quarmail_id = self.db.add_quarmail({
'mx_queue_id': 'queue_id', 'env_from': r5321_from, 'env_rcpt': r5321_rcpt, 'mx_queue_id': mx_queue_id, 'env_from': r5321_from, 'env_rcpt': r5321_rcpt,
'hdr_cf': x_spam_status, 'hdr_from': r5322_from, 'hdr_subject': subject, 'hdr_cf': x_spam_status, 'hdr_from': r5322_from, 'hdr_subject': subject,
'hdr_msgid': msg_id, 'hdr_date': date, 'cf_meta': 'cf_meta', 'hdr_msgid': msg_id, 'hdr_date': date, 'cf_meta': 'cf_meta',
'mailbox_id': 'quarantine@zwackl.de', 'imap_uid': uid, 'msg_size': msg_size 'mailbox_id': 'quarantine@zwackl.de', 'imap_uid': uid, 'msg_size': msg_size
@ -142,7 +149,7 @@ class Gulag:
except GulagDBException as e: except GulagDBException as e:
raise GulagException("GulagDBException: " + e.message) from e raise GulagException("GulagDBException: " + e.message) from e
def rspamd_http2smtp(self,mailbox_id): def rspamd_http2imap(self,mailbox_id):
mailbox = None mailbox = None
try: try:
mailbox = self.db.get_mailbox(mailbox_id) mailbox = self.db.get_mailbox(mailbox_id)
@ -151,31 +158,50 @@ class Gulag:
if(request.headers.get('X-Rspamd-From') == None): if(request.headers.get('X-Rspamd-From') == None):
raise GulagException("Missing Rspamd-specific headers (e.g. X-Rspamd-From)!") raise GulagException("Missing Rspamd-specific headers (e.g. X-Rspamd-From)!")
# recompose rejected mail that will be sent to quarantine mailbox # recompose rejected mail that will be sent to quarantine mailbox
#FIXME: print("mx_queue_id: " + request.headers.get('X-Rspamd-Qid'))
msg = None msg = None
try: try:
msg = email.message_from_string(request.get_data(as_text=True)) rcpts_hdr = ""
rcpts_hdr = str(request.headers.get('X-Rspamd-Rcpt')) for rcpt in json.loads(str(request.headers.get('X-Rspamd-Rcpt'))):
# FIXME: special chars []" rausstrippen! -> JSON!!! if(len(rcpts_hdr) > 0):
print("RCPTs neu: " + rcpts_hdr) rcpts_hdr = rcpts_hdr + "," + rcpt
msg.add_header("X-Envelope-To-Blocked", rcpts_hdr) else:
msg.add_header("X-Spam-Status", request.headers.get('X-Rspamd-Symbols')) rcpts_hdr = rcpt
# except email.errors.* as e: msg = "Return-Path: <" + request.headers.get('X-Rspamd-From') + ">\r\n"
msg = msg + "Received: from rspamd_http2imap relay by gulag-mailbox " + mailbox_id + "\r\n"
msg = msg + "X-Envelope-To-Blocked: " + rcpts_hdr + "\r\n"
msg = msg + "X-Spam-Status: " + request.headers.get('X-Rspamd-Symbols') + "\r\n"
msg = msg + "X-Spam-QID: " + request.headers.get('X-Rspamd-Qid') + "\r\n"
msg = msg + request.get_data(as_text=True)
#FIXME: except email.errors.* as e:
except: except:
raise GulagException(str(sys.exc_info())) raise GulagException(str(sys.exc_info()))
# Use IMAP´s APPEND command to store the message into mailbox
imap_mb = None
try: try:
with SMTP(host=mailbox['smtp_server'],port=mailbox['smtp_port']) as smtp: imap_mb = IMAPmailbox(
try: mailbox['imap_server'],
smtp.sendmail( mailbox['imap_user'],
request.headers.get('X-Rspamd-From'), mailbox['imap_pass'],
mailbox_id, mailbox['imap_mailbox']
msg.as_string() )
) imap_mb.append_message(msg)
except (SMTPRecipientsRefused,SMTPHeloError,SMTPSenderRefused,SMTPDataError) as e: except IMAPmailboxException as e:
raise GulagException(str(e)) from e raise GulagException(e.message) from e
except TimeoutError as e: # try:
raise GulagException(str(e)) from e # if not mailbox['smtp_server']:
# raise GulagException("No SMTP server configured for mailbox " + mailbox_id)
# # FIXME: SMTP tranaport security and authentication!
# with SMTP(host=mailbox['smtp_server'],port=mailbox['smtp_port']) as smtp:
# try:
# smtp.sendmail(
# request.headers.get('X-Rspamd-From'),
# mailbox_id,
# msg
# )
# except (SMTPRecipientsRefused,SMTPHeloError,SMTPSenderRefused,SMTPDataError) as e:
# raise GulagException(str(e)) from e
# except TimeoutError as e:
# raise GulagException(str(e)) from e

View File

@ -1,6 +1,7 @@
import imaplib import imaplib
import email import email
import email.header import email.header
import time
class IMAPmailboxException(Exception): class IMAPmailboxException(Exception):
message = None message = None
@ -71,19 +72,29 @@ class IMAPmailbox:
msg = email.message_from_bytes(data[0][1]) msg = email.message_from_bytes(data[0][1])
for part in msg.walk(): for part in msg.walk():
if part.get_filename(): if part.get_filename():
# ist ein Attachment # let´s define parts with filename as attachments
filename = email.header.decode_header(part.get_filename()) filename = email.header.decode_header(part.get_filename())
if filename[0][1]: if filename[0][1]:
# Encoded # Encoded -> decode
filename = filename[0][0].decode(filename[0][1]) filename = filename[0][0].decode(filename[0][1])
else: else:
# Nicht encoded # not encoded
filename = filename[0][0] filename = filename[0][0]
results.append({ results.append({
'filename': filename, 'filename': filename,
'content-type': part.get_content_type(), # Ist das wirklich wahr? 'content-type': part.get_content_type(),
'content': part.get_payload(decode=True) 'content': part.get_payload(decode=True)
}) })
# Ende if part.get_filename() # End if part.get_filename()
return results return results
def append_message(self,message):
rv, data = self.mailbox.append(
self.imap_mailbox,
'UNSEEN',
imaplib.Time2Internaldate(time.time()),
str(message).encode('utf-8')
)
if rv != 'OK':
raise IMAPmailboxException("ERROR appending message!")

View File

@ -64,9 +64,9 @@ class ResAttachment(GulagResource):
class ResRSPAMDImporter(GulagResource): class ResRSPAMDImporter(GulagResource):
def post(self,mailbox_id): def post(self,mailbox_id):
try: try:
self.gulag.rspamd_http2smtp(mailbox_id) self.gulag.rspamd_http2imap(mailbox_id)
# TODO: Response mit Location-Header? # TODO: Response mit Location-Header?
return {"resource: ": "HTTP2SMTP for RSPAMD"} return {"resource: ": "HTTP2IMAP for RSPAMD"}
except GulagException as e: except GulagException as e:
abort(400, message=e.message) abort(400, message=e.message)