diff --git a/app/Entities.py b/app/Entities.py index 13f37a2..ce3c424 100644 --- a/app/Entities.py +++ b/app/Entities.py @@ -1,5 +1,149 @@ +import re + +class MailboxException(Exception): + message = None + def __init__(self,message): + self.message = message + class Mailbox: + email_address = None + name = None + imap_server = None + imap_security = None + imap_user = None + imap_pass = None + imap_mailbox = None + imap_mailbox_fp = None + imap_separator = None + comment = None + href = None + + def __init__(self,mb_ref): + if 'email_address' not in mb_ref: + raise MailboxException("'email_address' is mandatory!") + self.email_address = mb_ref['email_address'] + if 'name' not in mb_ref: + raise MailboxException("'name' is mandatory!") + self.name = mb_ref['name'] + if 'imap_server' not in mb_ref: + raise MailboxException("'imap_server' is mandatory!") + self.imap_server = mb_ref['imap_server'] + if 'imap_security' in mb_ref: + if re.match("^(plain|starttls|tls)$", mb_ref['imap_security']) is not None: + self.imap_security = mb_ref['imap_security'] + else: + raise MailboxException('imap_security: {} is invalid! '+ + 'Valid values: plain,starttls,tls'.format(mb_ref['imap_security']) + ) + else: + raise MailboxException("'imap_security' is a mandatory!") + if 'imap_user' not in mb_ref: + raise MailboxException("'imap_user' is mandatory!") + self.imap_user = mb_ref['imap_user'] + if 'imap_pass' not in mb_ref: + raise MailboxException("'imap_pass' is mandatory!") + self.imap_pass = mb_ref['imap_pass'] + if 'imap_mailbox' not in mb_ref: + raise MailboxException("'imap_mailbox' is mandatory!") + self.imap_mailbox = mb_ref['imap_mailbox'] + if 'imap_mailbox_fp' not in mb_ref: + raise MailboxException("'imap_mailbox_fp' is mandatory!") + self.imap_mailbox_fp = mb_ref['imap_mailbox_fp'] + if 'imap_separator' not in mb_ref: + raise MailboxException("'imap_separator' is mandatory!") + self.imap_seperator = mb_ref['imap_separator'] + if 'comment' in mb_ref: + self.comment = mb_ref['comment'] + if 'href' in mb_ref: + self.href = mb_ref['href'] + +class QuarMailException(Exception): + message = None + def __init__(self,message): + self.message = message class QuarMail: + id = None + ctime = None + mx_queue_id = None + env_from = None + env_rcpt = None + hdr_cf = None + hdr_from = None + hdr_subject = None + hdr_msgid = None + hdr_date = None + cf_meta = None + mailbox_id = None + imap_uid = None + msg_size = None + href = None + + def __init__(self,qm_ref): + if 'id' not in qm_ref: + raise QuarMailException("'id' is mandatory!") + self.id = qm_ref['id'] + if 'ctime' not in qm_ref: + raise QuarMailException("'ctime' is mandatory!") + self.ctime = qm_ref['ctime'] + if 'mx_queue_id' not in qm_ref: + raise QuarMailException("'mx_queue_id' is mandatory!") + self.mx_queue_id = qm_ref['mx_queue_id'] + if 'env_from' not in qm_ref: + raise QuarMailException("'env_from' is mandatory!") + self.env_from = qm_ref['env_from'] + if 'env_rcpt' not in qm_ref: + raise QuarMailException("'env_rcpt' is mandatory!") + self.env_rcpt = qm_ref['env_rcpt'] + if 'hdr_cf' in qm_ref: + self.hdr_cf = qm_ref['hdr_cf'] + if 'hdr_from' in qm_ref: + self.hdr_from = qm_ref['hdr_from'] + if 'hdr_subject' in qm_ref: + self.hdr_subject = qm_ref['hdr_subject'] + if 'hdr_msgid' in qm_ref: + self.hdr_msgid = qm_ref['hdr_msgid'] + if 'hdr_date' in qm_ref: + self.hdr_date = qm_ref['hdr_date'] + if 'cf_meta' in qm_ref: + self.cf_meta = qm_ref['cf_meta'] + if 'mailbox_id' not in qm_ref: + raise QuarMailException("'mailbox_id' is mandatory!") + self.mailbox_id = qm_ref['mailbox_id'] + if 'imap_uid' not in qm_ref: + raise QuarMailException("'imap_uid' is mandatory!") + self.imap_uid = qm_ref['imap_uid'] + if 'msg_size' not in qm_ref: + raise QuarMailException("'msg_size' is mandatory!") + self.msg_size = qm_ref['msg_size'] + if 'href' in qm_ref: + self.href = qm_ref['href'] + + +class AttachmentException(Exception): + message = None + def __init__(self,message): + self.message = message class Attachment: + id = None + filename = None + content_type = None + comment = None + href = None + + def __init__(self,at_ref): + if 'id' not in at_ref: + raise AttachmentException("'id' is mandatory!") + self.id = at_ref['id'] + if 'filename' not in at_ref: + raise AttachmentException("'filename' is mandatory!") + self.filename = at_ref['filename'] + if 'content_type' not in at_ref: + raise AttachmentException("'content_type' is mandatory!") + self.content_type = at_ref['content_type'] + if 'comment' in at_ref: + self.comment = at_ref['comment'] + if 'href' in at_ref: + self.href = at_ref['href'] + diff --git a/app/Gulag.py b/app/Gulag.py index 0a64e5c..c91d7d1 100644 --- a/app/Gulag.py +++ b/app/Gulag.py @@ -27,7 +27,8 @@ class Gulag: self.config['db']['server'], self.config['db']['user'], self.config['db']['password'], - self.config['db']['name'] + self.config['db']['name'], + self.config['uri_prefixes'] ) except GulagDBException as e: raise GulagException(e.message) from e @@ -54,8 +55,18 @@ class Gulag: msg = unseen['msg'] msg_size = len(str(msg)) r5321_from = email.header.decode_header(msg['Return-Path'])[0][0] - r5321_rcpts = email.header.decode_header(msg['X-Envelope-To-Blocked'])[0][0] - r5322_from = email.header.decode_header(msg['From'])[0][0] + r5321_rcpts = None + try: + r5321_rcpts = email.header.decode_header(msg['X-Envelope-To-Blocked'])[0][0] + except: + print("Failed to extract envelope recipients! Skipping mail") + continue + r5322_from = None + try: + r5322_from = email.header.decode_header(msg['From'])[0][0] + except: + print("Failed to extract from header! Skipping mail") + continue subject = email.header.decode_header(msg['Subject'])[0][0] msg_id = None try: @@ -116,4 +127,16 @@ class Gulag: self.db.get_deprecated_mails(self.config['cleaner']['retention_period']) )) ) + + def get_mailboxes(self): + try: + return self.db.get_mailboxes() + except GulagDBException as e: + raise GulagException("GulagDBException: " + e.message) from e + + def get_quarmails(self): + try: + return self.db.get_quarmails() + except GulagDBException as e: + raise GulagException("GulagDBException: " + e.message) from e diff --git a/app/GulagDB.py b/app/GulagDB.py index 4d7be11..6a9bc74 100644 --- a/app/GulagDB.py +++ b/app/GulagDB.py @@ -1,4 +1,5 @@ import mysql.connector as mariadb +from Entities import Mailbox,MailboxException,QuarMail,QuarMailException,Attachment,AttachmentException class GulagDBException(Exception): message = None @@ -7,15 +8,18 @@ class GulagDBException(Exception): class GulagDB: conn = None + uri_prefixes = None - def __init__(self, server, user, password, name): + def __init__(self, server, user, password, name, uri_prefixes): try: self.conn = mariadb.connect( host=server, user=user, password=password, - database=name + database=name, + autocommit=True ) + self.uri_prefixes = uri_prefixes except mariadb.Error as e: raise GulagDBException(e) from e @@ -35,7 +39,12 @@ class GulagDB: dict = {} for (name, value) in zip(desc, tuple): dict[name[0]] = value - results.append(dict) + dict['href'] = self.uri_prefixes['mailboxes'] + dict['email_address'] + try: + results.append(Mailbox(dict).__dict__) + except MailboxException as e: + print("MailboxException: " + e.message) + continue return results except mariadb.Error as e: raise GulagDBException(e) from e @@ -55,8 +64,9 @@ class GulagDB: quarmail['mailbox_id'],quarmail['imap_uid'],quarmail['msg_size'] ) ) - self.conn.commit() - return cursor.lastrowid + id = cursor.lastrowid + cursor.close() + return id except mariadb.Error as e: raise GulagDBException(e) from e @@ -64,28 +74,35 @@ class GulagDB: try: cursor = self.conn.cursor() cursor.execute("delete from QuarMails where id=%s;", (id)) - self.conn.commit() - return cursor.lastrowid + cursor.close() + return True except mariadb.Error as e: raise GulagDBException(e) from e - def get_quarmails(self, mailbox_id): +# def get_quarmails(self,mailbox_id): + def get_quarmails(self): try: cursor = self.conn.cursor() - cursor.execute( - "select * from QuarMails where mailbox_id='%s';", - (mailbox_id) - ) +# cursor.execute( +# "select * from QuarMails where mailbox_id='%s';", +# (mailbox_id) +# ) + cursor.execute("select * from QuarMails;") results = [] data = cursor.fetchall() if data == None: return results desc = cursor.description + cursor.close() for tuple in data: dict = {} for (name, value) in zip(desc, tuple): - dict[name[0]] = value - results.append(dict) + if(name[0] == 'ctime'): + dict[name[0]] = value.strftime('%Y-%m-%d %H:%M:%S') + else: + dict[name[0]] = value + dict['href'] = self.uri_prefixes['quarmails'] + str(dict['id']) + results.append(QuarMail(dict).__dict__) return results except mariadb.Error as e: raise GulagDBException(e) from e @@ -104,7 +121,7 @@ class GulagDB: dict = {} for (name, value) in zip(desc, tuple): dict[name[0]] = value - results.append(dict) + results.append(QuarMail(dict).__dict__) return results except mariadb.Error as e: raise GulagDBException(e) from e @@ -116,7 +133,6 @@ class GulagDB: "(filename, content_type) values (%s,%s)", (attach['filename'], attach['content_type']) ) - self.conn.commit() return cursor.lastrowid except mariadb.Error as e: raise GulagDBException(e) from e @@ -128,7 +144,6 @@ class GulagDB: "(quarmail_id, attachment_id) values (%s,%s)", (quarmail_id, attachment_id) ) - self.conn.commit() except mariadb.Error as e: raise GulagDBException(e) from e diff --git a/app/Resources.py b/app/Resources.py index dc0333a..a422d4e 100644 --- a/app/Resources.py +++ b/app/Resources.py @@ -1,6 +1,7 @@ from flask import request from flask_restful import Resource, abort -import json +from Entities import Mailbox,MailboxException,QuarMail,QuarMailException,Attachment,AttachmentException +from Gulag import GulagException class GulagResource(Resource): gulag = None @@ -33,7 +34,10 @@ class ResRoot(GulagResource): class ResMailboxes(GulagResource): def get(self): - return {"resource": "Mailboxes"} + try: + return self.gulag.get_mailboxes() + except GulagException as e: + abort(500, message=e.message) class ResMailbox(GulagResource): def get(self,id): @@ -41,7 +45,10 @@ class ResMailbox(GulagResource): class ResQuarMails(GulagResource): def get(self): - return {"resource": "QuarMails"} + try: + return self.gulag.get_quarmails() + except GulagException as e: + abort(500, message=e.message) class ResQuarMail(GulagResource): def get(self,id): diff --git a/config/config.json b/config/config.json deleted file mode 100644 index 0ef1679..0000000 --- a/config/config.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "daemon":{ - "listen_host": "127.0.0.1", - "listen_port": 5001 - }, - "trusted_proxies": { - "rprx01":[ - "172.16.100.5", "fd00:100::5" - ], - "rprx02":[ - "172.16.100.6", "fd00:100::6" - ] - }, - "api_keys": { - "HIGHLY_SECURE_API_KEY": { - "user": "GULAG APP" - } - }, - "uri_prefixes": { - "root": "https:///api/v1/", - "quarmails": "https:///api/v1/quarmails/", - "attachments": "https:///api/v1/attachments/" - }, - "db":{ - "server": "127.0.0.1", - "user": "root", - "password": "", - "name": "Gulag" - }, - "cleaner":{ - "retention_period": "12 hour", - "interval": 10 - }, - "importer":{ - "interval": 10 - } -} diff --git a/config/uwsgi.ini b/config/uwsgi.ini deleted file mode 100644 index 4ba94bb..0000000 --- a/config/uwsgi.ini +++ /dev/null @@ -1,9 +0,0 @@ -[uwsgi] -processes = 4 -cheaper = 1 -cheaper-initial = 1 -cheaper-step = 1 -python-path = /app -wsgi-file = /app/uwsgi.py -pyargv = --config /config/config.json -socket = /socket/gulag_uwsgi.sock diff --git a/db/gulag.sql b/db/gulag.sql index 44e2cfc..8a5e4c8 100644 --- a/db/gulag.sql +++ b/db/gulag.sql @@ -1,5 +1,7 @@ create database Gulag; +use Gulag; + create table Mailboxes( email_address varchar(767) not null primary key collate 'ascii_general_ci', name varchar(256) not null, @@ -9,10 +11,10 @@ create table Mailboxes( imap_pass varchar(256) not null, imap_mailbox varchar(256) not null default 'INBOX', imap_mailbox_fp varchar(256) not null default 'false-positives', - imap_separator varchar(4) not null default '/' + imap_separator varchar(4) not null default '/', comment varchar(256) default null )ENGINE = InnoDB; -insert into Mailboxes (email_address,name,imap_user,imap_pass,) +insert into Mailboxes (email_address,name,imap_user,imap_pass) values('quarantine-in@example.org','E-Mail inbound quarantine','quarantine-in','quarantine-in_secure_password'); insert into Mailboxes (email_address,name,imap_user,imap_pass) values('quarantine-out@example.org','E-Mail outbound quarantine','quarantine-out','quarantine-out_secure_password');