From 4d93d33e346a7465b3e23173c37161a4e540118d Mon Sep 17 00:00:00 2001 From: Dominik Chilla Date: Mon, 14 Jan 2019 02:01:25 +0100 Subject: [PATCH] Bounce a QuarMail --- app/Entities.py | 5 +- app/Gulag.py | 37 ++++++++------ app/GulagDB.py | 2 +- app/GulagMailrelay.py | 116 ++++++++++++++++++++++++++++++++++++++++++ app/GulagUtils.py | 17 ------- app/gulag_server.py | 6 ++- 6 files changed, 148 insertions(+), 35 deletions(-) create mode 100644 app/GulagMailrelay.py diff --git a/app/Entities.py b/app/Entities.py index 4317550..ba92d5d 100644 --- a/app/Entities.py +++ b/app/Entities.py @@ -38,7 +38,7 @@ class Mailrelay: self.smtp_user = mr_ref['smtp_user'] if 'smtp_pass' not in mr_ref: raise MailrelayException("'smtp_pass' is mandatory!") - self.smtp_pass = mb_ref['imap_pass'] + self.smtp_pass = mr_ref['smtp_pass'] if 'comment' in mr_ref: self.comment = mr_ref['comment'] if 'href' in mr_ref: @@ -99,6 +99,9 @@ class Mailbox: if 'imap_separator' not in mb_ref: raise MailboxException("'imap_separator' is mandatory!") self.imap_seperator = mb_ref['imap_separator'] + if 'mailrelay_id' not in mb_ref: + raise MailboxException("'mailrelay_id' is mandatory!") + self.mailrelay_id = mb_ref['mailrelay_id'] if 'comment' in mb_ref: self.comment = mb_ref['comment'] if 'href' in mb_ref: diff --git a/app/Gulag.py b/app/Gulag.py index 82d9174..5f2b38c 100644 --- a/app/Gulag.py +++ b/app/Gulag.py @@ -4,6 +4,7 @@ from GulagDB import ( GulagDB,GulagDBException,GulagDBNotFoundException,GulagDBBadInputException ) from GulagMailbox import IMAPmailbox,IMAPmailboxException +from GulagMailrelay import GulagMailrelay,GulagMailrelayException from GulagUtils import whoami,extract_uris,extract_fqdn import ssdeep, hashlib @@ -305,16 +306,10 @@ class Gulag: raise GulagException(whoami(self) + e.message) from e imap_mb.close() # end for mailboxes - if 'rfc822_message' in args: - return { - "quarmails": qms_db, - "rfc822_messages": mailboxes - } - elif 'headers' in args: - return { - "quarmails": qms_db, - "headers": mailboxes - } + return { + "quarmails": qms_db, + "rfc822_messages": mailboxes + } def get_quarmail(self,args): qm_db = None @@ -344,8 +339,8 @@ class Gulag: qm_db['imap_uid'] ).decode("utf-8") elif 'headers' in args: - qm_db['headers'] = imap_mb.get_headers( - qmat_db['imap_uid'] + qm_db['rfc822_message'] = imap_mb.get_headers( + qm_db['imap_uid'] ) imap_mb.close() return qm_db @@ -431,16 +426,28 @@ class Gulag: def bounce_quarmail(self,args): try: + # get quarmail object with headers from mailbox quarmail = self.get_quarmail({ "quarmail_id": args['quarmail_id'], - "rfc822_message": True + "headers": True }) - # TODO: bounce quarmail headers-only to quarmail['env_from'] - # TODO: self.delete_quarmail() if arg['purge'] + # the mailbox reference holds the appropriate mailrelay_id + mailbox_ref = self.db.get_mailbox(quarmail['mailbox_id']) + logging.info(whoami(self)+"mailrelay_id: "+str(mailbox_ref['mailrelay_id'])) + mailrelay_ref = self.db.get_mailrelay(mailbox_ref['mailrelay_id']) + logging.info(whoami(self) + str(mailrelay_ref)) + mailrelay = GulagMailrelay(mailrelay_ref) + mailrelay.bounce_quarmail(quarmail) + if 'purge' in args: + self.delete_quarmail({"quarmail_id": args['quarmail_id']}) except GulagNotFoundException as e: raise GulagNotFoundException(whoami(self) + e.message) from e except GulagException as e: raise GulagException(whoami(self) + e.message) from e + except GulagDBNotFoundException as e: + raise GulagNotFoundException(whoami(self) + e.message) from e + except GulagMailrelayException as e: + raise GulagException(whoami(self) + e.message) from e def forward_quarmail(self,args): try: diff --git a/app/GulagDB.py b/app/GulagDB.py index f61a33f..0a44575 100644 --- a/app/GulagDB.py +++ b/app/GulagDB.py @@ -179,7 +179,7 @@ class GulagDB: except mariadb.Error as e: raise GulagDBException(whoami(self) + str(e.msg)) from e - def get_mailrelay(self,mailbox_id): + def get_mailrelay(self,mailrelay_id): try: cursor = self.conn.cursor() cursor.execute( diff --git a/app/GulagMailrelay.py b/app/GulagMailrelay.py new file mode 100644 index 0000000..6e10a7b --- /dev/null +++ b/app/GulagMailrelay.py @@ -0,0 +1,116 @@ +from smtplib import ( + SMTP,SMTPRecipientsRefused,SMTPHeloError,SMTPSenderRefused, SMTPDataError, + SMTPNotSupportedError + ) +import email +from email.message import EmailMessage +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email.mime.text import MIMEText +from email.mime.message import MIMEMessage +from email.utils import formatdate +from email import policy +import time,sys +from GulagUtils import whoami + +class GulagMailrelayException(Exception): + message = None + def __init__(self,message): + self.message = str(message) + +class GulagMailrelay: + id = None + smtp_server = None + smtp_port = None + smtp_security = None + smtp_user = None + smtp_pass = None + mailrelay = None + + def __init__(self,mailrelay_ref): + self.id = mailrelay_ref['id'] + self.smtp_server = mailrelay_ref['smtp_server'] + self.smtp_port = mailrelay_ref['smtp_port'] + self.smtp_security = mailrelay_ref['smtp_security'] + self.smtp_user = mailrelay_ref['smtp_user'] + self.smtp_pass = mailrelay_ref['smtp_pass'] + + def release_quarmail(self,quarmail): + try: + # FIXME: SMTP transport security and authentication! + with SMTP(host=self.smtp_server,port=self.smtp_port) as self.mailrelay: + self.mailrelay.sendmail( + quarmail['env_from'], + quarmail['env_rcpt'], + quarmail['rfc822_message'] + ) + self.mailrelay.quit() + except (SMTPRecipientsRefused,SMTPHeloError,SMTPSenderRefused, + SMTPDataError,SMTPNotSupportedError) as e: + raise GulagMailrelayException(whoami(self) + e.message) from e + except TimeoutError as e: + raise GulagMailrelayException(whoami(self) + e.message) from e + except ConnectionRefusedError as e: + raise GulagMailrelayException(whoami(self) + e.strerror) from e + + def bounce_quarmail(self,quarmail): + msg = None + if quarmail['env_from'] == '<>': + raise GulagMailrelayException(whoami(self) + + "Unwilling to double-bounce QuarMail("+quarmail['id']+")!" + ) + try: + # multipart/report + msg = MIMEMultipart('report', boundary='GULAG-DSN-BOUNDARY') + msg['Subject'] = 'Undelivered Mail Returned to Sender' + msg['From'] = 'GULAG MAILER-DAEMON (Mail Quarantine System) <>' + msg['To'] = quarmail['env_from'] + msg['Auto-Submitted'] = 'auto-replied' + msg['Message-ID'] = '' + msg['Date'] = formatdate() + msg.preamble = 'This is a MIME-encapsulated message.\r\n' + # text/plain + nt = "This is the mail system at host TODO-GULAG-QUARANTINE.HOST\r\n\r\n" + nt += "I'm sorry to have to inform you that your message could not\r\n" + nt += "be delivered to one or more recipients. It's headers are attached " + nt += "below.\r\n\r\n" + nt += "For further assistance, please send mail to postmaster.\r\n\r\n" + nt += "If you do so, please include this problem report. You can\r\n" + nt += "delete your own text from the attached returned message.\r\n\r\n" + nt += "<"+quarmail['env_rcpt']+">: host GULAG-QUARANTINE.HOST said: 550\r\n" + nt += "Requested action not taken: DANGEROUS\r\n" + msg.attach(MIMEText(nt, 'plain')) + # message/delivery-status + dr = "Reporting-MTA: dns; GULAG-QUARANTINE.HOST\r\n" + dr += "Queue-ID: "+quarmail['mx_queue_id']+"\r\n" + dr += "Sender: rfc822; "+quarmail['env_from']+"\r\n" + dr += "Arrival-Date: "+quarmail['ctime'] + dr += "Final-Recipient: rfc822;"+quarmail['env_rcpt']+"\r\n\r\n" + dr += "Original-Recipient: rfc822;"+quarmail['env_rcpt']+"\r\n" + dr += "Action: failed\r\n" + dr += "Status: 5.0.0\r\n" + dr += "Remote-MTA: dns; GULAG-QUARANTINE.HOST\r\n" + dr += "Diagnostic-Code: smtp; 550 Requested action not taken: DANGEROUS\r\n" + dr_part = MIMEBase('message','delivery-status') + dr_part.set_payload(dr) + #dr_part.policy = policy.compat32 + #msg.attach(dr_part) + # message/rfc822 + msg.attach(MIMEMessage( + email.message_from_bytes(quarmail['rfc822_message']) + )) + except: + raise GulagMailrelayException(whoami(self) + str(sys.exc_info())) + try: + # FIXME: SMTP transport security and authentication! + with SMTP(host=self.smtp_server,port=self.smtp_port) as self.mailrelay: + self.mailrelay.sendmail('<>', quarmail['env_from'], msg.as_string()) + self.mailrelay.quit() + return True + except (SMTPRecipientsRefused,SMTPHeloError,SMTPSenderRefused, + SMTPDataError,SMTPNotSupportedError) as e: + raise GulagMailrelayException(whoami(self) + e.message) from e + except TimeoutError as e: + raise GulagMailrelayException(whoami(self) + e.message) from e + except ConnectionRefusedError as e: + raise GulagMailrelayException(whoami(self) + e.strerror) from e diff --git a/app/GulagUtils.py b/app/GulagUtils.py index 2c4a3ec..02e6eec 100644 --- a/app/GulagUtils.py +++ b/app/GulagUtils.py @@ -1,26 +1,9 @@ import sys,re,urllib from urllib.parse import urlparse -from smtplib import SMTP def whoami(obj): return type(obj).__name__ + "::" + sys._getframe(1).f_code.co_name + "(): " -def send_mail(args): - try: - # FIXME: SMTP transport 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 - print("TODO") - except TimeoutError as e: - raise Exception('xyz') from e - def extract_uris(input_text): uris = {} uri_pattern = r'(https?:\/\/[^\s<>"]+)' diff --git a/app/gulag_server.py b/app/gulag_server.py index 9feadf1..9b5411b 100755 --- a/app/gulag_server.py +++ b/app/gulag_server.py @@ -8,7 +8,7 @@ from Resources import (ResRoot,ResMailboxes, ResQuarMails,ResQuarMail,ResQuarMailAttachments, ResQuarMailAttachment,ResAttachments,ResAttachment, ResRspamd2Mailbox,ResQuarMailURIs,ResQuarMailURI, - ResMailradar2Mailbox,ResQuarMailRelease + ResMailradar2Mailbox,ResQuarMailRelease,ResQuarMailBounce ) parser = argparse.ArgumentParser() parser.add_argument('--config', required=True, help="Path to config file") @@ -44,6 +44,10 @@ try: '/api/v1/quarmails//release', resource_class_kwargs={'gulag_object': gulag} ) + api.add_resource(ResQuarMailBounce, + '/api/v1/quarmails//bounce', + resource_class_kwargs={'gulag_object': gulag} + ) api.add_resource(ResQuarMailAttachments, '/api/v1/quarmails//attachments', resource_class_kwargs={'gulag_object': gulag}