diff --git a/README.md b/README.md index e69de29..6fe07a4 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,8 @@ +# pymilter-suspicious-from + +This is a very rough python milter implementation which is supposed to parse `From:` header values and mark suspicious ones. + +``` +From: "Totally Official " +``` + diff --git a/config.py b/config.py new file mode 100644 index 0000000..60b655c --- /dev/null +++ b/config.py @@ -0,0 +1,5 @@ +import logging + +log_level = logging.INFO +milter_socket = "inet:7777@127.0.0.1" +milter_timeout = 60 diff --git a/main.py b/main.py index df96d9b..3dee136 100644 --- a/main.py +++ b/main.py @@ -1,31 +1,33 @@ -import time import sys import logging import Milter -import re - from email.header import decode_header from email.utils import getaddresses +import re + +import config # Basic logger that also logs to stdout # TODO: Improve this a lot. logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) +logger.setLevel(config.log_level) handler = logging.StreamHandler(sys.stdout) -handler.setLevel(logging.DEBUG) +handler.setLevel(config.log_level) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) +# Rough regex to fetch domain values from address-like text address_domain_regex = re.compile('.*@(?P[\.\w-]+)') def get_decoded_header(value): + """Use python builtins to decode from header properly.""" decoded_header_items = decode_header(value) decoded_header_value = '' for item in decoded_header_items: @@ -37,6 +39,7 @@ def get_decoded_header(value): def normalizeRawFromHeader(value): + """Clean up linebreaks and spaces that are not needed.""" return value.replace('\n', '').replace('\r', '').strip() @@ -53,6 +56,8 @@ class SuspiciousFrom(Milter.Base): logger.debug(f"({self.id}) Instanciated.") def reset(self): + """It looks like one milter instance can reach eom hook multiple times. + This allows to re-use an instance in a more clean way.""" self.final_result = Milter.ACCEPT self.new_headers = [] @@ -69,21 +74,25 @@ class SuspiciousFrom(Milter.Base): if data[0] == '': logger.info(f"({self.id}) No label in from header, OK!") self.new_headers.append({'name': 'X-From-Checked', 'value': 'OK - No label specified'}) + self.new_headers.append({'name': 'X-From-Suspicious', 'value': 'NO'}) else: label_domain = getDomainFromValue(data[0]) address_domain = getDomainFromValue(data[1]) - logger.info(f"({self.id})Extracted label_domain '{label_domain}' and address_domain '{address_domain}'") + logger.info(f"({self.id}) Extracted label_domain '{label_domain}' and address_domain '{address_domain}'") if label_domain is not None: logger.debug(f"({self.id}) Label '{data[0]}' contains an address with domain '{label_domain}'.") if label_domain.lower() == address_domain.lower(): logger.info(f"({self.id}) Label domain '{label_domain}' matches address domain '{address_domain}'. Good!") self.new_headers.append({'name': 'X-From-Checked', 'value': 'OK - Label domain matches address domain'}) + self.new_headers.append({'name': 'X-From-Suspicious', 'value': 'NO'}) else: logger.info(f"({self.id}) Label domain '{label_domain}' did NOT match address domain '{address_domain}'. BAD!") self.new_headers.append({'name': 'X-From-Checked', 'value': 'FAIL - Label domain does NOT match address domain'}) + self.new_headers.append({'name': 'X-From-Suspicious', 'value': 'YES'}) else: logger.info(f"({self.id}) No domain found in label. Good!") self.new_headers.append({'name': 'X-From-Checked', 'value': 'OK - No domain found in label.'}) + self.new_headers.append({'name': 'X-From-Suspicious', 'value': 'NO'}) # Use continue here, so we can reach eom hook. # TODO: Log and react if multiple From-headers are found? return Milter.CONTINUE @@ -100,13 +109,11 @@ class SuspiciousFrom(Milter.Base): def main(): # TODO: Move this into configuration of some sort. - milter_socket = "inet:7777@127.0.0.1" - milter_timeout = 60 Milter.factory = SuspiciousFrom logger.info(f"Starting Milter.") # This call blocks the main thread. # TODO: Improve handling CTRL+C - Milter.runmilter("SuspiciousFromMilter", milter_socket, milter_timeout, rmsock=False) + Milter.runmilter("SuspiciousFromMilter", config.milter_socket, config.milter_timeout, rmsock=False) logger.info(f"Milter finished running.")