From 05939e8732496940a0758cd33aa24ef22fd99598 Mon Sep 17 00:00:00 2001 From: Dominik Chilla Date: Wed, 21 Nov 2018 01:30:30 +0100 Subject: [PATCH] RSPAMD HTTP2IMAP relay --- app/Gulag.py | 74 ++++++++++++++++++++++++++++++--------------- app/GulagMailbox.py | 21 ++++++++++--- app/Resources.py | 4 +-- 3 files changed, 68 insertions(+), 31 deletions(-) diff --git a/app/Gulag.py b/app/Gulag.py index af1315a..112bd24 100644 --- a/app/Gulag.py +++ b/app/Gulag.py @@ -35,9 +35,9 @@ class Gulag: except GulagDBException as 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): - # Alle Mailboxes durchiterieren und die Meta-Infos aller neuen (unseen) - # Nachrichten in die Datenbank importieren for mailbox in self.db.get_mailboxes(): imap_mb = None try: @@ -81,14 +81,21 @@ class Gulag: except: pass 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 = r5321_rcpts.replace(" ", "") + r5321_rcpts = r5321_rcpts.replace("<", "") + r5321_rcpts = r5321_rcpts.replace(">", "") # Pro Envelope-RCPT einen Eintrag in die DB schreiben. # Die E-Mail im IMAP-Backend existiert jedoch nur ein Mal und wird # über die mailbox_id sowie die imap_uid mehrfach referenziert. for r5321_rcpt in r5321_rcpts.split(","): 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_msgid': msg_id, 'hdr_date': date, 'cf_meta': 'cf_meta', 'mailbox_id': 'quarantine@zwackl.de', 'imap_uid': uid, 'msg_size': msg_size @@ -142,7 +149,7 @@ class Gulag: except GulagDBException as e: raise GulagException("GulagDBException: " + e.message) from e - def rspamd_http2smtp(self,mailbox_id): + def rspamd_http2imap(self,mailbox_id): mailbox = None try: mailbox = self.db.get_mailbox(mailbox_id) @@ -151,31 +158,50 @@ class Gulag: if(request.headers.get('X-Rspamd-From') == None): raise GulagException("Missing Rspamd-specific headers (e.g. X-Rspamd-From)!") - # recompose rejected mail that will be sent to quarantine mailbox - #FIXME: print("mx_queue_id: " + request.headers.get('X-Rspamd-Qid')) msg = None try: - msg = email.message_from_string(request.get_data(as_text=True)) - rcpts_hdr = str(request.headers.get('X-Rspamd-Rcpt')) - # FIXME: special chars []" rausstrippen! -> JSON!!! - print("RCPTs neu: " + rcpts_hdr) - msg.add_header("X-Envelope-To-Blocked", rcpts_hdr) - msg.add_header("X-Spam-Status", request.headers.get('X-Rspamd-Symbols')) -# except email.errors.* as e: + rcpts_hdr = "" + for rcpt in json.loads(str(request.headers.get('X-Rspamd-Rcpt'))): + if(len(rcpts_hdr) > 0): + rcpts_hdr = rcpts_hdr + "," + rcpt + else: + rcpts_hdr = rcpt + 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: raise GulagException(str(sys.exc_info())) + # Use IMAP´s APPEND command to store the message into mailbox + imap_mb = None try: - with SMTP(host=mailbox['smtp_server'],port=mailbox['smtp_port']) as smtp: - try: - smtp.sendmail( - request.headers.get('X-Rspamd-From'), - mailbox_id, - msg.as_string() - ) - except (SMTPRecipientsRefused,SMTPHeloError,SMTPSenderRefused,SMTPDataError) as e: - raise GulagException(str(e)) from e - except TimeoutError as e: - raise GulagException(str(e)) from e + imap_mb = IMAPmailbox( + mailbox['imap_server'], + mailbox['imap_user'], + mailbox['imap_pass'], + mailbox['imap_mailbox'] + ) + imap_mb.append_message(msg) + except IMAPmailboxException as e: + raise GulagException(e.message) from e +# try: +# 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 diff --git a/app/GulagMailbox.py b/app/GulagMailbox.py index eb203c6..f8f29d6 100644 --- a/app/GulagMailbox.py +++ b/app/GulagMailbox.py @@ -1,6 +1,7 @@ import imaplib import email import email.header +import time class IMAPmailboxException(Exception): message = None @@ -71,19 +72,29 @@ class IMAPmailbox: msg = email.message_from_bytes(data[0][1]) for part in msg.walk(): if part.get_filename(): - # ist ein Attachment + # let´s define parts with filename as attachments filename = email.header.decode_header(part.get_filename()) if filename[0][1]: - # Encoded + # Encoded -> decode filename = filename[0][0].decode(filename[0][1]) else: - # Nicht encoded + # not encoded filename = filename[0][0] results.append({ 'filename': filename, - 'content-type': part.get_content_type(), # Ist das wirklich wahr? + 'content-type': part.get_content_type(), 'content': part.get_payload(decode=True) }) - # Ende if part.get_filename() + # End if part.get_filename() 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!") + diff --git a/app/Resources.py b/app/Resources.py index aa55d67..fff525e 100644 --- a/app/Resources.py +++ b/app/Resources.py @@ -64,9 +64,9 @@ class ResAttachment(GulagResource): class ResRSPAMDImporter(GulagResource): def post(self,mailbox_id): try: - self.gulag.rspamd_http2smtp(mailbox_id) + self.gulag.rspamd_http2imap(mailbox_id) # TODO: Response mit Location-Header? - return {"resource: ": "HTTP2SMTP for RSPAMD"} + return {"resource: ": "HTTP2IMAP for RSPAMD"} except GulagException as e: abort(400, message=e.message)