From e1709f763f23420e29204e6e7f5d5994e591f98b Mon Sep 17 00:00:00 2001 From: Thomas Oettli Date: Mon, 13 Sep 2021 14:47:13 +0200 Subject: [PATCH] fix actions, storages and mailer shutdown --- pymodmilter/action.py | 24 ++++++++++++--------- pymodmilter/notify.py | 49 +++++++++++++++++++++--------------------- pymodmilter/run.py | 4 +++- pymodmilter/storage.py | 26 ++++++++++++++-------- 4 files changed, 58 insertions(+), 45 deletions(-) diff --git a/pymodmilter/action.py b/pymodmilter/action.py index 5daabde..b3c09e3 100644 --- a/pymodmilter/action.py +++ b/pymodmilter/action.py @@ -20,7 +20,7 @@ import os from pymodmilter import CustomLogger, BaseConfig from pymodmilter.conditions import ConditionsConfig, Conditions -from pymodmilter import modify, storage +from pymodmilter import modify, notify, storage class ActionConfig(BaseConfig): @@ -133,22 +133,26 @@ class ActionConfig(BaseConfig): elif self["type"] == "notify": self["headersonly"] = False + self["class"] = notify.EMailNotification - self.add_string_arg( - cfg, ["smtp_host", "envelope_from", "from", "subject", - "template"]) + args = ["smtp_host", "envelope_from", "from_header", "subject", + "template"] + if "repl_img" in cfg: + args.append("repl_img") + + self.add_string_arg(cfg, args) self.add_int_arg(cfg, "smtp_port") - if "embedded_imgs" in cfg: - assert isinstance(cfg["embedded_imgs"], list), \ - f"{self['name']}: embedded_imgs: invalid value, " \ + if "embed_imgs" in cfg: + assert isinstance(cfg["embed_imgs"], list), \ + f"{self['name']}: embed_imgs: invalid value, " \ f"should be list" - for img in cfg["embedded_imgs"]: + for img in cfg["embed_imgs"]: assert isinstance(img, str), \ - f"{self['name']}: embedded_imgs: invalid entry, " \ + f"{self['name']}: embed_imgs: invalid entry, " \ f"should be string" - self["args"]["embedded_imgs"] = cfg["embedded_imgs"] + self["args"]["embed_imgs"] = cfg["embed_imgs"] else: raise RuntimeError(f"{self['name']}: type: invalid action type") diff --git a/pymodmilter/notify.py b/pymodmilter/notify.py index 2602d61..e517b6d 100644 --- a/pymodmilter/notify.py +++ b/pymodmilter/notify.py @@ -28,18 +28,14 @@ from urllib.parse import quote from pymodmilter import mailer -class BaseNotification(object): +class BaseNotification: "Notification base class" def __init__(self): + self.logger = logging.getLogger(__name__) return - def notify(self, msg, qid, mailfrom, recipients, - template_vars=defaultdict(str), synchronous=False): - return - - def execute(self, milter, pretend=False, - logger=logging.getLogger(__name__)): + def execute(self, milter, pretend=False, logger=None): return @@ -233,9 +229,6 @@ class EMailNotification(BaseNotification): template_vars=defaultdict(str), synchronous=False, logger=None): "Notify recipients via email." - super().notify( - msg, qid, mailfrom, recipients, template_vars, synchronous, logger) - if logger is None: logger = self.logger @@ -274,7 +267,7 @@ class EMailNotification(BaseNotification): "HTML_TEXT": sanitized_text, "FROM": escape(msg["from"], quote=False), "ENVELOPE_FROM": escape(mailfrom, quote=False), - "EMAIL_ENVELOPE_FROM_URL": escape(quote(mailfrom), + "ENVELOPE_FROM_URL": escape(quote(mailfrom), quote=False), "TO": escape(msg["to"], quote=False), "ENVELOPE_TO": escape(recipient, quote=False), @@ -284,40 +277,46 @@ class EMailNotification(BaseNotification): # parse template htmltext = self.template.format_map(variables) - msg = MIMEMultipart('related') - msg["From"] = self.from_header.format_map( - defaultdict(str, EMAIL_FROM=msg["from"])) - msg["To"] = msg["to"] - msg["Subject"] = self.subject.format_map( - defaultdict(str, EMAIL_SUBJECT=msg["subject"])) - msg["Date"] = email.utils.formatdate() - msg.attach(MIMEText(htmltext, "html", 'UTF-8')) + newmsg = MIMEMultipart('related') + newmsg["From"] = self.from_header.format_map( + defaultdict(str, FROM=msg["from"])) + newmsg["To"] = msg["to"] + newmsg["Subject"] = self.subject.format_map( + defaultdict(str, SUBJECT=msg["subject"])) + newmsg["Date"] = email.utils.formatdate() + newmsg.attach(MIMEText(htmltext, "html", 'UTF-8')) if image_replaced: logger.debug("attaching notification_replacement_img") - msg.attach(self.replacement_img) + newmsg.attach(self.replacement_img) for img in self.embed_imgs: logger.debug("attaching imgage") - msg.attach(img) + newmsg.attach(img) logger.debug(f"sending notification email to: {recipient}") if synchronous: try: mailer.smtp_send(self.smtp_host, self.smtp_port, - self.mailfrom, recipient, msg.as_string()) + self.mailfrom, recipient, + newmsg.as_string()) except Exception as e: raise RuntimeError( f"error while sending email to '{recipient}': {e}") else: mailer.sendmail(self.smtp_host, self.smtp_port, qid, - self.mailfrom, recipient, msg.as_string(), + self.mailfrom, recipient, newmsg.as_string(), "notification email") def execute(self, milter, pretend=False, - logger=logging.getLogger(__name__)): + logger=None): + super().execute(milter, pretend, logger) + + if logger is None: + logger = self.logger + self.notify(msg=milter.msg, qid=milter.qid, mailfrom=milter.msginfo["mailfrom"], recipients=milter.msginfo["rcpts"], - template_vars=milter["msginfo"]["vars"], + template_vars=milter.msginfo["vars"], logger=logger) diff --git a/pymodmilter/run.py b/pymodmilter/run.py index 3d4c462..a3a64d8 100644 --- a/pymodmilter/run.py +++ b/pymodmilter/run.py @@ -20,6 +20,7 @@ import logging import logging.handlers import sys +from pymodmilter import mailer from pymodmilter import ModifyMilterConfig, ModifyMilter from pymodmilter import __version__ as version @@ -140,11 +141,12 @@ def main(): rc = 0 try: Milter.runmilter("pymodmilter", socketname=socket, timeout=600) - logger.info("pymodmilter stopped") except Milter.milter.error as e: logger.error(e) rc = 255 + mailer.queue.put(None) + logger.info("pymodmilter stopped") sys.exit(rc) diff --git a/pymodmilter/storage.py b/pymodmilter/storage.py index f4817fc..93fb518 100644 --- a/pymodmilter/storage.py +++ b/pymodmilter/storage.py @@ -22,7 +22,7 @@ from glob import glob from time import gmtime -class BaseMailStorage(object): +class BaseMailStorage: "Mail storage base class" def __init__(self): return @@ -69,7 +69,7 @@ class FileMailStorage(BaseMailStorage): def _get_file_paths(self, storage_id): datafile = os.path.join(self.directory, storage_id) - metafile = f"{datafile}${self._metadata_suffix}" + metafile = f"{datafile}{self._metadata_suffix}" return metafile, datafile def _save_datafile(self, datafile, data): @@ -90,7 +90,9 @@ class FileMailStorage(BaseMailStorage): metafile, datafile = self._get_file_paths(storage_id) try: - os.remove(metafile) + if not self.skip_metadata: + os.remove(metafile) + os.remove(datafile) except IOError as e: raise RuntimeError(f"unable to remove file: {e}") @@ -105,7 +107,9 @@ class FileMailStorage(BaseMailStorage): # save mail self._save_datafile(datafile, data) - if not self.skip_metadata: + if self.skip_metadata: + metafile = None + else: # save metadata metadata = { "mailfrom": mailfrom, @@ -146,13 +150,17 @@ class FileMailStorage(BaseMailStorage): if self.metavar: milter.msginfo["vars"][f"{self.metavar}_ID"] = storage_id - milter.msginfo["vars"][f"{self.metavar}_METAFILE"] = metafile milter.msginfo["vars"][f"{self.metavar}_DATAFILE"] = datafile + if not self.skip_metadata: + milter.msginfo["vars"][f"{self.metavar}_METAFILE"] = metafile def get_metadata(self, storage_id): "Return metadata of email in storage." super(FileMailStorage, self).get_metadata(storage_id) + if self.skip_metadata: + return None + metafile, _ = self._get_file_paths(storage_id) if not os.path.isfile(metafile): raise RuntimeError( @@ -211,7 +219,7 @@ class FileMailStorage(BaseMailStorage): "Delete email from storage." super(FileMailStorage, self).delete(storage_id, recipients) - if not recipients: + if not recipients or self.skip_metadata: self._remove(storage_id) return @@ -238,9 +246,9 @@ class FileMailStorage(BaseMailStorage): super(FileMailStorage, self).get_mail(storage_id) metadata = self.get_metadata(storage_id) - datafile = os.path.join(self.directory, storage_id) + _, datafile = self._get_file_paths(storage_id) try: - fp = open(datafile, "rb") + data = open(datafile, "rb").read() except IOError as e: raise RuntimeError(f"unable to open email data file: {e}") - return (fp, metadata) + return (data, metadata)