too much :)

This commit is contained in:
Dominik Chilla 2019-01-06 23:05:24 +01:00
parent ebb762ba84
commit ff04b9f3e4
11 changed files with 247 additions and 181 deletions

View File

@ -1,13 +1,56 @@
import re import re
class MailrelayException(Exception):
message = None
def __init__(self,message):
self.message = message
class Mailrelay:
id = None
smtp_server = None
smtp_port = None
smtp_security = None
smtp_user = None
smtp_pass = None
comment = None
href = None
def __init__(self,mr_ref):
if 'id' not in mr_ref:
raise MailrelayException("'id' is mandatory!")
self.id = mr_ref['id']
if 'smtp_server' not in mr_ref:
raise MailrelayException("'smtp_server' is mandatory!")
self.smtp_server = mr_ref['smtp_server']
if 'smtp_security' in mr_ref:
if re.match("^(plain|starttls|tls)$",mr_ref['smtp_security']) is not None:
self.smtp_security = mr_ref['smtp_security']
else:
raise MailrelayException('smtp_security: {} is invalid! '+
'Valid values: plain,starttls,tls'.format(mr_ref['smtp_security'])
)
else:
raise MailrelayException("'smtp_security' is a mandatory!")
if 'smtp_port' in mr_ref:
self.smtp_port = mr_ref['smtp_port']
if 'smtp_user' not in mr_ref:
raise MailrelayException("'smtp_user' is mandatory!")
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']
if 'comment' in mr_ref:
self.comment = mr_ref['comment']
if 'href' in mr_ref:
self.href = mr_ref['href']
class MailboxException(Exception): class MailboxException(Exception):
message = None message = None
def __init__(self,message): def __init__(self,message):
self.message = message self.message = message
class Mailbox: class Mailbox:
email_address = None id = None
name = None
imap_server = None imap_server = None
imap_port = None imap_port = None
imap_security = None imap_security = None
@ -16,13 +59,14 @@ class Mailbox:
imap_mailbox = None imap_mailbox = None
imap_mailbox_fp = None imap_mailbox_fp = None
imap_separator = None imap_separator = None
mailrelay_id = None
comment = None comment = None
href = None href = None
def __init__(self,mb_ref): def __init__(self,mb_ref):
if 'email_address' not in mb_ref: if 'id' not in mb_ref:
raise MailboxException("'email_address' is mandatory!") raise MailboxException("'id' is mandatory!")
self.email_address = mb_ref['email_address'] self.id = mb_ref['id']
if 'name' not in mb_ref: if 'name' not in mb_ref:
raise MailboxException("'name' is mandatory!") raise MailboxException("'name' is mandatory!")
self.name = mb_ref['name'] self.name = mb_ref['name']
@ -85,6 +129,7 @@ class QuarMail:
uri_count = None uri_count = None
source_id = None source_id = None
ssdeep = None ssdeep = None
release_time = None
def __init__(self,qm_ref): def __init__(self,qm_ref):
if 'id' not in qm_ref: if 'id' not in qm_ref:
@ -135,6 +180,8 @@ class QuarMail:
if 'ssdeep' not in qm_ref: if 'ssdeep' not in qm_ref:
raise QuarMailException("'ssdeep' is mandatory!") raise QuarMailException("'ssdeep' is mandatory!")
self.ssdeep = qm_ref['ssdeep'] self.ssdeep = qm_ref['ssdeep']
if 'release_time' not in qm_ref:
raise QuarMailException("'release_time' is mandatory!")
class AttachmentException(Exception): class AttachmentException(Exception):
message = None message = None

View File

