Add dynamic template variables according to regex

This commit is contained in:
2019-05-01 13:02:41 +02:00
parent 4da5ee203d
commit f37b50eaac
4 changed files with 47 additions and 23 deletions

View File

@@ -79,6 +79,13 @@ The following configuration options are mandatory in each quarantine section:
* **{EMAIL_HTML_TEXT}**
Sanitized version of the e-mail text part of the original e-mail. Only harmless HTML tags and attributes are included. Images are replaced with the image set by notification_email_replacement_img option.
Some template variables are only available if the regex of the matching quarantine contains subgroups or named subgroups (python syntax). This is useful to include information (e.g. virus names, spam points, ...) of the matching header within the notification.
The following dynamic tempalte variables are available:
* **{SUBGROUP_n}**
Content of a subgroup, 'n' will be replaced by the index number of each subgroup, starting with 0.
* **{subgroup_name}**
Content of a named subgroup, 'subgroup_name' will be replaced by its name.
The following configuration options are mandatory for this notification type:
* **notification_email_from**
Notification e-mail from-address.

View File

@@ -119,9 +119,13 @@ class QuarantineMilter(Milter.Base):
# check email header against quarantine regex
self.logger.debug("{}: {}: checking header against regex '{}'".format(self.queueid, quarantine["name"], quarantine["regex"]))
if quarantine["regex_compiled"].match(header):
match = quarantine["regex_compiled"].search(header)
if match:
self.logger.debug("{}: {}: header matched regex".format(self.queueid, quarantine["name"]))
if "subgroups" not in quarantine.keys():
# save subgroups of match into the quarantine object for later use as template variables
quarantine["subgroups"] = match.groups(default="")
quarantine["named_subgroups"] = match.groupdict(default="")
# check for whitelisted recipients
whitelist = quarantine["whitelist_obj"]
if whitelist != None:
@@ -212,7 +216,8 @@ class QuarantineMilter(Milter.Base):
# add email to quarantine
self.logger.info("{}: adding to quarantine '{}' for: {}".format(self.queueid, quarantine["name"], ", ".join(recipients)))
try:
quarantine_id = quarantine["quarantine_obj"].add(self.queueid, self.mailfrom, recipients, self.subject, fp=self.fp)
quarantine_id = quarantine["quarantine_obj"].add(self.queueid, self.mailfrom, recipients, self.subject, self.fp,
quarantine["subgroups"], quarantine["named_subgroups"])
except RuntimeError as e:
self.logger.error("{}: unable to add to quarantine '{}': {}".format(self.queueid, quarantine["name"], e))
return Milter.TEMPFAIL
@@ -222,7 +227,8 @@ class QuarantineMilter(Milter.Base):
# notify
self.logger.info("{}: sending notification for quarantine '{}' to: {}".format(self.queueid, quarantine["name"], ", ".join(recipients)))
try:
quarantine["notification_obj"].notify(self.queueid, quarantine_id, self.subject, self.mailfrom, recipients, fp=self.fp)
quarantine["notification_obj"].notify(self.queueid, quarantine_id, self.subject, self.mailfrom, recipients, self.fp,
quarantine["subgroups"], quarantine["named_subgroups"])
except RuntimeError as e:
self.logger.error("{}: unable to send notification for quarantine '{}': {}".format(self.queueid, quarantine["name"], e))
return Milter.TEMPFAIL
@@ -316,7 +322,7 @@ def generate_milter_config(configtest=False, config_files=[]):
# pre-compile regex
logger.debug("{}: compiling regex '{}'".format(quarantine_name, config["regex"]))
config["regex_compiled"] = re.compile(config["regex"])
config["regex_compiled"] = re.compile(config["regex"], re.MULTILINE + re.DOTALL)
# create quarantine instance
quarantine_type = config["quarantine_type"].lower()

View File

