From 71d6b0b200cb60676788c6aaa040fd7307b62d0a Mon Sep 17 00:00:00 2001 From: Dominik Chilla Date: Sun, 18 Nov 2018 23:56:38 +0100 Subject: [PATCH] HTTP2SMTP relay for Rspamd :) --- app/Entities.py | 18 ++++++++++++++++++ app/Gulag.py | 41 ++++++++++++++++++++++++++++++++++++++++- app/GulagDB.py | 24 +++++++++++++++++++++++- app/Resources.py | 14 +++++++++++--- app/gulag_server.py | 6 +++++- db/gulag.sql | 6 ++++++ 6 files changed, 103 insertions(+), 6 deletions(-) diff --git a/app/Entities.py b/app/Entities.py index ce3c424..969f3e8 100644 --- a/app/Entities.py +++ b/app/Entities.py @@ -9,12 +9,18 @@ class Mailbox: email_address = None name = None imap_server = None + imap_port = None imap_security = None imap_user = None imap_pass = None imap_mailbox = None imap_mailbox_fp = None imap_separator = None + smtp_server = None + smtp_port = None + smtp_security = None + smtp_user = None + smtp_pass = None comment = None href = None @@ -37,6 +43,8 @@ class Mailbox: ) else: raise MailboxException("'imap_security' is a mandatory!") + if 'imap_port' in mb_ref: + self.imap_port = mb_ref['imap_port'] if 'imap_user' not in mb_ref: raise MailboxException("'imap_user' is mandatory!") self.imap_user = mb_ref['imap_user'] @@ -52,6 +60,16 @@ class Mailbox: if 'imap_separator' not in mb_ref: raise MailboxException("'imap_separator' is mandatory!") self.imap_seperator = mb_ref['imap_separator'] + if 'smtp_server' in mb_ref: + self.smtp_server = mb_ref['smtp_server'] + if 'smtp_port' in mb_ref: + self.smtp_port = mb_ref['smtp_port'] + if 'smtp_security' in mb_ref: + self.smtp_security = mb_ref['smtp_security'] + if 'smtp_user' in mb_ref: + self.smtp_user = mb_ref['smtp_user'] + if 'smtp_pass' in mb_ref: + self.smtp_pass = mb_ref['smtp_pass'] 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 c91d7d1..af1315a 100644 --- a/app/Gulag.py +++ b/app/Gulag.py @@ -1,5 +1,7 @@ import json,sys -import email,email.header +import email,email.header,email.message +from flask import request +from smtplib import SMTP from GulagDB import GulagDB,GulagDBException from GulagMailbox import IMAPmailbox,IMAPmailboxException @@ -139,4 +141,41 @@ class Gulag: return self.db.get_quarmails() except GulagDBException as e: raise GulagException("GulagDBException: " + e.message) from e + + def rspamd_http2smtp(self,mailbox_id): + mailbox = None + try: + mailbox = self.db.get_mailbox(mailbox_id) + except GulagDBException as e: + raise GulagException(e.message) from e + + 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: + except: + raise GulagException(str(sys.exc_info())) + + 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 diff --git a/app/GulagDB.py b/app/GulagDB.py index 6a9bc74..6cd59eb 100644 --- a/app/GulagDB.py +++ b/app/GulagDB.py @@ -49,6 +49,28 @@ class GulagDB: except mariadb.Error as e: raise GulagDBException(e) from e + def get_mailbox(self,mailbox_id): + try: + cursor = self.conn.cursor() + cursor.execute( + "select * from Mailboxes where email_address='" + mailbox_id + "' limit 1;" + ) + data = cursor.fetchall() + if data == None: + raise GulagDBException("Mailbox '" + mailbox_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['mailboxes'] + dict['email_address'] + try: + return Mailbox(dict).__dict__ + except MailboxException as e: + raise GulagDBException(e.message) from e + except mariadb.Error as e: + raise GulagDBException(e) from e + def add_quarmail(self, quarmail): try: cursor = self.conn.cursor() @@ -121,7 +143,7 @@ class GulagDB: dict = {} for (name, value) in zip(desc, tuple): dict[name[0]] = value - results.append(QuarMail(dict).__dict__) + results.append(dict) return results except mariadb.Error as e: raise GulagDBException(e) from e diff --git a/app/Resources.py b/app/Resources.py index a422d4e..aa55d67 100644 --- a/app/Resources.py +++ b/app/Resources.py @@ -1,6 +1,5 @@ -from flask import request -from flask_restful import Resource, abort -from Entities import Mailbox,MailboxException,QuarMail,QuarMailException,Attachment,AttachmentException +#from flask import request +from flask_restful import Resource, abort, reqparse from Gulag import GulagException class GulagResource(Resource): @@ -62,3 +61,12 @@ class ResAttachment(GulagResource): def get(self,id): return {"resource": "Attachment by ID"} +class ResRSPAMDImporter(GulagResource): + def post(self,mailbox_id): + try: + self.gulag.rspamd_http2smtp(mailbox_id) + # TODO: Response mit Location-Header? + return {"resource: ": "HTTP2SMTP for RSPAMD"} + except GulagException as e: + abort(400, message=e.message) + diff --git a/app/gulag_server.py b/app/gulag_server.py index f2a8e6b..934df8e 100755 --- a/app/gulag_server.py +++ b/app/gulag_server.py @@ -4,7 +4,7 @@ import argparse,sys from flask import Flask from flask_restful import Api from Gulag import Gulag,GulagException -from Resources import ResRoot,ResMailboxes,ResQuarMails,ResAttachments +from Resources import ResRoot,ResMailboxes,ResQuarMails,ResAttachments,ResRSPAMDImporter parser = argparse.ArgumentParser() parser.add_argument('--config', required=True, help="Path to config file") @@ -33,6 +33,10 @@ try: '/api/v1/attachments/', resource_class_kwargs={'gulag_object': gulag} ) + api.add_resource(ResRSPAMDImporter, + '/api/v1/mailboxes//rspamdimporter/', + resource_class_kwargs={'gulag_object': gulag} + ) if __name__ == '__main__': app.run(debug=False, # will be overriden by uwsgi.ini diff --git a/db/gulag.sql b/db/gulag.sql index 8a5e4c8..f2458a2 100644 --- a/db/gulag.sql +++ b/db/gulag.sql @@ -6,12 +6,18 @@ create table Mailboxes( email_address varchar(767) not null primary key collate 'ascii_general_ci', name varchar(256) not null, imap_server varchar(256) not null default '127.0.0.1', + imap_port smallint unsigned not null default 143, imap_security varchar(32) not null default 'plain', imap_user varchar(256) not null, 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 '/', + smtp_server varchar(256) default null, + smtp_port smallint unsigned not null default 25, + smtp_security varchar(32) not null default 'plain', + smtp_user varchar(256) default null, + smtp_pass varchar(2048) default null, comment varchar(256) default null )ENGINE = InnoDB; insert into Mailboxes (email_address,name,imap_user,imap_pass)