@ -57,28 +57,37 @@ class Gulag:
try: try:
self.db = GulagDB(self.config['db'],self.config['uri_prefixes']) self.db = GulagDB(self.config['db'],self.config['uri_prefixes'])
self.fields['Mailboxes'] = self.db.get_fields('Mailboxes') self.fields['Mailboxes'] = self.db.get_fields('Mailboxes')
self.fields['Mailrelays'] = self.db.get_fields('Mailrelays')
self.fields['QuarMails'] = self.db.get_fields('QuarMails') self.fields['QuarMails'] = self.db.get_fields('QuarMails')
self.fields['Attachments'] = self.db.get_fields('Attachments') self.fields['Attachments'] = self.db.get_fields('Attachments')
except GulagDBException as e: except GulagDBException as e:
logging.warning(whoami(self) + e.message) logging.warning(whoami(self) + e.message)
raise GulagException(whoami(self) + e.message) from e raise GulagException(whoami(self) + e.message) from e
logging.info('Gulag core initialized by ' + os.path.basename(__file__))
def check_fields(self,fields_target,args): def check_filters(self,fields_target,filters):
if fields_target not in self.fields: if fields_target not in self.fields:
raise GulagException( raise GulagBadInputException(
whoami(self) + fields_target + " not found in Gulag.fields!" whoami(self) + fields_target + " not found in Gulag.fields!"
) )
for arg in args: if 'rules' not in filters:
if(arg == 'query_offset' or arg == 'query_limit' raise GulagBadInputException(whoami(self) +
or arg == 'sort_index' or arg == 'sort_order' "no 'rules' found in filters!"
or arg == 'rfc822_message' or arg == 'filters' )
or arg in self.db.vcols): if 'groupOp' not in filters:
raise GulagBadInputException(whoami(self) +
"'groupOp' not found in filters!"
)
if filters['groupOp'] != 'AND' and filters['groupOp'] != 'OR':
raise GulagBadInputException(whoami(self) +
"invalid 'groupOp': " + filters['groupOp']
)
# {"groupOp":"AND","rules":[{"field":"uri_count","op":"eq","data":"3"}]}
for rule in filters['rules']:
if(rule['field'] in self.db.vcols):
continue continue
if arg not in self.fields[fields_target]: if rule['field'] not in self.fields[fields_target]:
raise GulagException( raise GulagBadInputException(whoami(self) +
whoami(self) + arg + " is not a valid field of " rule['field'] + " is not a valid field of " + fields_target + "!"
+ fields_target + "!"
) )
# Iterate through all mailboxes, extract metadata # Iterate through all mailboxes, extract metadata
@ -86,12 +95,14 @@ class Gulag:
def import_quarmails(self): def import_quarmails(self):
for mailbox in self.db.get_mailboxes(): for mailbox in self.db.get_mailboxes():
imap_mb = None imap_mb = None
messages = []
try: try:
imap_mb = IMAPmailbox(mailbox) imap_mb = IMAPmailbox(mailbox)
messages = imap_mb.get_unseen_messages()
except IMAPmailboxException as e: except IMAPmailboxException as e:
logging.warning(whoami(self) + e.message) logging.warning(whoami(self) + e.message)
continue continue
for unseen in imap_mb.get_unseen_messages(): for unseen in messages:
quarmail_ids = [] quarmail_ids = []
attachments = [] attachments = []
uris = {} uris = {}
@ -162,7 +173,9 @@ class Gulag:
except GulagDBException as e: except GulagDBException as e:
logging.warn(whoami(self) + e.message) logging.warn(whoami(self) + e.message)
raise GulagException(whoami(self) + e.message) from e raise GulagException(whoami(self) + e.message) from e
logging.info(whoami(self) + "QuarMail (%s) imported" % (quarmail_id)) logging.info(whoami(self) +
"QuarMail(%s)@Mailbox(%s) imported" % (quarmail_id,mailbox['id'])
)
quarmail_ids.append(quarmail_id) quarmail_ids.append(quarmail_id)
# Ende for rcpts # Ende for rcpts
# Alle MIME-Parts durchiterieren und Attachments # Alle MIME-Parts durchiterieren und Attachments
@ -242,7 +255,8 @@ class Gulag:
def get_quarmails(self,args): def get_quarmails(self,args):
qms_db = None qms_db = None
try: try:
self.check_fields('QuarMails',args) if 'filters' in args:
self.check_filters('QuarMails',args['filters'])
qms_db = self.db.get_quarmails(args) qms_db = self.db.get_quarmails(args)
except GulagDBBadInputException as e: except GulagDBBadInputException as e:
raise GulagBadInputException(whoami(self) + e.message) from e raise GulagBadInputException(whoami(self) + e.message) from e
@ -321,6 +335,20 @@ class Gulag:
logging.warning(whoami(self) + e.message) logging.warning(whoami(self) + e.message)
raise GulagException(whoami(self) + e.message) from e raise GulagException(whoami(self) + e.message) from e
def release_quarmail(self,args):
try:
quarmail = self.get_quarmail({
"quarmail_id": args['quarmail_id'],
"rfc822_message": True
})
#
# TODO: re-send quarmail to original env_rcpt
# TODO: self.delete_quarmail() if arg['purge']
except GulagNotFoundException as e:
raise GulagNotFoundException(whoami(self) + e.message) from e
except GulagException as e:
raise GulagException(whoami(self) + e.message) from e
def delete_quarmail(self, args): def delete_quarmail(self, args):
qm_db = None qm_db = None
try: try:
@ -610,6 +638,3 @@ class Gulag:
imap_mb.close() imap_mb.close()
except IMAPmailboxException as e: except IMAPmailboxException as e:
raise GulagException(whoami(self) + e.message) from e raise GulagException(whoami(self) + e.message) from e
def release_quarmail(self,args):
pass

