fix actions, storages and mailer shutdown

This commit is contained in:
2021-09-13 14:47:13 +02:00
parent 737a7b555b
commit e1709f763f
4 changed files with 58 additions and 45 deletions

View File

@@ -20,7 +20,7 @@ import os
from pymodmilter import CustomLogger, BaseConfig from pymodmilter import CustomLogger, BaseConfig
from pymodmilter.conditions import ConditionsConfig, Conditions from pymodmilter.conditions import ConditionsConfig, Conditions
from pymodmilter import modify, storage from pymodmilter import modify, notify, storage
class ActionConfig(BaseConfig): class ActionConfig(BaseConfig):
@@ -133,22 +133,26 @@ class ActionConfig(BaseConfig):
elif self["type"] == "notify": elif self["type"] == "notify":
self["headersonly"] = False self["headersonly"] = False
self["class"] = notify.EMailNotification
self.add_string_arg( args = ["smtp_host", "envelope_from", "from_header", "subject",
cfg, ["smtp_host", "envelope_from", "from", "subject", "template"]
"template"]) if "repl_img" in cfg:
args.append("repl_img")
self.add_string_arg(cfg, args)
self.add_int_arg(cfg, "smtp_port") self.add_int_arg(cfg, "smtp_port")
if "embedded_imgs" in cfg: if "embed_imgs" in cfg:
assert isinstance(cfg["embedded_imgs"], list), \ assert isinstance(cfg["embed_imgs"], list), \
f"{self['name']}: embedded_imgs: invalid value, " \ f"{self['name']}: embed_imgs: invalid value, " \
f"should be list" f"should be list"
for img in cfg["embedded_imgs"]: for img in cfg["embed_imgs"]:
assert isinstance(img, str), \ assert isinstance(img, str), \
f"{self['name']}: embedded_imgs: invalid entry, " \ f"{self['name']}: embed_imgs: invalid entry, " \
f"should be string" f"should be string"
self["args"]["embedded_imgs"] = cfg["embedded_imgs"] self["args"]["embed_imgs"] = cfg["embed_imgs"]
else: else:
raise RuntimeError(f"{self['name']}: type: invalid action type") raise RuntimeError(f"{self['name']}: type: invalid action type")

View File

