10 Commits
2.0.2 ... 2.0.5

7 changed files with 91 additions and 81 deletions

View File

@@ -2,7 +2,7 @@
# Distributed under the terms of the GNU General Public License v2 # Distributed under the terms of the GNU General Public License v2
EAPI=7 EAPI=7
PYTHON_COMPAT=( python3_{9,10} ) PYTHON_COMPAT=( python3_{9..10} )
DISTUTILS_USE_SETUPTOOLS=rdepend DISTUTILS_USE_SETUPTOOLS=rdepend
SCM="" SCM=""
@@ -37,7 +37,7 @@ RDEPEND="
lxml? ( dev-python/lxml[${PYTHON_USEDEP}] ) lxml? ( dev-python/lxml[${PYTHON_USEDEP}] )
dev-python/netaddr[${PYTHON_USEDEP}] dev-python/netaddr[${PYTHON_USEDEP}]
dev-python/peewee[${PYTHON_USEDEP}] dev-python/peewee[${PYTHON_USEDEP}]
dev-python/pymilter[${PYTHON_USEDEP}]" >=dev-python/pymilter-1.5[${PYTHON_USEDEP}]"
python_install_all() { python_install_all() {
distutils-r1_python_install_all distutils-r1_python_install_all

View File

@@ -27,7 +27,7 @@ __all__ = [
"whitelist", "whitelist",
"QuarantineMilter"] "QuarantineMilter"]
__version__ = "2.0.2" __version__ = "2.0.5"
from pyquarantine import _runtime_patches from pyquarantine import _runtime_patches
@@ -100,7 +100,7 @@ class QuarantineMilter(Milter.Base):
self.logger.warning(f"unable to serialize message as bytes: {e}") self.logger.warning(f"unable to serialize message as bytes: {e}")
try: try:
self.logger.warning("try to serialize as str and encode") self.logger.warning("try to serialize as str and encode")
data = self.msg.as_string().encode("ascii", errors="replace") data = self.msg.as_string().encode(errors="replace")
except Exception as e: except Exception as e:
self.logger.error( self.logger.error(
f"unable to serialize message, giving up: {e}") f"unable to serialize message, giving up: {e}")
@@ -208,6 +208,7 @@ class QuarantineMilter(Milter.Base):
return Milter.CONTINUE return Milter.CONTINUE
@Milter.decode("replace")
def envfrom(self, mailfrom, *str): def envfrom(self, mailfrom, *str):
try: try:
self.mailfrom = "@".join(parse_addr(mailfrom)).lower() self.mailfrom = "@".join(parse_addr(mailfrom)).lower()
@@ -219,6 +220,7 @@ class QuarantineMilter(Milter.Base):
return Milter.CONTINUE return Milter.CONTINUE
@Milter.decode("replace")
def envrcpt(self, to, *str): def envrcpt(self, to, *str):
try: try:
self.rcpts.add("@".join(parse_addr(to)).lower()) self.rcpts.add("@".join(parse_addr(to)).lower())
@@ -243,6 +245,7 @@ class QuarantineMilter(Milter.Base):
return Milter.CONTINUE return Milter.CONTINUE
@Milter.decode("replace")
def header(self, field, value): def header(self, field, value):
try: try:
# remove CR and LF from address fields, otherwise pythons # remove CR and LF from address fields, otherwise pythons
@@ -258,11 +261,7 @@ class QuarantineMilter(Milter.Base):
v = v.replace("\r", "").replace("\n", "") v = v.replace("\r", "").replace("\n", "")
value = Header(s=v).encode() value = Header(s=v).encode()
# remove surrogates self.fp.write(field.encode() + b": " + value.encode() + b"\r\n")
field = field.encode("ascii", errors="replace")
value = value.encode("ascii", errors="replace")
self.fp.write(field + b": " + value + b"\r\n")
except Exception as e: except Exception as e:
self.logger.exception( self.logger.exception(
f"an exception occured in header method: {e}") f"an exception occured in header method: {e}")

View File

@@ -21,14 +21,18 @@ import shutil
import sys import sys
SYSTEMD_PATH = "/lib/systemd/system" SYSTEMD_PATHS = ["/lib/systemd/system", "/usr/lib/systemd/system"]
OPENRC = "/sbin/openrc" OPENRC = "/sbin/openrc"
def _systemd_files(pkg_dir, name): def _systemd_files(pkg_dir, name):
for path in SYSTEMD_PATHS:
if os.path.isdir(path):
break
return [ return [
(f"{pkg_dir}/misc/systemd/{name}-milter.service", (f"{pkg_dir}/misc/systemd/{name}-milter.service",
f"{SYSTEMD_PATH}/{name}-milter.service", True)] f"{path}/{name}-milter.service", True)]
def _openrc_files(pkg_dir, name): def _openrc_files(pkg_dir, name):
@@ -117,7 +121,11 @@ def _check_root():
def _check_systemd(): def _check_systemd():
systemd = os.path.isdir(SYSTEMD_PATH) for path in SYSTEMD_PATHS:
systemd = os.path.isdir(path)
if systemd:
break
if systemd: if systemd:
logging.info("systemd detected") logging.info("systemd detected")

View File

@@ -12,6 +12,7 @@
# along with pyquarantine. If not, see <http://www.gnu.org/licenses/>. # along with pyquarantine. If not, see <http://www.gnu.org/licenses/>.
# #
from sys import version_info
import encodings import encodings
@@ -150,27 +151,45 @@ def get_obs_local_part(value):
setattr(email._header_value_parser, "get_obs_local_part", get_obs_local_part) setattr(email._header_value_parser, "get_obs_local_part", get_obs_local_part)
# https://bugs.python.org/issue30681 #######################################
# add charset alias for windows-874 #
#######################################
# #
# fix: https://github.com/python/cpython/pull/22090 # https://bugs.python.org/issue17254
#
# fix: https://github.com/python/cpython/pull/10237
import email.errors aliases = encodings.aliases.aliases
from email.errors import HeaderDefect
for alias in ["windows-874", "windows_874"]:
if alias not in aliases:
aliases[alias] = "cp874"
setattr(encodings.aliases, "aliases", aliases)
class InvalidDateDefect(HeaderDefect): if version_info.major == 3 and version_info.minor < 10:
# https://bugs.python.org/issue30681
#
# fix: https://github.com/python/cpython/pull/22090
import email.errors
from email.errors import HeaderDefect
class InvalidDateDefect(HeaderDefect):
"""Header has unparseable or invalid date""" """Header has unparseable or invalid date"""
setattr(email.errors, "InvalidDateDefect", InvalidDateDefect) setattr(email.errors, "InvalidDateDefect", InvalidDateDefect)
import email.utils import email.utils
from email.utils import _parsedate_tz from email.utils import _parsedate_tz
import datetime import datetime
def parsedate_to_datetime(data): def parsedate_to_datetime(data):
parsed_date_tz = _parsedate_tz(data) parsed_date_tz = _parsedate_tz(data)
if parsed_date_tz is None: if parsed_date_tz is None:
raise ValueError('Invalid date value or format "%s"' % str(data)) raise ValueError('Invalid date value or format "%s"' % str(data))
@@ -181,14 +200,14 @@ def parsedate_to_datetime(data):
tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))
setattr(email.utils, "parsedate_to_datetime", parsedate_to_datetime) setattr(email.utils, "parsedate_to_datetime", parsedate_to_datetime)
import email.headerregistry import email.headerregistry
from email import utils, _header_value_parser as parser from email import utils, _header_value_parser as parser
@classmethod @classmethod
def parse(cls, value, kwds): def parse(cls, value, kwds):
if not value: if not value:
kwds['defects'].append(errors.HeaderMissingRequiredValue()) kwds['defects'].append(errors.HeaderMissingRequiredValue())
kwds['datetime'] = None kwds['datetime'] = None
@@ -209,21 +228,4 @@ def parse(cls, value, kwds):
kwds['parse_tree'] = cls.value_parser(kwds['decoded']) kwds['parse_tree'] = cls.value_parser(kwds['decoded'])
setattr(email.headerregistry.DateHeader, "parse", parse) setattr(email.headerregistry.DateHeader, "parse", parse)
#######################################
# add charset alias for windows-874 #
#######################################
#
# https://bugs.python.org/issue17254
#
# fix: https://github.com/python/cpython/pull/10237
aliases = encodings.aliases.aliases
for alias in ["windows-874", "windows_874"]:
if alias not in aliases:
aliases[alias] = "cp874"
setattr(encodings.aliases, "aliases", aliases)

