Small bugfix and code cleanup

This commit is contained in:
2020-03-01 22:25:13 +01:00
parent ad79efe139
commit 415c41ed7a

View File

@@ -33,7 +33,8 @@ class HeaderRule:
"""HeaderRule to implement a rule to apply on e-mail headers.""" """HeaderRule to implement a rule to apply on e-mail headers."""
def __init__(self, name, action, header, search="", value="", def __init__(self, name, action, header, search="", value="",
ignore_hosts=[], ignore_envfrom=None, only_hosts=[], log=True): ignore_hosts=[], ignore_envfrom=None, only_hosts=[],
log=True):
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.name = name self.name = name
self.action = action self.action = action
@@ -61,7 +62,8 @@ class HeaderRule:
search, re.MULTILINE + re.DOTALL + re.IGNORECASE) search, re.MULTILINE + re.DOTALL + re.IGNORECASE)
except re.error as e: except re.error as e:
raise RuntimeError( raise RuntimeError(
f"unable to parse option 'search' of rule '{name}': {e}") f"unable to parse option 'search' of "
f"rule '{name}': {e}")
if action in ["add", "mod"] and not value: if action in ["add", "mod"] and not value:
raise RuntimeError("value of option 'value' is empty") raise RuntimeError("value of option 'value' is empty")
@@ -80,7 +82,8 @@ class HeaderRule:
self.ignore_envfrom = re.compile(ignore_envfrom, re.IGNORECASE) self.ignore_envfrom = re.compile(ignore_envfrom, re.IGNORECASE)
except re.error as e: except re.error as e:
raise RuntimeError( raise RuntimeError(
f"unable to parse option 'ignore_envfrom' of rule '{name}': {e}") f"unable to parse option 'ignore_envfrom' of "
f"rule '{name}': {e}")
try: try:
for index, only in enumerate(only_hosts): for index, only in enumerate(only_hosts):
@@ -122,7 +125,10 @@ class HeaderRule:
return ignore return ignore
def execute(self, headers): def execute(self, headers):
"""Execute rule on given headers and return list with modified headers.""" """
Execute rule on given headers and return list
with modified headers.
"""
if self.action == "add": if self.action == "add":
return [(self.header, self.value, 0, 1)] return [(self.header, self.value, 0, 1)]
@@ -139,14 +145,18 @@ class HeaderRule:
else: else:
occurrences[name] += 1 occurrences[name] += 1
# check if header line matches regex
value = header[name] value = header[name]
# check if header line matches regex
if self.header.search(f"{name}: {value}"): if self.header.search(f"{name}: {value}"):
if self.action == "del": if self.action == "del":
# set an empty value to delete the header # set an empty value to delete the header
new_value = "" new_value = ""
else: else:
new_value = self.search.sub(self.value, value) # Remove line breaks from new_value, EmailMessage object
# does not like them
new_value = self.search.sub(self.value, value).replace(
"\n", "").replace(
"\r", "")
if value != new_value: if value != new_value:
header = EmailMessage(policy=default_policy) header = EmailMessage(policy=default_policy)
header.add_header(name, new_value) header.add_header(name, new_value)
@@ -171,7 +181,8 @@ class HeaderMilter(Milter.Base):
def connect(self, IPname, family, hostaddr): def connect(self, IPname, family, hostaddr):
self.logger.debug( self.logger.debug(
f"accepted milter connection from {hostaddr[0]} port {hostaddr[1]}") f"accepted milter connection from {hostaddr[0]} "
f"port {hostaddr[1]}")
ip = IPAddress(hostaddr[0]) ip = IPAddress(hostaddr[0])
# remove rules which ignore this host # remove rules which ignore this host
@@ -181,7 +192,8 @@ class HeaderMilter(Milter.Base):
if not self.rules: if not self.rules:
self.logger.debug( self.logger.debug(
f"host {hostaddr[0]} is ignored by all rules, skip further processing") f"host {hostaddr[0]} is ignored by all rules, "
f"skip further processing")
return Milter.ACCEPT return Milter.ACCEPT
return Milter.CONTINUE return Milter.CONTINUE
@@ -193,7 +205,8 @@ class HeaderMilter(Milter.Base):
if not self.rules: if not self.rules:
self.logger.debug( self.logger.debug(
f"mail from {mailfrom} is ignored by all rules, skip further processing") f"mail from {mailfrom} is ignored by all rules, "
f"skip further processing")
return Milter.ACCEPT return Milter.ACCEPT
return Milter.CONTINUE return Milter.CONTINUE
@@ -245,24 +258,31 @@ class HeaderMilter(Milter.Base):
old_value = self.headers[index][1][name] old_value = self.headers[index][1][name]
old_header = f"{name}: {old_value}" old_header = f"{name}: {old_value}"
if rule.log: if rule.log:
self.logger.info(f"{self.qid}: modify: header: {old_header[0:70]}: {mod_header[0:70]}") self.logger.info(
f"{self.qid}: modify: header: "
f"{old_header[0:70]}: {mod_header[0:70]}")
else: else:
self.logger.debug( self.logger.debug(
f"{self.qid}: modify: header (occ. {occurrence}): {old_header}: {mod_header}") f"{self.qid}: modify: header "
f"(occ. {occurrence}): {old_header}: "
f"{mod_header}")
self.headers[index] = (name, header) self.headers[index] = (name, header)
elif rule.action == "del": elif rule.action == "del":
if rule.log: if rule.log:
self.logger.info( self.logger.info(
f"{self.qid}: delete: header: {mod_header[0:70]}") f"{self.qid}: delete: header: "
f"{mod_header[0:70]}")
else: else:
self.logger.debug( self.logger.debug(
f"{self.qid}: delete: header (occ. {occurrence}): {mod_header}") f"{self.qid}: delete: header "
f"(occ. {occurrence}): {mod_header}")
del self.headers[index] del self.headers[index]
self.chgheader(name, occurrence, enc_value) self.chgheader(name, occurrence, enc_value)
return Milter.ACCEPT return Milter.ACCEPT
except Exception as e: except Exception as e:
self.logger.exception(f"an exception occured in eom function: {e}") self.logger.exception(
f"an exception occured in eom function: {e}")
return Milter.TEMPFAIL return Milter.TEMPFAIL
@@ -327,7 +347,8 @@ def main():
for option in ["rules"]: for option in ["rules"]:
if not parser.has_option("global", option): if not parser.has_option("global", option):
raise RuntimeError( raise RuntimeError(
f"mandatory option '{option}' not present in config section 'global'") f"mandatory option '{option}' not present in config "
f"section 'global'")
# read global config section # read global config section
global_config = dict(parser.items("global")) global_config = dict(parser.items("global"))
@@ -336,11 +357,13 @@ def main():
active_rules = [r.strip() for r in global_config["rules"].split(",")] active_rules = [r.strip() for r in global_config["rules"].split(",")]
if len(active_rules) != len(set(active_rules)): if len(active_rules) != len(set(active_rules)):
raise RuntimeError( raise RuntimeError(
"at least one rule is specified multiple times in 'rules' option") "at least one rule is specified multiple times "
"in 'rules' option")
if "global" in active_rules: if "global" in active_rules:
active_rules.remove("global") active_rules.remove("global")
logger.warning( logger.warning(
"removed illegal rule name 'global' from list of active rules") "removed illegal rule name 'global' from list of "
"active rules")
if not active_rules: if not active_rules:
raise RuntimeError("no rules configured") raise RuntimeError("no rules configured")
@@ -361,7 +384,8 @@ def main():
config[option] = global_config[option] config[option] = global_config[option]
if option not in config.keys(): if option not in config.keys():
raise RuntimeError( raise RuntimeError(
f"mandatory option '{option}' not specified for rule '{rule_name}'") f"mandatory option '{option}' not specified for "
f"rule '{rule_name}'")
config["action"] = config["action"].lower() config["action"] = config["action"].lower()
if config["action"] not in ["add", "del", "mod"]: if config["action"] not in ["add", "del", "mod"]:
raise RuntimeError( raise RuntimeError(
@@ -379,7 +403,8 @@ def main():
config[option] = global_config[option] config[option] = global_config[option]
if option not in config.keys(): if option not in config.keys():
raise RuntimeError( raise RuntimeError(
f"mandatory option '{option}' not specified for rule '{rule_name}'") f"mandatory option '{option}' not specified for "
f"rule '{rule_name}'")
# check if optional config options are present in config # check if optional config options are present in config
defaults = { defaults = {
@@ -395,11 +420,11 @@ def main():
if option not in config.keys(): if option not in config.keys():
config[option] = defaults[option] config[option] = defaults[option]
if config["ignore_hosts"]: if config["ignore_hosts"]:
config["ignore_hosts"] = [h.strip() config["ignore_hosts"] = [
for h in config["ignore_hosts"].split(",")] h.strip() for h in config["ignore_hosts"].split(",")]
if config["only_hosts"]: if config["only_hosts"]:
config["only_hosts"] = [h.strip() config["only_hosts"] = [
for h in config["only_hosts"].split(",")] h.strip() for h in config["only_hosts"].split(",")]
config["log"] = config["log"].lower() config["log"] = config["log"].lower()
if config["log"] == "true": if config["log"] == "true":
config["log"] = True config["log"] = True
@@ -407,7 +432,8 @@ def main():
config["log"] = False config["log"] = False
else: else:
raise RuntimeError( raise RuntimeError(
f"invalid value specified for option 'log' for rule '{rule_name}'") f"invalid value specified for option 'log' for "
f"rule '{rule_name}'")
# add rule # add rule
logging.debug(f"adding rule '{rule_name}'") logging.debug(f"adding rule '{rule_name}'")