@@ -33,7 +33,7 @@ class BaseNotification(object):
self.config = config
self.logger = logging.getLogger(__name__)
def notify(self, queueid, quarantine_id, subject, mailfrom, recipients, fp, synchronous=False):
def notify(self, queueid, quarantine_id, subject, mailfrom, recipients, fp, subgroups=None, named_subgroups=None, synchronous=False):
fp.seek(0)
pass
@@ -224,9 +224,9 @@ class EMailNotification(BaseNotification):
return soup
def notify(self, queueid, quarantine_id, subject, mailfrom, recipients, fp, synchronous=False):
def notify(self, queueid, quarantine_id, subject, mailfrom, recipients, fp, subgroups=None, named_subgroups=None, synchronous=False):
"Notify recipients via email."
super(EMailNotification, self).notify(queueid, quarantine_id, subject, mailfrom, recipients, fp, synchronous)
super(EMailNotification, self).notify(queueid, quarantine_id, subject, mailfrom, recipients, fp, subgroups, named_subgroups, synchronous)
# extract html text from email
self.logger.debug("{}: extraction email text from original email".format(queueid))
@@ -244,21 +244,29 @@ class EMailNotification(BaseNotification):
# sanitizing email text of original email
sanitized_text = self.sanitize(queueid, soup)
# escape possible html entities in subject
subject = escape(subject)
# sending email notifications
for recipient in recipients:
self.logger.debug("{}: generating notification email for '{}'".format(queueid, recipient))
self.logger.debug("{}: parsing email template".format(queueid))
htmltext = self.template.format( \
EMAIL_HTML_TEXT=sanitized_text, \
EMAIL_FROM=escape(mailfrom), \
EMAIL_TO=escape(recipient), \
EMAIL_SUBJECT=subject, \
EMAIL_QUARANTINE_ID=quarantine_id
)
# generate dict containing all template variables
variables = {
"EMAIL_HTML_TEXT": sanitized_text,
"EMAIL_FROM": escape(mailfrom),
"EMAIL_TO": escape(recipient),
"EMAIL_SUBJECT": escape(subject),
"EMAIL_QUARANTINE_ID": quarantine_id
}
if subgroups:
number = 0
for subgroup in subgroups:
variables["SUBGROUP_{}".format(number)] = escape(subgroup)
if named_subgroups:
for key, value in named_subgroups.items(): named_subgroups[key] = escape(value)
variables.update(named_subgroups)
# parse template
htmltext = self.template.format(**variables)
msg = MIMEMultipart('alternative')
msg["Subject"] = self.subject

View File

@@ -33,7 +33,7 @@ class BaseQuarantine(object):
self.config = config
self.logger = logging.getLogger(__name__)
def add(self, queueid, mailfrom, recipients, subject, fp):
def add(self, queueid, mailfrom, recipients, subject, fp, subgroups=None, named_subgroups=None):
"Add email to quarantine."
fp.seek(0)
return ""
@@ -109,9 +109,9 @@ class FileQuarantine(BaseQuarantine):
except IOError as e:
raise RuntimeError("unable to remove data file: {}".format(e))
def add(self, queueid, mailfrom, recipients, subject, fp):
def add(self, queueid, mailfrom, recipients, subject, fp, subgroups=None, named_subgroups=None):
"Add email to file quarantine and return quarantine-id."
super(FileQuarantine, self).add(queueid, mailfrom, recipients, subject, fp)
super(FileQuarantine, self).add(queueid, mailfrom, recipients, subject, fp, subgroups, named_subgroups)
quarantine_id = "{}_{}".format(datetime.now().strftime("%Y%m%d%H%M%S"), queueid)
# save mail
@@ -123,7 +123,9 @@ class FileQuarantine(BaseQuarantine):
"recipients": recipients,
"subject": subject,
"date": timegm(gmtime()),
"queue_id": queueid
"queue_id": queueid,
"subgroups": subgroups,
"named_subgroups": named_subgroups
}
try:
self._save_metafile(quarantine_id, metadata)
@@ -223,7 +225,8 @@ class FileQuarantine(BaseQuarantine):
datafile = os.path.join(self.directory, quarantine_id)
try:
with open(datafile, "rb") as fp:
self.config["notification_obj"].notify(metadata["queue_id"], quarantine_id, metadata["subject"], metadata["mailfrom"], recipients, fp, synchronous=True)
self.config["notification_obj"].notify(metadata["queue_id"], quarantine_id, metadata["subject"], metadata["mailfrom"], recipients, fp,
metadata["subgroups"], metadata["named_subgroups"], synchronous=True)
except IOError as e:
raise(RuntimeError("unable to read data file: {}".format(e)))