From 505da62f2ad7f6839c07f22c5cc413d6ecd49600 Mon Sep 17 00:00:00 2001 From: nico Date: Fri, 8 Nov 2019 14:55:49 +0100 Subject: slimmed down methods (#4) * slimmed down config class + add start stop arguments to config class init * update requirements.txt * correctly apply custom timestamp to report functions too * init report methods earlier --- config.py | 42 +++++------------------------------------- main.py | 33 +++++++++++++++------------------ report.py | 40 ++++++++++++++++++++++++++++------------ requirements.txt | 6 +++--- 4 files changed, 51 insertions(+), 70 deletions(-) diff --git a/config.py b/config.py index 10e938f..117c016 100644 --- a/config.py +++ b/config.py @@ -1,20 +1,17 @@ # -*- coding: utf-8 -*- import json import os +import sys class Config(object): def __init__(self): self.config = dict() - self.valid_config = bool # filepath of the config.json in the project directory self.path = os.path.dirname(__file__) self.filepath = ('/'.join([self.path, 'config.json'])) - # load config - self.load() - def load(self): try: # try to read config.json @@ -24,41 +21,12 @@ class Config(object): except FileNotFoundError: # if file is absent create file open(self.filepath, "w").close() + print("-- config.json is missing.", file=sys.stderr) + print("-- {file} has been created.".format(file=self.filepath), file=sys.stderr) except json.decoder.JSONDecodeError: # config file is present but empty + print("-- JSON parsing error, please check your config.json file.", file=sys.stderr) pass - def get_at(self, attrib: str): - """ - retrieve attribute from config file - :param attrib: keyword corresponding to keyword in config dictionary - :return: value of specified keyword or False if keyword is not present in dictionary - """ - if attrib in self.config: - # return corresponding attrib from config - return self.config[attrib] - else: - # if attrib is not present in config return False - self.config[attrib] = False - - def set_at(self, attrib: str, param): - """ - set attribute to parameter inside config file - :param attrib: keyword which should be updated/created in config dictionary - :param param: parameter the keyword should be updated to - """ - self.config[attrib] = param - - # save new attrib to file - with open(self.filepath, "w", encoding="utf-8") as f: - f.write(json.dumps(self.config, indent=4)) - - def unset_at(self, attrib: str): - """ - unset attribute inside config file - :param attrib: attribute which should be unset inside config file - """ - if attrib in self.config: - # only if attrib is actually present unset it - self.config.pop(attrib) + return self.config diff --git a/main.py b/main.py index 3cec813..60e65f5 100755 --- a/main.py +++ b/main.py @@ -11,7 +11,6 @@ import sys import tabulate from defusedxml import ElementTree -from config import Config from report import ReportDomain @@ -25,9 +24,9 @@ class AbuseReport: self.start = arguments.start self.stop = arguments.stop self.path = os.path.dirname(__file__) - self.config = Config() self.conn = sqlite3.connect("/".join([self.path, "spam.db"])) + self.Report = ReportDomain(self.conn) self.jid_pattern = re.compile("^(?:([^\"&'/:<>@]{1,1023})@)?([^/@]{1,1023})(?:/(.{1,1023}))?$") self.message_pattern = re.compile(r'', re.DOTALL) @@ -79,6 +78,9 @@ class AbuseReport: # set stop value to now self.stop = dt.datetime.strftime(dt.datetime.now(), '%Y-%m-%dT%H:%M:%S') + # add validated timestamps to report class + self.Report.addtime(self.start, self.stop) + # if one or more domains are specified return only their info if self.domain is not None: @@ -86,11 +88,10 @@ class AbuseReport: for domain in self.domain: # build and execute - sql = '''SELECT COUNT(*) AS messages, COUNT(DISTINCT user) AS bots, domain, MIN(ts) AS first, MAX(ts) AS last \ - FROM spam \ - WHERE domain = :domain \ - AND ts > :start \ - AND ts < :stop;''' + sql = '''SELECT COUNT(*) AS messages, COUNT(DISTINCT user) AS bots, domain, MIN(ts) AS first, MAX(ts) AS last + FROM spam + WHERE domain = :domain + AND ts > :start AND ts < :stop;''' parameter = { "domain": domain, "start": self.start, @@ -114,15 +115,14 @@ class AbuseReport: else: # build and execute - sql = '''SELECT COUNT(*) AS messages, COUNT(DISTINCT user) AS bots, domain AS domain from spam \ - WHERE ts > :start \ - AND ts < :stop \ + sql = '''SELECT COUNT(*) AS messages, COUNT(DISTINCT user) AS bots, domain AS domain from spam + WHERE ts > :start AND ts < :stop GROUP BY domain ORDER BY 1 DESC LIMIT 10;''' result = self.conn.execute(sql, {"start": self.start, "stop": self.stop}).fetchall() # tabelize data - spam_table = tabulate.tabulate(result, headers=["messages", "bots", "domain", "first seen", "last seen"], - tablefmt="github") + spam_table = tabulate.tabulate(result, tablefmt="psql", headers=["messages", "bots", "domain","first seen", + "last seen"]) # output to stdout output = "\n\n".join([spam_table]) @@ -203,9 +203,6 @@ class AbuseReport: :param domain: string containing a domain name :param query: list of tuples containing the query results for the specified domain/s """ - # init report class - report = ReportDomain(self.config, self.conn) - try: # open abuse report template file with open("/".join([self.path, "template/abuse-template.txt"]), "r", encoding="utf-8") as template: @@ -225,15 +222,15 @@ class AbuseReport: # write report files with open("/".join([self.path, "report", report_filename]), "w", encoding="utf-8") as report_out: - content = report.template(report_template, domain, query) + content = self.Report.template(report_template, domain, query) report_out.write(content) with open("/".join([self.path, "report", jids_filename]), "w", encoding="utf-8") as report_out: - content = report.jids(domain) + content = self.Report.jids(domain) report_out.write(content) with open("/".join([self.path, "report", logs_filename]), "w", encoding="utf-8") as report_out: - content = report.logs(domain) + content = self.Report.logs(domain) report_out.write(content) diff --git a/report.py b/report.py index e87517c..98d50a9 100644 --- a/report.py +++ b/report.py @@ -2,15 +2,23 @@ import dns.resolver as dns import tabulate +from config import Config + class ReportDomain: - def __init__(self, config, conn): + def __init__(self, conn): """ - :param config: configuration object :param conn: sqlite connection object """ - self.config = config + self.config = Config().load() self.conn = conn + self.start = int() + self.stop = int() + + def addtime(self, start, stop): + # add start and stop timestamps + self.start = start + self.stop = stop def template(self, template: str, domain: str, query: list): """ @@ -20,7 +28,7 @@ class ReportDomain: :param query: list of tuples containing the query results for the specified domain/s :return: string containing the fully formatted abuse report """ - name = self.config.get_at("name") + name = self.config["name"] # lookup and format srv target and ip srv, ips = self.srv(domain) @@ -37,9 +45,13 @@ class ReportDomain: :param domain: string containing a domain name :return: formatted result string """ - - jids = self.conn.execute('''SELECT user || '@' || domain AS jid FROM spam WHERE ts BETWEEN DATE('now','-14 days') - AND DATE('now') AND domain=:domain GROUP BY user ORDER BY 1;''', {"domain": domain}).fetchall() + sql = '''SELECT user || '@' || domain AS jid FROM spam + WHERE ts > :start + AND ts < :stop + AND domain = :domain + GROUP BY user ORDER BY 1;''' + param = {"domain": domain, "start": self.start, "stop":self.stop} + jids = self.conn.execute(sql, param).fetchall() return tabulate.tabulate(jids, tablefmt="plain") @@ -49,11 +61,15 @@ class ReportDomain: :param domain: string containing a domain name :return: formatted string containing the result """ - logs = self.conn.execute('''SELECT CHAR(10) || MIN(ts) || ' - ' || MAX(ts) || char(10) || COUNT(*) || - 'messages:' || char(10) ||'========================================================================' || - char(10) || message || char(10) || '========================================================================' - FROM spam WHERE ts BETWEEN DATE('now','-14 days') AND DATE('now') AND domain=:domain GROUP BY message ORDER - BY COUNT(*) DESC LIMIT 10;''', {"domain": domain}).fetchall() + sql = '''SELECT CHAR(10) || MIN(ts) || ' - ' || MAX(ts) || char(10) || COUNT(*) || 'messages:' || char(10) || + '========================================================================' || char(10) || message || + char(10) || '========================================================================' FROM spam + WHERE ts > :start + AND ts < :stop + AND domain = :domain + GROUP BY message ORDER BY COUNT(*) DESC LIMIT 10;''' + param = {"domain": domain, "start": self.start, "stop": self.stop} + logs = self.conn.execute(sql, param).fetchall() return tabulate.tabulate(logs, tablefmt="plain") diff --git a/requirements.txt b/requirements.txt index 785dc6e..8dec4df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -defusedxml -tabulate -dnspython +defusedxml>=0.6.0 +dnspython>=1.16.0 +tabulate>=0.8.5 \ No newline at end of file -- cgit v1.2.3-18-g5258