View File

@@ -162,8 +162,9 @@ class MilterMessage(MIMEPart):
def inject_body_part(part, content, subtype="plain"): def inject_body_part(part, content, subtype="plain"):
parts = [] parts = []
text_body = None text_body = None
text_content = None
if subtype == "html": if subtype == "html":
text_body = part.get_body(preferencelist=("plain")) text_body, text_content = part.get_body_content("plain")
for p in part.iter_parts(): for p in part.iter_parts():
if text_body and p == text_body: if text_body and p == text_body:
@@ -173,8 +174,8 @@ def inject_body_part(part, content, subtype="plain"):
boundary = part.get_boundary() boundary = part.get_boundary()
p_subtype = part.get_content_subtype() p_subtype = part.get_content_subtype()
part.clear_content() part.clear_content()
if text_body: if text_content != None:
part.set_content(content) part.set_content(text_content)
part.add_alternative(content, subtype=subtype) part.add_alternative(content, subtype=subtype)
else: else:
part.set_content(content, subtype=subtype) part.set_content(content, subtype=subtype)

View File

@@ -135,7 +135,7 @@ def main():
sysloghandler = logging.handlers.SysLogHandler( sysloghandler = logging.handlers.SysLogHandler(
address="/dev/log", facility=logging.handlers.SysLogHandler.LOG_MAIL) address="/dev/log", facility=logging.handlers.SysLogHandler.LOG_MAIL)
sysloghandler.setFormatter( sysloghandler.setFormatter(
logging.Formatter("pyquarantine: %(message)s")) logging.Formatter(f"{name}[%(process)d]: %(message)s"))
root_logger.addHandler(sysloghandler) root_logger.addHandler(sysloghandler)
logger.info("milter starting") logger.info("milter starting")

View File

@@ -18,7 +18,7 @@ setup(name = "pyquarantine",
# 3 - Alpha # 3 - Alpha
# 4 - Beta # 4 - Beta
# 5 - Production/Stable # 5 - Production/Stable
"Development Status :: 4 - Beta", "Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python", "Programming Language :: Python",
@@ -48,6 +48,6 @@ setup(name = "pyquarantine",
] ]
) )
], ],
install_requires = ["pymilter", "jsonschema", "netaddr", "beautifulsoup4[lxml]", "peewee"], install_requires = ["pymilter >= 1.5", "jsonschema", "netaddr", "beautifulsoup4[lxml]", "peewee"],
python_requires = ">=3.8" python_requires = ">=3.9"
) )