Switch back to email.header lib because of error handling

This commit is contained in:
2020-03-18 16:12:43 +01:00
parent 62311612cd
commit 15ec705cb1
2 changed files with 55 additions and 37 deletions

View File

@@ -23,12 +23,33 @@ 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.charset import Charset
from email.parser import HeaderParser from email.header import Header, decode_header
from email.policy import default as default_policy
from netaddr import IPAddress, IPNetwork, AddrFormatError from netaddr import IPAddress, IPNetwork, AddrFormatError
def make_header(decoded_seq, maxlinelen=None, header_name=None,
continuation_ws=' ', errors='strict'):
"""Create a Header from a sequence of pairs as returned by decode_header()
decode_header() takes a header value string and returns a sequence of
pairs of the format (decoded_string, charset) where charset is the string
name of the character set.
This function takes one of those sequence of pairs and returns a Header
instance. Optional maxlinelen, header_name, and continuation_ws are as in
the Header constructor.
"""
h = Header(maxlinelen=maxlinelen, header_name=header_name,
continuation_ws=continuation_ws)
for s, charset in decoded_seq:
# None means us-ascii but we can simply pass it on to h.append()
if charset is not None and not isinstance(charset, Charset):
charset = Charset(charset)
h.append(s, charset, errors=errors)
return h
class HeaderRule: class HeaderRule:
"""HeaderRule to implement a rule to apply on e-mail headers.""" """HeaderRule to implement a rule to apply on e-mail headers."""
@@ -145,19 +166,19 @@ class HeaderRule:
else: else:
occurrences[name] += 1 occurrences[name] += 1
value = header[name]
# check if header line matches regex # check if header line matches regex
if self.header.search(f"{name}: {value}"): header_line = str(header)
if self.header.search(header_line):
value = header_line.split(":", 1)[1].strip()
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) new_value = self.search.sub(self.value, value)
if value != new_value: if value != new_value:
header = EmailMessage(policy=default_policy) header = make_header(
# Remove line breaks, EmailMessage object decode_header(
#does not like them f"{name}: {new_value}"), errors="replace")
header.add_header(name, " ".join(new_value.splitlines()))
modified.append((name, header, index, occurrences[name])) modified.append((name, header, index, occurrences[name]))
index += 1 index += 1
return modified return modified
@@ -215,15 +236,18 @@ class HeaderMilter(Milter.Base):
self.headers = [] self.headers = []
return Milter.CONTINUE return Milter.CONTINUE
@Milter.noreply
def header(self, name, value): def header(self, name, value):
try:
# remove surrogates from value # remove surrogates from value
value = value.encode(errors="surrogateescape").decode(errors="replace") value = value.encode(errors="surrogateescape").decode(errors="replace")
self.logger.debug(f"{self.qid}: received header: {name}: {value}") self.logger.debug(f"{self.qid}: received header: {name}: {value}")
header = HeaderParser( header = make_header(decode_header(f"{name}: {value}"), errors="replace")
policy=default_policy).parsestr(f"{name}: {value}")
self.logger.debug( self.logger.debug(
f"{self.qid}: decoded header: {name}: {header[name]}") f"{self.qid}: decoded header: {header}")
except Exception as e:
self.logger.exception(
f"an exception occured in header function: {e}")
return Milter.TEMPFAIL
self.headers.append((name, header)) self.headers.append((name, header))
return Milter.CONTINUE return Milter.CONTINUE
@@ -232,51 +256,45 @@ class HeaderMilter(Milter.Base):
for rule in self.rules: for rule in self.rules:
self.logger.debug(f"{self.qid}: executing rule '{rule.name}'") self.logger.debug(f"{self.qid}: executing rule '{rule.name}'")
modified = rule.execute(self.headers) modified = rule.execute(self.headers)
for name, header, index, occurrence in modified: for name, header, index, occurrence in modified:
value = header[name] header_line = str(header)
# remove illegal characters, pymilter does not like them value = header.encode().split(":", 1)[1].strip()
enc_value = header.as_string().replace(
"\r", "").replace(
"\n", "").replace(
"\x00", "").split(
":", 1)[1].strip()
mod_header = f"{name}: {value}"
if rule.action == "add": if rule.action == "add":
if rule.log: if rule.log:
self.logger.info( self.logger.info(
f"{self.qid}: add: header: {mod_header[0:70]}") f"{self.qid}: add: header: "
f"{header_line[0:70]}")
else: else:
self.logger.debug( self.logger.debug(
f"{self.qid}: add: header: {mod_header}") f"{self.qid}: add: header: "
f"{header_line}")
self.headers.insert(0, (name, header)) self.headers.insert(0, (name, header))
self.addheader(name, enc_value, 1) self.addheader(name, value, 1)
else: else:
if rule.action == "mod": if rule.action == "mod":
old_value = self.headers[index][1][name] old_header = str(self.headers[index][1])
old_header = f"{name}: {old_value}"
if rule.log: if rule.log:
self.logger.info( self.logger.info(
f"{self.qid}: modify: header: " f"{self.qid}: modify: header: "
f"{old_header[0:70]}: {mod_header[0:70]}") f"{old_header[0:70]}: {header_line[0:70]}")
else: else:
self.logger.debug( self.logger.debug(
f"{self.qid}: modify: header " f"{self.qid}: modify: header "
f"(occ. {occurrence}): {old_header}: " f"(occ. {occurrence}): {old_header}: "
f"{mod_header}") f"{header_line}")
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: " f"{self.qid}: delete: header: "
f"{mod_header[0:70]}") f"{header_line[0:70]}")
else: else:
self.logger.debug( self.logger.debug(
f"{self.qid}: delete: header " f"{self.qid}: delete: header "
f"(occ. {occurrence}): {mod_header}") f"(occ. {occurrence}): {header_line}")
del self.headers[index] del self.headers[index]
self.chgheader(name, occurrence, enc_value) self.chgheader(name, occurrence, value)
return Milter.ACCEPT return Milter.ACCEPT
except Exception as e: except Exception as e:
self.logger.exception( self.logger.exception(

View File

@@ -5,7 +5,7 @@ def read_file(fname):
return f.read() return f.read()
setup(name = "pyheadermilter", setup(name = "pyheadermilter",
version = "0.0.6", version = "0.0.7",
author = "Thomas Oettli", author = "Thomas Oettli",
author_email = "spacefreak@noop.ch", author_email = "spacefreak@noop.ch",
description = "A pymilter based sendmail/postfix pre-queue filter.", description = "A pymilter based sendmail/postfix pre-queue filter.",