diff --git a/app/Entities.py b/app/Entities.py index 969f3e8..4ff6eff 100644 --- a/app/Entities.py +++ b/app/Entities.py @@ -96,6 +96,8 @@ class QuarMail: imap_uid = None msg_size = None href = None + attach_count = None + attachments = None def __init__(self,qm_ref): if 'id' not in qm_ref: @@ -136,6 +138,10 @@ class QuarMail: self.msg_size = qm_ref['msg_size'] if 'href' in qm_ref: self.href = qm_ref['href'] + if 'attach_count' in qm_ref: + self.attach_count = qm_ref['attach_count'] + if 'attachments' in qm_ref: + self.attachments = qm_ref['attachments'] class AttachmentException(Exception): diff --git a/app/Gulag.py b/app/Gulag.py index 1c5c975..931e973 100644 --- a/app/Gulag.py +++ b/app/Gulag.py @@ -39,9 +39,9 @@ class Gulag: except IMAPmailboxException as e: print(e.message) continue - quarmail_ids = [] - attachments = [] for unseen in imap_mb.get_unseen_messages(): + quarmail_ids = [] + attachments = [] uid = unseen['imap_uid'] msg = unseen['msg'] msg_size = len(str(msg)) @@ -113,13 +113,14 @@ class Gulag: attachments.append(attach_id) # Ende if part.get_filename() # Ende for msg.walk() + # QuarMails und Attachments verknüpfen + if(len(attachments) > 0): + for quarmail_id in quarmail_ids: + for attachment_id in attachments: + self.db.quarmail2attachment(str(quarmail_id), str(attachment_id)) # Ende for(unseen) imap_mb.close() - # QuarMails und Attachments verknüpfen - if(len(attachments) > 0): - for quarmail_id in quarmail_ids: - for attachment_id in attachments: - self.db.quarmail2attachment(str(quarmail_id), str(attachment_id)) + # Ende for get_mailboxes def cleanup_quarmails(self): @@ -144,13 +145,12 @@ class Gulag: def get_quarmail(self,args): qm_db = None try: - qm_db = self.db.get_quarmail({ - "id": args['id'] - }) + qm_db = self.db.get_quarmail({"id": args['id']}) except GulagDBException as e: raise GulagException("GulagDBException: " + e.message) from e if 'rfc822_message' not in args: return qm_db + # pull full RFC822 message from mailbox mailbox = None try: mailbox = self.db.get_mailbox(qm_db['mailbox_id']) @@ -164,6 +164,14 @@ class Gulag: except IMAPmailboxException as e: print(e.message) raise GulagException(e.message) from e + + def get_attachment(self,args): + at_db = None + try: + at_db = self.db.get_attachment({"id": args['id']}) + return at_db + except GulagDBException as e: + raise GulagException(e.message) from e def rspamd_http2imap(self,mailbox_id): mailbox = None @@ -171,10 +179,12 @@ class Gulag: mailbox = self.db.get_mailbox(mailbox_id) except GulagDBException as e: raise GulagException(e.message) from e - + # check if the request comes really from rspamd´s metadata_exporter + # default metadata_header prefix 'X-Rspamd' will be expected 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 + # Prepend gulag-specific headers to rejected mail + # before pushing into quarantine mailbox msg = None try: rcpts_hdr = "" @@ -184,35 +194,33 @@ class Gulag: 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: + msg += "Received: from rspamd_http2imap relay by gulag-mailbox " + mailbox_id + "\r\n" + msg += "X-Envelope-To-Blocked: " + rcpts_hdr + "\r\n" + msg += "X-Spam-Status: " + request.headers.get('X-Rspamd-Symbols') + "\r\n" + msg += "X-Spam-QID: " + request.headers.get('X-Rspamd-Qid') + "\r\n" + # append original mail + msg += request.get_data(as_text=True) except: raise GulagException(str(sys.exc_info())) - - # Use IMAP´s APPEND command to store the message into mailbox imap_mb = None try: imap_mb = IMAPmailbox(mailbox) imap_mb.append_message(msg) except IMAPmailboxException as e: raise GulagException(e.message) from e + +# def send_mail(self,args): # 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 +# # 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/GulagDB.py b/app/GulagDB.py index c453ced..af1ff3e 100644 --- a/app/GulagDB.py +++ b/app/GulagDB.py @@ -10,7 +10,6 @@ class GulagDB: conn = None uri_prefixes = None -# def __init__(self, server, user, password, name, uri_prefixes): def __init__(self, args, uri_prefixes): try: if 'unix_socket' in args: @@ -114,7 +113,11 @@ class GulagDB: def get_quarmails(self): try: cursor = self.conn.cursor() - cursor.execute("select * from QuarMails;") +# cursor.execute("select * from QuarMails;") + query = "select *,(select count(*) from QuarMail2Attachment" + query += " where QuarMails.id=QuarMail2Attachment.quarmail_id) as attach_count" + query += " from QuarMails;" + cursor.execute(query) results = [] data = cursor.fetchall() if not data: @@ -138,7 +141,10 @@ class GulagDB: try: cursor = self.conn.cursor() # TODO: build SQL query by args - query = "select * from QuarMails where id='" + args['id'] + "';" + #query = "select * from QuarMails where id='" + args['id'] + "';" + query = "select *,(select count(*) from QuarMail2Attachment" + query += " where QuarMails.id=QuarMail2Attachment.quarmail_id) as attach_count" + query += " from QuarMails where QuarMails.id="+ str(args['id']) +";" cursor.execute(query) data = cursor.fetchall() if not data: @@ -153,6 +159,10 @@ class GulagDB: else: dict[name[0]] = value dict['href'] = self.uri_prefixes['quarmails'] + str(dict['id']) + try: + dict['attachments'] = self.get_attachments_by_quarmail(args['id']) + except GulagDBException as e: + pass return QuarMail(dict).__dict__ except mariadb.Error as e: raise GulagDBException(e) from e @@ -160,7 +170,8 @@ class GulagDB: def get_deprecated_mails(self,retention_period): try: cursor = self.conn.cursor() - query = "select ctime,mailbox_id,imap_uid from QuarMails where ctime < date_sub(NOW(), INTERVAL "+ retention_period +");" + query = "select ctime,mailbox_id,imap_uid from QuarMails" + query += " where ctime < date_sub(NOW(), INTERVAL "+ str(retention_period) +");" cursor.execute(query) results = [] data = cursor.fetchall() @@ -186,7 +197,47 @@ class GulagDB: return cursor.lastrowid except mariadb.Error as e: raise GulagDBException(e) from e - + + def get_attachment(self, args): + try: + cursor = self.conn.cursor() + cursor.execute("select * from Attachments where id=" + str(args['id']) + ";") + data = cursor.fetchall() + if not data: + raise GulagDBException("Attachment("+ str(args['id']) +") does not exist!") + desc = cursor.description + tuple = data[0] + dict = {} + for (name, value) in zip(desc, tuple): + dict[name[0]] = value + dict['href'] = self.uri_prefixes['attachments'] + str(dict['id']) + return Attachment(dict).__dict__ + except mariadb.Error as e: + raise GulagDBException(e) from e + + def get_attachments_by_quarmail(self,quarmail_id): + try: + query = "select Attachments.* from QuarMail2Attachment" + query += " left join QuarMails ON QuarMails.id = QuarMail2Attachment.quarmail_id" + query += " left join Attachments ON Attachments.id = QuarMail2Attachment.attachment_id" + query += " where QuarMails.id = " + str(quarmail_id) + ";" + cursor = self.conn.cursor() + cursor.execute(query) + results = [] + data = cursor.fetchall() + if not data: + raise GulagDBException("QuarMail("+ str(quarmail_id) +") has no attachments!") + desc = cursor.description + for tuple in data: + dict = {} + for (name, value) in zip(desc, tuple): + dict[name[0]] = value + dict['href'] = self.uri_prefixes['attachments'] + str(dict['id']) + results.append(Attachment(dict).__dict__) + return results + except mariadb.Error as e: + raise GulagDBException(e) from e + def quarmail2attachment(self,quarmail_id,attachment_id): try: cursor = self.conn.cursor() diff --git a/app/Resources.py b/app/Resources.py index 882265e..3277aaf 100644 --- a/app/Resources.py +++ b/app/Resources.py @@ -65,7 +65,11 @@ class ResAttachments(GulagResource): class ResAttachment(GulagResource): def get(self,id): - return {"resource": "Attachment by ID"} + args = {"id": id} + try: + return self.gulag.get_attachment(args) + except GulagException as e: + abort(400, message=e.message) class ResRSPAMDImporter(GulagResource): def post(self,mailbox_id): diff --git a/app/gulag_server.py b/app/gulag_server.py index fc74a53..64e5258 100755 --- a/app/gulag_server.py +++ b/app/gulag_server.py @@ -6,7 +6,7 @@ from flask_restful import Api from Gulag import Gulag,GulagException from Resources import (ResRoot,ResMailboxes, ResQuarMails,ResQuarMail,ResAttachments, - ResRSPAMDImporter + ResAttachment,ResRSPAMDImporter ) parser = argparse.ArgumentParser() parser.add_argument('--config', required=True, help="Path to config file") @@ -39,6 +39,10 @@ try: '/api/v1/attachments/', resource_class_kwargs={'gulag_object': gulag} ) + api.add_resource(ResAttachment, + '/api/v1/attachments/', + resource_class_kwargs={'gulag_object': gulag} + ) api.add_resource(ResRSPAMDImporter, '/api/v1/mailboxes//rspamdimporter/', resource_class_kwargs={'gulag_object': gulag}