@@ -28,18 +28,14 @@ from urllib.parse import quote
from pymodmilter import mailer from pymodmilter import mailer
class BaseNotification(object): class BaseNotification:
"Notification base class" "Notification base class"
def __init__(self): def __init__(self):
self.logger = logging.getLogger(__name__)
return return
def notify(self, msg, qid, mailfrom, recipients, def execute(self, milter, pretend=False, logger=None):
template_vars=defaultdict(str), synchronous=False):
return
def execute(self, milter, pretend=False,
logger=logging.getLogger(__name__)):
return return
@@ -233,9 +229,6 @@ class EMailNotification(BaseNotification):
template_vars=defaultdict(str), synchronous=False, template_vars=defaultdict(str), synchronous=False,
logger=None): logger=None):
"Notify recipients via email." "Notify recipients via email."
super().notify(
msg, qid, mailfrom, recipients, template_vars, synchronous, logger)
if logger is None: if logger is None:
logger = self.logger logger = self.logger
@@ -274,7 +267,7 @@ class EMailNotification(BaseNotification):
"HTML_TEXT": sanitized_text, "HTML_TEXT": sanitized_text,
"FROM": escape(msg["from"], quote=False), "FROM": escape(msg["from"], quote=False),
"ENVELOPE_FROM": escape(mailfrom, quote=False), "ENVELOPE_FROM": escape(mailfrom, quote=False),
"EMAIL_ENVELOPE_FROM_URL": escape(quote(mailfrom), "ENVELOPE_FROM_URL": escape(quote(mailfrom),
quote=False), quote=False),
"TO": escape(msg["to"], quote=False), "TO": escape(msg["to"], quote=False),
"ENVELOPE_TO": escape(recipient, quote=False), "ENVELOPE_TO": escape(recipient, quote=False),
@@ -284,40 +277,46 @@ class EMailNotification(BaseNotification):
# parse template # parse template
htmltext = self.template.format_map(variables) htmltext = self.template.format_map(variables)
msg = MIMEMultipart('related') newmsg = MIMEMultipart('related')
msg["From"] = self.from_header.format_map( newmsg["From"] = self.from_header.format_map(
defaultdict(str, EMAIL_FROM=msg["from"])) defaultdict(str, FROM=msg["from"]))
msg["To"] = msg["to"] newmsg["To"] = msg["to"]
msg["Subject"] = self.subject.format_map( newmsg["Subject"] = self.subject.format_map(
defaultdict(str, EMAIL_SUBJECT=msg["subject"])) defaultdict(str, SUBJECT=msg["subject"]))
msg["Date"] = email.utils.formatdate() newmsg["Date"] = email.utils.formatdate()
msg.attach(MIMEText(htmltext, "html", 'UTF-8')) newmsg.attach(MIMEText(htmltext, "html", 'UTF-8'))
if image_replaced: if image_replaced:
logger.debug("attaching notification_replacement_img") logger.debug("attaching notification_replacement_img")
msg.attach(self.replacement_img) newmsg.attach(self.replacement_img)
for img in self.embed_imgs: for img in self.embed_imgs:
logger.debug("attaching imgage") logger.debug("attaching imgage")
msg.attach(img) newmsg.attach(img)
logger.debug(f"sending notification email to: {recipient}") logger.debug(f"sending notification email to: {recipient}")
if synchronous: if synchronous:
try: try:
mailer.smtp_send(self.smtp_host, self.smtp_port, 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: except Exception as e:
raise RuntimeError( raise RuntimeError(
f"error while sending email to '{recipient}': {e}") f"error while sending email to '{recipient}': {e}")
else: else:
mailer.sendmail(self.smtp_host, self.smtp_port, qid, mailer.sendmail(self.smtp_host, self.smtp_port, qid,
self.mailfrom, recipient, msg.as_string(), self.mailfrom, recipient, newmsg.as_string(),
"notification email") "notification email")
def execute(self, milter, pretend=False, 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, self.notify(msg=milter.msg, qid=milter.qid,
mailfrom=milter.msginfo["mailfrom"], mailfrom=milter.msginfo["mailfrom"],
recipients=milter.msginfo["rcpts"], recipients=milter.msginfo["rcpts"],
template_vars=milter["msginfo"]["vars"], template_vars=milter.msginfo["vars"],
logger=logger) logger=logger)

View File

@@ -20,6 +20,7 @@ import logging
import logging.handlers import logging.handlers
import sys import sys
from pymodmilter import mailer
from pymodmilter import ModifyMilterConfig, ModifyMilter from pymodmilter import ModifyMilterConfig, ModifyMilter
from pymodmilter import __version__ as version from pymodmilter import __version__ as version
@@ -140,11 +141,12 @@ def main():
rc = 0 rc = 0
try: try:
Milter.runmilter("pymodmilter", socketname=socket, timeout=600) Milter.runmilter("pymodmilter", socketname=socket, timeout=600)
logger.info("pymodmilter stopped")
except Milter.milter.error as e: except Milter.milter.error as e:
logger.error(e) logger.error(e)
rc = 255 rc = 255
mailer.queue.put(None)
logger.info("pymodmilter stopped")
sys.exit(rc) sys.exit(rc)

View File

@@ -22,7 +22,7 @@ from glob import glob
from time import gmtime from time import gmtime
class BaseMailStorage(object): class BaseMailStorage:
"Mail storage base class" "Mail storage base class"
def __init__(self): def __init__(self):
return return
@@ -69,7 +69,7 @@ class FileMailStorage(BaseMailStorage):
def _get_file_paths(self, storage_id): def _get_file_paths(self, storage_id):
datafile = os.path.join(self.directory, 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 return metafile, datafile
def _save_datafile(self, datafile, data): def _save_datafile(self, datafile, data):
@@ -90,7 +90,9 @@ class FileMailStorage(BaseMailStorage):
metafile, datafile = self._get_file_paths(storage_id) metafile, datafile = self._get_file_paths(storage_id)
try: try:
if not self.skip_metadata:
os.remove(metafile) os.remove(metafile)
os.remove(datafile) os.remove(datafile)
except IOError as e: except IOError as e:
raise RuntimeError(f"unable to remove file: {e}") raise RuntimeError(f"unable to remove file: {e}")
@@ -105,7 +107,9 @@ class FileMailStorage(BaseMailStorage):
# save mail # save mail
self._save_datafile(datafile, data) self._save_datafile(datafile, data)
if not self.skip_metadata: if self.skip_metadata:
metafile = None
else:
# save metadata # save metadata
metadata = { metadata = {
"mailfrom": mailfrom, "mailfrom": mailfrom,
@@ -146,13 +150,17 @@ class FileMailStorage(BaseMailStorage):
if self.metavar: if self.metavar:
milter.msginfo["vars"][f"{self.metavar}_ID"] = storage_id 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 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): def get_metadata(self, storage_id):
"Return metadata of email in storage." "Return metadata of email in storage."
super(FileMailStorage, self).get_metadata(storage_id) super(FileMailStorage, self).get_metadata(storage_id)
if self.skip_metadata:
return None
metafile, _ = self._get_file_paths(storage_id) metafile, _ = self._get_file_paths(storage_id)
if not os.path.isfile(metafile): if not os.path.isfile(metafile):
raise RuntimeError( raise RuntimeError(
@@ -211,7 +219,7 @@ class FileMailStorage(BaseMailStorage):
"Delete email from storage." "Delete email from storage."
super(FileMailStorage, self).delete(storage_id, recipients) super(FileMailStorage, self).delete(storage_id, recipients)
if not recipients: if not recipients or self.skip_metadata:
self._remove(storage_id) self._remove(storage_id)
return return
@@ -238,9 +246,9 @@ class FileMailStorage(BaseMailStorage):
super(FileMailStorage, self).get_mail(storage_id) super(FileMailStorage, self).get_mail(storage_id)
metadata = self.get_metadata(storage_id) metadata = self.get_metadata(storage_id)
datafile = os.path.join(self.directory, storage_id) _, datafile = self._get_file_paths(storage_id)
try: try:
fp = open(datafile, "rb") data = open(datafile, "rb").read()
except IOError as e: except IOError as e:
raise RuntimeError(f"unable to open email data file: {e}") raise RuntimeError(f"unable to open email data file: {e}")
return (fp, metadata) return (data, metadata)