Decode headers before processing

This commit is contained in:
2020-02-27 16:37:50 +01:00
parent 13ca896a0f
commit c1ad068197

View File

@@ -25,6 +25,8 @@ import re
import sys import sys
from Milter.utils import parse_addr from Milter.utils import parse_addr
from email.message import EmailMessage
from email.policy import default as default_policy
from netaddr import IPAddress, IPNetwork, AddrFormatError from netaddr import IPAddress, IPNetwork, AddrFormatError
@@ -34,7 +36,7 @@ class HeaderRule:
def __init__(self, name, action, header, search="", value="", ignore_hosts=[], ignore_envfrom=None, only_hosts=[], log=True): def __init__(self, name, action, header, search="", value="", 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
self.header = header self.header = header
self.search = search self.search = search
self.value = value self.value = value
@@ -79,12 +81,6 @@ class HeaderRule:
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))
def get_action(self):
return self._action
def log_modification(self):
return self.log
def ignore_host(self, host): def ignore_host(self, host):
ip = IPAddress(host) ip = IPAddress(host)
ignore = False ignore = False
@@ -118,7 +114,7 @@ class HeaderRule:
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)]
modified = [] modified = []
@@ -126,7 +122,7 @@ class HeaderRule:
occurrences = {} occurrences = {}
# iterate headers # iterate headers
for name, value in headers: for name, hdr in headers:
# keep track of the occurrence of each header, needed by Milter.Base.chgheader # 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
@@ -134,14 +130,18 @@ class HeaderRule:
occurrences[name] += 1 occurrences[name] += 1
# check if header line matches regex # check if header line matches regex
value = hdr[name]
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 # set an empty value to delete the header
new_value = "" new_value = ""
else: else:
str(hdr).split(": ", 1)[1].strip()
new_value = self.search.sub(self.value, value) new_value = self.search.sub(self.value, value)
if value != new_value: if value != new_value:
modified.append((name, new_value, index, occurrences[name])) hdr = EmailMessage(policy=default_policy)
hdr[name] = new_value
modified.append((name, hdr, index, occurrences[name]))
index += 1 index += 1
return modified return modified
@@ -194,7 +194,11 @@ class HeaderMilter(Milter.Base):
@Milter.noreply @Milter.noreply
def header(self, name, value): def header(self, name, value):
self.headers.append((name, value.encode(errors="surrogateescape").decode(errors="replace"))) # remove surrogates from value
value = value.encode(errors="surrogateescape").decode(errors="replace")
hdr = EmailMessage(policy=default_policy)
hdr[name] = value
self.headers.append((name, hdr))
return Milter.CONTINUE return Milter.CONTINUE
def eom(self): def eom(self):
@@ -202,36 +206,37 @@ class HeaderMilter(Milter.Base):
for rule in self.rules: for rule in self.rules:
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, hdr, index, occurrence in modified:
value = hdr[name]
encoded_value = bytes(header).decode().split(": ")[1].rstrip()
mod_header = "{}: {}".format(name, value) mod_header = "{}: {}".format(name, value)
if action == "add": if rule.action == "add":
if log: if rule.log:
self.logger.info("{}: add: header: {}".format(self.queueid, mod_header[0:70])) self.logger.info("{}: add: header: {}".format(self.queueid, mod_header[0:70]))
else: else:
self.logger.debug("{}: add: header: {}".format(self.queueid, mod_header)) self.logger.debug("{}: add: header: {}".format(self.queueid, mod_header))
self.headers.insert(0, (name, hdr))
self.headers.insert(0, (name, value)) self.addheader(name, encoded_value, 1)
self.addheader(name, value, 1)
else: else:
if action == "mod": if rule.action == "mod":
old_header = "{}: {}".format(name, self.headers[index][1]) old_value = self.headers[index][1][name]
if log: old_header = "{}: {}".format(name, old_value)
if rule.log:
self.logger.info("{}: modify: header: {}: {}".format( self.logger.info("{}: modify: header: {}: {}".format(
self.queueid, old_header[0:70], mod_header[0:70])) self.queueid, old_header[0:70], mod_header[0:70]))
else: else:
self.logger.debug("{}: modify: header (occ. {}): {}: {}".format( self.logger.debug("{}: modify: header (occ. {}): {}: {}".format(
self.queueid, occurrence, old_header, mod_header)) self.queueid, occurrence, old_header, mod_header))
self.headers[index] = (name, value) self.headers[index] = (name, hdr)
elif action == "del": elif rule.action == "del":
if log: if rule.log:
self.logger.info("{}: delete: header: {}".format(self.queueid, mod_header[0:70])) self.logger.info("{}: delete: header: {}".format(self.queueid, mod_header[0:70]))
else: else:
self.logger.debug("{}: delete: header (occ. {}): {}".format(self.queueid, occurrence, mod_header)) self.logger.debug("{}: delete: header (occ. {}): {}".format(self.queueid, occurrence, mod_header))
del self.headers[index] del self.headers[index]
self.chgheader(name, occurrence, value)
self.chgheader(name, occurrence, encoded_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))