View File

@ -2,7 +2,8 @@ import mysql.connector as mariadb
from Entities import( from Entities import(
Mailbox,MailboxException,QuarMail, Mailbox,MailboxException,QuarMail,
QuarMailException,Attachment, QuarMailException,Attachment,
AttachmentException,URI,URIException AttachmentException,URI,URIException,
Mailrelay,MailrelayException
) )
from GulagUtils import whoami from GulagUtils import whoami
@ -47,7 +48,7 @@ class GulagDB:
except mariadb.Error as e: except mariadb.Error as e:
raise GulagDBException(whoami(self) + str(e.msg)) from e raise GulagDBException(whoami(self) + str(e.msg)) from e
self.uri_prefixes = uri_prefixes self.uri_prefixes = uri_prefixes
# virtual columns cannot not be stated in where-clause # virtual columns
self.vcols['attach_count'] = {} self.vcols['attach_count'] = {}
self.vcols['uri_count'] = {} self.vcols['uri_count'] = {}
@ -62,7 +63,7 @@ class GulagDB:
data = cursor.fetchall() data = cursor.fetchall()
if not data: if not data:
raise GulagDBNotFoundException(whoami(self) raise GulagDBNotFoundException(whoami(self)
+ "describe " + table_name + " failed: got no fields!" + "describe ''" + table_name + "'' failed: got no fields!"
) )
desc = cursor.description desc = cursor.description
cursor.close() cursor.close()
@ -81,60 +82,32 @@ class GulagDB:
int(args['query_offset']) int(args['query_offset'])
except ValueError: except ValueError:
raise GulagDBBadInputException(whoami(self) + raise GulagDBBadInputException(whoami(self) +
"query_offset must be numeric!" "'query_offset' must be numeric!"
) )
try:
int(args['query_limit'])
except ValueError:
raise GulagDBBadInputException(whoami(self) +
"'query_limit' must be numeric!"
)
return "limit "+args['query_offset']+","+args['query_limit']
elif('query_offset' in args and 'query_limit' not in args):
raise GulagDBBadInputException(whoami(self) +
"'query_offset' without 'query_limit' is useless!"
)
elif('query_limit' in args and 'query_offset' not in args):
try: try:
int(args['query_limit']) int(args['query_limit'])
except ValueError: except ValueError:
raise GulagDBBadInputException(whoami(self) + raise GulagDBBadInputException(whoami(self) +
"query_limit must be numeric!" "query_limit must be numeric!"
) )
return "limit "+args['query_offset']+","+args['query_limit']
elif('query_offset' in args and 'query_limit' not in args):
raise GulagDBBadInputException(whoami(self) +
"query_offset without query_limit is useless!"
)
elif('query_limit' in args and 'query_offset' not in args):
try:
int(args['query_limit'])
except ValueError:
raise GulagDBBadInputException(whoami(self) + "query_limit must be numeric!")
return "limit " + args['query_limit'] return "limit " + args['query_limit']
else: else:
return "" return ""
def get_where_clause(self,args):
where_clause = ""
cnt = 0
for arg in args:
if(arg == 'query_offset' or arg == 'query_limit'
or arg == 'sort_index' or arg == 'sort_order'
or arg == 'rfc822_message'):
continue
if(cnt == 0):
if arg in self.vcols:
where_clause += "having " + arg + "='" + args[arg] + "' "
else:
where_clause += "where " + arg + "='" + args[arg] + "' "
else:
where_clause += "and " + arg + "='" + args[arg] + "' "
cnt += 1
return where_clause
def get_where_clause_from_filters(self,filters): def get_where_clause_from_filters(self,filters):
# {"groupOp":"AND","rules":[{"field":"uri_count","op":"eq","data":"3"}]} # {"groupOp":"AND","rules":[{"field":"uri_count","op":"eq","data":"3"}]}
if 'rules' not in filters:
raise GulagDBBadInputException(whoami(self) +
"no 'rules' found in filters!"
)
if 'groupOp' not in filters:
raise GulagDBBadInputException(whoami(self) +
"'groupOp' not found in filters!"
)
if filters['groupOp'] != 'AND' and filters['groupOp'] != 'OR':
raise GulagDBBadInputException(whoami(self) +
"invalid 'groupOp': " + filters['groupOp']
)
where_clause = "" where_clause = ""
for rule in filters['rules']: for rule in filters['rules']:
if 'field' not in rule: if 'field' not in rule:
@ -177,6 +150,65 @@ class GulagDB:
where_clause += " " + filters['groupOp'] + " " + field_op_data where_clause += " " + filters['groupOp'] + " " + field_op_data
return where_clause return where_clause
def add_mailrelay(self,args):
pass
def delete_mailrelay(self,mailrelay_id):
pass
def get_mailrelays(self):
try:
cursor = self.conn.cursor()
cursor.execute("select * from Mailrelays;")
results = []
data = cursor.fetchall()
if not data:
return results
desc = cursor.description
for tuple in data:
dict = {}
for (name, value) in zip(desc, tuple):
dict[name[0]] = value
dict['href'] = self.uri_prefixes['mailrelays'] + dict['id']
try:
results.append(Mailrelay(dict).__dict__)
except MailboxException as e:
print("MailrelayException: " + e.message)
continue
return results
except mariadb.Error as e:
raise GulagDBException(whoami(self) + str(e.msg)) from e
def get_mailrelay(self,mailbox_id):
try:
cursor = self.conn.cursor()
cursor.execute(
"select * from Mailrelays where id='" + mailrelay_id + "' limit 1;"
)
data = cursor.fetchall()
if not data:
raise GulagDBNotFoundException(whoami(self)
+ "Mailrelay '" + mailrelay_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['mailrelays'] + dict['id']
try:
return Mailrelay(dict).__dict__
except MailrelayException as e:
raise GulagDBException(whoami(self) + e.message) from e
except mariadb.Error as e:
raise GulagDBException(whoami(self) + str(e.msg)) from e
def add_mailbox(self,args):
pass
def delete_mailbox(self,mailbox_id):
pass
def get_mailboxes(self): def get_mailboxes(self):
try: try:
cursor = self.conn.cursor() cursor = self.conn.cursor()
@ -190,7 +222,7 @@ class GulagDB:
dict = {} dict = {}
for (name, value) in zip(desc, tuple): for (name, value) in zip(desc, tuple):
dict[name[0]] = value dict[name[0]] = value
dict['href'] = self.uri_prefixes['mailboxes'] + dict['email_address'] dict['href'] = self.uri_prefixes['mailboxes'] + dict['id']
try: try:
results.append(Mailbox(dict).__dict__) results.append(Mailbox(dict).__dict__)
except MailboxException as e: except MailboxException as e:
@ -204,7 +236,7 @@ class GulagDB:
try: try:
cursor = self.conn.cursor() cursor = self.conn.cursor()
cursor.execute( cursor.execute(
"select * from Mailboxes where email_address='" + mailbox_id + "' limit 1;" "select * from Mailboxes where id='" + mailbox_id + "' limit 1;"
) )
data = cursor.fetchall() data = cursor.fetchall()
if not data: if not data:
@ -216,7 +248,7 @@ class GulagDB:
dict = {} dict = {}
for (name, value) in zip(desc, tuple): for (name, value) in zip(desc, tuple):
dict[name[0]] = value dict[name[0]] = value
dict['href'] = self.uri_prefixes['mailboxes'] + dict['email_address'] dict['href'] = self.uri_prefixes['mailboxes'] + dict['id']
try: try:
return Mailbox(dict).__dict__ return Mailbox(dict).__dict__
except MailboxException as e: except MailboxException as e:
@ -260,8 +292,6 @@ class GulagDB:
where_clause = "" where_clause = ""
if 'filters' in args: if 'filters' in args:
where_clause = self.get_where_clause_from_filters(args['filters']) where_clause = self.get_where_clause_from_filters(args['filters'])
else:
where_clause = self.get_where_clause(args)
cursor = self.conn.cursor() cursor = self.conn.cursor()
query = "select *,(select count(*) from QuarMail2Attachment" query = "select *,(select count(*) from QuarMail2Attachment"
query += " where QuarMails.id=QuarMail2Attachment.quarmail_id) as attach_count," query += " where QuarMails.id=QuarMail2Attachment.quarmail_id) as attach_count,"

View File

@ -11,7 +11,7 @@ class IMAPmailboxException(Exception):
self.message = str(message) self.message = str(message)
class IMAPmailbox: class IMAPmailbox:
email_address = None id = None
imap_server = None imap_server = None
imap_user = None imap_user = None
imap_pass = None imap_pass = None
@ -19,7 +19,7 @@ class IMAPmailbox:
mailbox = None mailbox = None
def __init__(self, mb_ref): def __init__(self, mb_ref):
self.email_address = mb_ref['email_address'] self.id = mb_ref['id']
self.imap_server = mb_ref['imap_server'] self.imap_server = mb_ref['imap_server']
self.imap_user = mb_ref['imap_user'] self.imap_user = mb_ref['imap_user']
self.imap_pass = mb_ref['imap_pass'] self.imap_pass = mb_ref['imap_pass']
@ -54,8 +54,9 @@ class IMAPmailbox:
for uid in data[0].split(): for uid in data[0].split():
rv, data = self.mailbox.uid('FETCH', uid, '(RFC822)') rv, data = self.mailbox.uid('FETCH', uid, '(RFC822)')
if rv != 'OK': if rv != 'OK':
print("ERROR getting message", str(uid)) raise IMAPmailboxException(whoami(self) +
continue str(data) + ", IMAP_UID: " + str(uid)
)
results.append({ results.append({
'imap_uid': uid, 'imap_uid': uid,
'msg': data[0][1] 'msg': data[0][1]
@ -114,14 +115,13 @@ class IMAPmailbox:
else: else:
# not encoded # not encoded
part_fn = part_fn[0][0] part_fn = part_fn[0][0]
print("C-T-E: " + str(part['Content-Transfer-Encoding']))
if(part_fn == filename): if(part_fn == filename):
return part.get_payload(decode=False) return part.get_payload(decode=False)
# End if part.get_filename() # End if part.get_filename()
# End msg.walk() loop # End msg.walk() loop
raise IMAPmailboxException(whoami(self) + raise IMAPmailboxException(whoami(self) +
"Attachment ("+ str(filename) +")@IMAP UID(" + str(imap_uid) + ")@" "Attachment ("+ str(filename) +")@IMAP UID(" + str(imap_uid) + ")@"
+ str(self.email_address) + " not found!" + str(self.id) + " not found!"
) )
def get_main_parts(self,imap_uid): def get_main_parts(self,imap_uid):
@ -134,5 +134,5 @@ class IMAPmailbox:
if(len(mparts) > 0): if(len(mparts) > 0):
return mparts return mparts
raise IMAPmailboxException(whoami(self) + raise IMAPmailboxException(whoami(self) +
"IMAP_UID(" + str(imap_uid)+")@"+str(self.email_address)+" has no main parts!" "IMAP_UID(" + str(imap_uid)+")@"+str(self.id)+" has no main parts!"
) )

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse,sys,os,time,signal import argparse,sys,os,time,signal,logging
from Gulag import Gulag,GulagException from Gulag import Gulag,GulagException
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -16,13 +16,14 @@ if(importer_pid == 0):
except GulagException as e: except GulagException as e:
print(e.message) print(e.message)
sys.exit(1) sys.exit(1)
logging.info("Gulag-Importer: starting")
while True: while True:
try: try:
gulag.import_quarmails() gulag.import_quarmails()
except GulagException as e: except GulagException as e:
print("Importer-Exception: " + e.message, file=sys.stderr) logging.error("Gulag-Importer-Exception: " + e.message)
except: except:
print("Importer-Exception: " + str(sys.exc_info()),file=sys.stderr) logging.error("Gulag-Importer-Exception: " + str(sys.exc_info()))
time.sleep(gulag.config['importer']['interval']) time.sleep(gulag.config['importer']['interval'])
cleaner_pid = os.fork() cleaner_pid = os.fork()
@ -31,28 +32,27 @@ if(cleaner_pid == 0):
try: try:
gulag = Gulag(args.config) gulag = Gulag(args.config)
except GulagException as e: except GulagException as e:
print(e.message) logging.info("Gulag-Cleaner-Exception: " + e.message)
sys.exit(1) sys.exit(1)
logging.info("Gulag-Cleaner: starting")
while True: while True:
try: try:
gulag.cleanup_quarmails() gulag.cleanup_quarmails()
except GulagException as e: except GulagException as e:
print("Cleaner-Exception: " + e.message) logging.info("Cleaner-Exception: " + e.message)
except: except:
print("Cleaner-Exception: " + str(sys.exc_info()),file=sys.stderr) logging.info("Cleaner-Exception: " + str(sys.exc_info()))
time.sleep(gulag.config['cleaner']['interval']) time.sleep(gulag.config['cleaner']['interval'])
# Parent # Parent
child_pids.append(importer_pid) child_pids.append(importer_pid)
child_pids.append(cleaner_pid) child_pids.append(cleaner_pid)
try: try:
print("Entered helpers main loop...")
while True: while True:
time.sleep(10) time.sleep(10)
except: except:
print("Helpers MAIN-EXCEPTION: " + str(sys.exc_info())) logging.info("Helpers MAIN-EXCEPTION: " + str(sys.exc_info()))
# Destroy childs # Destroy childs
for child_pid in child_pids: for child_pid in child_pids:
print("Killing child pid: %s", child_pid) logging.info("Helpers parent: Killing child pid: %s", child_pid)
os.kill(child_pid, signal.SIGTERM) os.kill(child_pid, signal.SIGTERM)

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse,sys import argparse,sys,logging
from flask import Flask from flask import Flask
from flask_restful import Api from flask_restful import Api
from Gulag import Gulag,GulagException from Gulag import Gulag,GulagException
@ -8,7 +8,7 @@ from Resources import (ResRoot,ResMailboxes,
ResQuarMails,ResQuarMail,ResQuarMailAttachments, ResQuarMails,ResQuarMail,ResQuarMailAttachments,
ResQuarMailAttachment,ResAttachments,ResAttachment, ResQuarMailAttachment,ResAttachments,ResAttachment,
ResRspamd2Mailbox,ResQuarMailURIs,ResQuarMailURI, ResRspamd2Mailbox,ResQuarMailURIs,ResQuarMailURI,
ResMailradar2Mailbox ResMailradar2Mailbox,ResQuarMailRelease
) )
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--config', required=True, help="Path to config file") parser.add_argument('--config', required=True, help="Path to config file")
@ -19,6 +19,7 @@ try:
gulag = Gulag(args.config) gulag = Gulag(args.config)
except GulagException as e: except GulagException as e:
raise Exception(e.message) from e raise Exception(e.message) from e
logging.info("Gulag-Server: starting")
app = Flask(__name__) app = Flask(__name__)
# https://github.com/flask-restful/flask-restful/issues/780#issuecomment-434588559 # https://github.com/flask-restful/flask-restful/issues/780#issuecomment-434588559
app.config['ERROR_404_HELP'] = False app.config['ERROR_404_HELP'] = False
@ -39,6 +40,10 @@ try:
'/api/v1/quarmails/<int:quarmail_id>', '/api/v1/quarmails/<int:quarmail_id>',
resource_class_kwargs={'gulag_object': gulag} resource_class_kwargs={'gulag_object': gulag}
) )
api.add_resource(ResQuarMailRelease,
'/api/v1/quarmails/<int:quarmail_id>/release',
resource_class_kwargs={'gulag_object': gulag}
)
api.add_resource(ResQuarMailAttachments, api.add_resource(ResQuarMailAttachments,
'/api/v1/quarmails/<int:quarmail_id>/attachments', '/api/v1/quarmails/<int:quarmail_id>/attachments',
resource_class_kwargs={'gulag_object': gulag} resource_class_kwargs={'gulag_object': gulag}
@ -80,4 +85,4 @@ try:
gulag.db.close() gulag.db.close()
sys.exit(0) sys.exit(0)
except: except:
print("MAIN-EXCEPTION: " + str(sys.exc_info())) logging.error("Gulag-Server-Exception: " + str(sys.exc_info()))

View File

@ -9,7 +9,7 @@ try:
'filters': {"groupOp":"OR","rules":[ 'filters': {"groupOp":"OR","rules":[
# {"field":"uri_count","op":"eq","data":"2"}, # {"field":"uri_count","op":"eq","data":"2"},
# {"field":"attach_count","op":"ne","data":"0"} # {"field":"attach_count","op":"ne","data":"0"}
{"field":"uri_count","op":"lt","data":"2"} # {"field":"uri_count","op":"gt","data":"2"}
]}, ]},
#'rfc822_message': 'ja, ich will', #'rfc822_message': 'ja, ich will',
#'query_limit': 2, #'query_limit': 2,

View File

@ -24,6 +24,7 @@
}, },
"uri_prefixes": { "uri_prefixes": {
"root": "http://127.0.0.1:9090/api/v1/", "root": "http://127.0.0.1:9090/api/v1/",
"mailrelays": "http://127.0.0.1:9090/api/v1/mailrelays/",
"mailboxes": "http://127.0.0.1:9090/api/v1/mailboxes/", "mailboxes": "http://127.0.0.1:9090/api/v1/mailboxes/",
"quarmails": "http://127.0.0.1:9090/api/v1/quarmails/", "quarmails": "http://127.0.0.1:9090/api/v1/quarmails/",
"attachments": "http://127.0.0.1:9090/api/v1/attachments/" "attachments": "http://127.0.0.1:9090/api/v1/attachments/"

View File

@ -3,7 +3,7 @@ create database Gulag;
use Gulag; use Gulag;
create table Mailrelays( create table Mailrelays(
id varchar(64) not null primary key, id varchar(128) not null primary key,
smtp_server varchar(256) default '127.0.0.1' collate 'ascii_general_ci', smtp_server varchar(256) default '127.0.0.1' collate 'ascii_general_ci',
smtp_port smallint unsigned not null default 25, smtp_port smallint unsigned not null default 25,
smtp_security varchar(32) not null default 'plain' collate 'ascii_general_ci', smtp_security varchar(32) not null default 'plain' collate 'ascii_general_ci',
@ -11,10 +11,11 @@ create table Mailrelays(
smtp_pass varchar(1024) default null collate 'ascii_general_ci', smtp_pass varchar(1024) default null collate 'ascii_general_ci',
comment varchar(256) default null comment varchar(256) default null
)ENGINE = InnoDB; )ENGINE = InnoDB;
insert into Mailrelays (id) values ('default_local_mailrelay');
create table Mailboxes( create table Mailboxes(
email_address varchar(767) not null primary key collate 'ascii_general_ci', id varchar(128) not null primary key,
name varchar(256) not null, name varchar(512) not null,
imap_server varchar(256) not null default '127.0.0.1' collate 'ascii_general_ci', imap_server varchar(256) not null default '127.0.0.1' collate 'ascii_general_ci',
imap_port smallint unsigned not null default 143, imap_port smallint unsigned not null default 143,
imap_security varchar(32) not null default 'plain' collate 'ascii_general_ci', imap_security varchar(32) not null default 'plain' collate 'ascii_general_ci',
@ -23,10 +24,12 @@ create table Mailboxes(
imap_mailbox varchar(256) not null default 'INBOX', imap_mailbox varchar(256) not null default 'INBOX',
imap_mailbox_fp varchar(256) not null default 'false-positives', imap_mailbox_fp varchar(256) not null default 'false-positives',
imap_separator varchar(4) not null default '/', imap_separator varchar(4) not null default '/',
mailrelay_id varchar(128) not null,
foreign key (mailrelay_id) references Mailrelays (id) on update cascade on delete restrict,
comment varchar(256) default null comment varchar(256) default null
)ENGINE = InnoDB; )ENGINE = InnoDB;
insert into Mailboxes (email_address,name,imap_user,imap_pass) insert into Mailboxes (id,name,imap_user,imap_pass,mailrelay_id)
values('quarantine@example.org','E-Mail inbound quarantine','quarantine','quarantine_secure_password'); values('quarantine@example.org','E-Mail inbound quarantine','quarantine','quarantine_secure','default_local_mailrelay');
create table Sources ( create table Sources (
id varchar(32) not null collate 'ascii_general_ci' primary key id varchar(32) not null collate 'ascii_general_ci' primary key
@ -37,7 +40,7 @@ insert into Sources (id) values ('mailradar');
create table QuarMails ( create table QuarMails (
id int unsigned auto_increment primary key, id int unsigned auto_increment primary key,
ctime TIMESTAMP, ctime TIMESTAMP default CURRENT_TIMESTAMP,
mx_queue_id varchar(64) not null collate 'ascii_general_ci', mx_queue_id varchar(64) not null collate 'ascii_general_ci',
env_from varchar(256) not null, env_from varchar(256) not null,
env_rcpt varchar(256) not null, env_rcpt varchar(256) not null,
@ -47,13 +50,14 @@ create table QuarMails (
hdr_msgid varchar(512) default null, hdr_msgid varchar(512) default null,
hdr_date varchar(128) default null collate 'ascii_general_ci', hdr_date varchar(128) default null collate 'ascii_general_ci',
cf_meta TEXT default null, cf_meta TEXT default null,
imap_uid int unsigned not null,
mailbox_id varchar(256) not null collate 'ascii_general_ci',
foreign key (mailbox_id) references Mailboxes (email_address) on update cascade on delete cascade,
source_id varchar(32) not null collate 'ascii_general_ci',
foreign key (source_id) references Sources (id) on update cascade on delete cascade,
msg_size int unsigned not null, msg_size int unsigned not null,
ssdeep varchar(592) not null collate 'ascii_general_ci' ssdeep varchar(592) not null collate 'ascii_general_ci',
release_time TIMESTAMP default 0,
imap_uid int unsigned not null,
mailbox_id varchar(128) not null,
foreign key (mailbox_id) references Mailboxes (id) on update cascade on delete restrict,
source_id varchar(32) not null collate 'ascii_general_ci',
foreign key (source_id) references Sources (id) on update cascade on delete restrict
)ENGINE = InnoDB; )ENGINE = InnoDB;
create table Attachments ( create table Attachments (

View File

@ -8,12 +8,10 @@ info:
license: license:
name: Apache 2.0 name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html url: http://www.apache.org/licenses/LICENSE-2.0.html
host: gulag.example.com host: gulag.example.com
basePath: "/api/v1" basePath: "/api/v1"
schemes: schemes:
- https - https
paths: paths:
/quarmails: /quarmails:
get: get:
@ -25,11 +23,6 @@ paths:
produces: produces:
- application/json - application/json
parameters: parameters:
- in: query
name: query_offset
description: number of records to skip for pagination
required: false
type: string
- in: query - in: query
name: query_limit name: query_limit
description: number of records to retrieve description: number of records to retrieve
@ -37,6 +30,11 @@ paths:
format: int32 format: int32
minimum: 0 minimum: 0
required: false required: false
- in: query
name: query_offset
description: number of records to skip for pagination
required: false
type: string
- in: query - in: query
name: sort_index name: sort_index
description: field used to sort results description: field used to sort results
@ -47,72 +45,6 @@ paths:
description: order used to sort results description: order used to sort results
type: string type: string
required: false required: false
- in: query
name: id
description: unique id of a quarantined email
type: integer
format: int32
minimum: 0
required: false
- in: query
name: ctime
description: create timestamp of quarantined email
type: string
required: false
- in: query
name: mx_queueid
type: string
required: false
- in: query
name: env_from
type: string
description: RFC5321 envelope sender
required: false
- in: query
name: env_rcpt
type: string
description: RFC5321 envelope recipient
required: false
- in: query
name: hdr_cf
type: string
description: content scanner header
required: false
- in: query
name: hdr_from
type: string
description: RFC5322 From header
required: false
- in: query
name: hdr_subject
type: string
description: RFC5322 Subject header
required: false
- in: query
name: hdr_msgid
type: string
description: RFC5322 Message-ID header
required: false
- in: query
name: hdr_date
type: string
description: RFC5322 Date header
required: false
- in: query
name: cf_meta
type: string
description: content filter meta data
required: false
- in: query
name: mailbox_id
description: IMAP mailbox identifier, e.g. quarantine@example.org
type: string
required: false
- in: query
name: imap_uid
description: IMAP UID of a quarantined email
type: string
required: false
- in: query - in: query
name: rfc822_message name: rfc822_message
type: string type: string
@ -122,7 +54,7 @@ paths:
name: filters name: filters
type: string type: string
required: false required: false
description: jqgrid-style search filter description: 'jqgrid-style filters, e.g. {"groupOp":"AND","rules":[{"field":"uri_count","op":"eq","data":"3"}]}'
responses: responses:
200: 200:
description: search results matching criteria description: search results matching criteria
@ -159,6 +91,8 @@ paths:
$ref: '#/definitions/QuarMail' $ref: '#/definitions/QuarMail'
400: 400:
description: bad input parameter description: bad input parameter
404:
description: not found
500: 500:
description: server error description: server error
delete: delete:
@ -201,6 +135,8 @@ paths:
$ref: '#/definitions/Attachment' $ref: '#/definitions/Attachment'
400: 400:
description: bad input parameter description: bad input parameter
404:
description: not found
500: 500:
description: server error description: server error
@ -262,6 +198,8 @@ paths:
$ref: '#/definitions/URI' $ref: '#/definitions/URI'
400: 400:
description: bad input parameter description: bad input parameter
404:
description: not found
500: 500:
description: server error description: server error
@ -310,6 +248,7 @@ definitions:
- mailbox_id - mailbox_id
- imap_uid - imap_uid
- msg_size - msg_size
- ssdeep
- href - href
properties: properties:
id: id:
@ -365,6 +304,10 @@ definitions:
uri_count: uri_count:
type: integer type: integer
description: number of uris description: number of uris
ssdeep:
type: string
description: Context triggered piecewise hash (CTPH)
example: '6:lWRUFiWwx5QHD2Q2/NNsj90YzrWPpsj7v:lWiEQHD2Q+sj90aKsjr'
rfc822_message: rfc822_message:
type: string type: string
description: full RFC822 email message description: full RFC822 email message
@ -378,6 +321,8 @@ definitions:
- magic - magic
- mailbox_id - mailbox_id
- imap_uid - imap_uid
- ssdeep
- sha256
- href - href
properties: properties:
id: id:
@ -409,9 +354,18 @@ definitions:
imap_uid: imap_uid:
type: integer type: integer
description: IMAP unique id of the quarantined email description: IMAP unique id of the quarantined email
example: 12345
data: data:
type: string type: string
description: raw/encoded (see content_encoding) attachment payload description: raw/encoded (see content_encoding) attachment payload
ssdeep:
type: string
description: Context Triggered Piecewise Hash (CTPH)
example: '6:lWRUFiWwx5QHD2Q2/NNsj90YzrWPpsj7v:lWiEQHD2Q+sj90aKsjr'
sha256:
type: string
description: SHA256 digest
example: '658cb334f4ab3d747e77fdfceaa1ff3c2477ccc8500c4d8f4552ac0471089b60'
URI: URI:
type: object type: object
required: required: