Cleanup source code

This commit is contained in:
2019-09-05 13:57:13 +02:00
parent cb83cb378f
commit 2ce6b2350e

View File

@@ -29,38 +29,48 @@ from netaddr import IPAddress, IPNetwork, AddrFormatError
class HeaderRule: class HeaderRule:
def __init__(self, name, action, header, search=None, value=None, ignore_hosts=[], only_hosts=[], log=True): """HeaderRule to implement a rule to apply on e-mail headers."""
def __init__(self, name, action, header, search="", value="", ignore_hosts=[], 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
if action == "add":
self.header = header self.header = header
else: self.search = search
self.value = value
self.ignore_hosts = ignore_hosts
self.only_hosts = only_hosts
self.log = log
if action in ["del", "mod"]:
# compile header regex
try: try:
self.header = re.compile(header, re.MULTILINE + re.DOTALL + re.IGNORECASE) self.header = re.compile(header, re.MULTILINE + re.DOTALL + re.IGNORECASE)
except re.error as e: except re.error as e:
raise RuntimeError("unable to parse option 'header' of rule '{}': {}".format(name, e)) raise RuntimeError("unable to parse option 'header' of rule '{}': {}".format(name, e))
if action == "mod": if action == "mod":
# compile search regex
try: try:
self.search = re.compile(search, re.MULTILINE + re.DOTALL + re.IGNORECASE) self.search = re.compile(search, re.MULTILINE + re.DOTALL + re.IGNORECASE)
except re.error as e: except re.error as e:
raise RuntimeError("unable to parse option 'search' of rule '{}': {}".format(name, e)) raise RuntimeError("unable to parse option 'search' of rule '{}': {}".format(name, e))
else:
self.search = search if action in ["add", "mod"] and not value:
self.value = value raise RuntimeError("value of option 'value' is empty")
self.ignore_hosts = []
# replace strings in ignore_hosts and only_hosts with IPNetwork instances
try: try:
for ignore in ignore_hosts: for index, ignore in enumerate(ignore_hosts):
self.ignore_hosts.append(IPNetwork(ignore)) self.ignore_hosts[index] = IPNetwork(ignore)
except AddrFormatError as e: except AddrFormatError as e:
raise RuntimeError("unable to parse option 'ignore_hosts' of rule '{}': {}".format(name, e)) raise RuntimeError("unable to parse option 'ignore_hosts' of rule '{}': {}".format(name, e))
self.only_hosts = []
try: try:
for only in only_hosts: for index, only in enumerate(only_hosts):
self.only_hosts.append(IPNetwork(only)) self.only_hosts[index] = IPNetwork(only)
except AddrFormatError as e: except AddrFormatError as e:
raise RuntimeError("unable to parse option 'only_hosts' of rule '{}': {}".format(name, e)) raise RuntimeError("unable to parse option 'only_hosts' of rule '{}': {}".format(name, e))
self.log = log
def get_action(self): def get_action(self):
return self._action return self._action
@@ -71,34 +81,46 @@ class HeaderRule:
def ignore_host(self, host): def ignore_host(self, host):
ip = IPAddress(host) ip = IPAddress(host)
ignore = False ignore = False
# check if host matches ignore_hosts
for ignored in self.ignore_hosts: for ignored in self.ignore_hosts:
if ip in ignored: if ip in ignored:
ignore = True ignore = True
break break
if not ignore and self.only_hosts: if not ignore and self.only_hosts:
# host does not match ignore_hosts, check if it matches only_hosts
ignore = True ignore = True
for only in self.only_hosts: for only in self.only_hosts:
if ip in only: if ip in only:
ignore = False ignore = False
break break
if ignore: if ignore:
self.logger.debug("host {} is ignored by rule {}".format(host, self.name)) self.logger.debug("host {} is ignored by rule {}".format(host, self.name))
return ignore return ignore
def execute(self, headers): def execute(self, headers):
modified = [] """Execute rule on given headers and return list with modified headers."""
if self._action == "add": if self._action == "add":
modified.append((self.header, self.value, 0, 1)) return [(self.header, self.value, 0, 1)]
else:
modified = []
index = 0 index = 0
occurrences = {} occurrences = {}
# iterate headers
for name, value in headers: for name, value in headers:
# keep track of the occurrence of each header, needed by Milter.Base.chgheader
if name not in occurrences.keys(): if name not in occurrences.keys():
occurrences[name] = 1 occurrences[name] = 1
else: else:
occurrences[name] += 1 occurrences[name] += 1
# check if header line matches regex
if self.header.search("{}: {}".format(name, value)): if self.header.search("{}: {}".format(name, value)):
if self._action == "del": if self._action == "del":
# set an empty value to delete the header
value = "" value = ""
else: else:
value = self.search.sub(self.value, value) value = self.search.sub(self.value, value)
@@ -124,9 +146,12 @@ class HeaderMilter(Milter.Base):
def connect(self, IPname, family, hostaddr): def connect(self, IPname, family, hostaddr):
self.logger.debug("accepted milter connection from {} port {}".format(*hostaddr)) self.logger.debug("accepted milter connection from {} port {}".format(*hostaddr))
ip = IPAddress(hostaddr[0]) ip = IPAddress(hostaddr[0])
# remove rules which ignore this host
for rule in self.rules.copy(): for rule in self.rules.copy():
if rule.ignore_host(ip): if rule.ignore_host(ip):
self.rules.remove(rule) self.rules.remove(rule)
if not self.rules: if not self.rules:
self.logger.debug("host {} is ignored by all rules, skip further processing".format(hostaddr[0])) self.logger.debug("host {} is ignored by all rules, skip further processing".format(hostaddr[0]))
return Milter.ACCEPT return Milter.ACCEPT
@@ -147,20 +172,21 @@ class HeaderMilter(Milter.Base):
def eom(self): def eom(self):
try: try:
for rule in self.rules: for rule in self.rules:
action = rule.get_action()
log = rule.log_modification()
self.logger.debug("{}: executing rule '{}'".format(self.queueid, rule.name)) self.logger.debug("{}: executing rule '{}'".format(self.queueid, rule.name))
modified = rule.execute(self.headers) modified = rule.execute(self.headers)
action = rule.get_action()
log = rule.log_modification()
for name, value, index, occurrence in modified: for name, value, index, occurrence in modified:
if action == "add": if action == "add":
if log: if log:
self.logger.info("{}: add: header: {}".format(self.queueid, "{}: {}".format(name, value)[0:50])) self.logger.info("{}: add: header: {}".format(self.queueid, "{}: {}".format(name, value)[0:50]))
else: else:
self.logger.debug("{}: add: header: {}".format(self.queueid, "{}: {}".format(name, value)[0:50])) self.logger.debug("{}: add: header: {}".format(self.queueid, "{}: {}".format(name, value)[0:50]))
self.headers.insert(0, (name, value)) self.headers.insert(0, (name, value))
self.addheader(name, value, 1) self.addheader(name, value, 1)
else: else:
self.chgheader(name, occurrence, value)
if action == "mod": if action == "mod":
if log: if log:
self.logger.info("{}: modify: header: {}".format(self.queueid, "{}: {}".format(name, self.headers[index][1])[0:50])) self.logger.info("{}: modify: header: {}".format(self.queueid, "{}: {}".format(name, self.headers[index][1])[0:50]))
@@ -173,6 +199,7 @@ class HeaderMilter(Milter.Base):
else: else:
self.logger.debug("{}: delete: header (occ. {}): {}".format(self.queueid, occurrence, "{}: {}".format(name, self.headers[index][1])[0:50])) self.logger.debug("{}: delete: header (occ. {}): {}".format(self.queueid, occurrence, "{}: {}".format(name, self.headers[index][1])[0:50]))
del self.headers[index] del self.headers[index]
self.chgheader(name, occurrence, value)
return Milter.ACCEPT return Milter.ACCEPT
except Exception as e: except Exception as e:
self.logger.exception("an exception occured in eom function: {}".format(e)) self.logger.exception("an exception occured in eom function: {}".format(e))
@@ -293,6 +320,7 @@ def main():
config["log"] = False config["log"] = False
else: else:
raise RuntimeError("invalid value specified for option 'log' for rule '{}'".format(rule_name)) raise RuntimeError("invalid value specified for option 'log' for rule '{}'".format(rule_name))
# add rule # add rule
logging.debug("adding rule '{}'".format(rule_name)) logging.debug("adding rule '{}'".format(rule_name))
rules.append(HeaderRule(name=rule_name, **config)) rules.append(HeaderRule(name=rule_name, **config))