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}** * **{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. 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: The following configuration options are mandatory for this notification type:
* **notification_email_from** * **notification_email_from**
Notification e-mail from-address. Notification e-mail from-address.

View File

@@ -119,9 +119,13 @@ class QuarantineMilter(Milter.Base):
# check email header against quarantine regex # check email header against quarantine regex
self.logger.debug("{}: {}: checking header against regex '{}'".format(self.queueid, quarantine["name"], 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"])) 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 # check for whitelisted recipients
whitelist = quarantine["whitelist_obj"] whitelist = quarantine["whitelist_obj"]
if whitelist != None: if whitelist != None:
@@ -212,7 +216,8 @@ class QuarantineMilter(Milter.Base):
# add email to quarantine # add email to quarantine
self.logger.info("{}: adding to quarantine '{}' for: {}".format(self.queueid, quarantine["name"], ", ".join(recipients))) self.logger.info("{}: adding to quarantine '{}' for: {}".format(self.queueid, quarantine["name"], ", ".join(recipients)))
try: 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: except RuntimeError as e:
self.logger.error("{}: unable to add to quarantine '{}': {}".format(self.queueid, quarantine["name"], e)) self.logger.error("{}: unable to add to quarantine '{}': {}".format(self.queueid, quarantine["name"], e))
return Milter.TEMPFAIL return Milter.TEMPFAIL
@@ -222,7 +227,8 @@ class QuarantineMilter(Milter.Base):
# notify # notify
self.logger.info("{}: sending notification for quarantine '{}' to: {}".format(self.queueid, quarantine["name"], ", ".join(recipients))) self.logger.info("{}: sending notification for quarantine '{}' to: {}".format(self.queueid, quarantine["name"], ", ".join(recipients)))
try: 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: except RuntimeError as e:
self.logger.error("{}: unable to send notification for quarantine '{}': {}".format(self.queueid, quarantine["name"], e)) self.logger.error("{}: unable to send notification for quarantine '{}': {}".format(self.queueid, quarantine["name"], e))
return Milter.TEMPFAIL return Milter.TEMPFAIL
@@ -316,7 +322,7 @@ def generate_milter_config(configtest=False, config_files=[]):
# pre-compile regex # pre-compile regex
logger.debug("{}: compiling regex '{}'".format(quarantine_name, config["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 # create quarantine instance
quarantine_type = config["quarantine_type"].lower() quarantine_type = config["quarantine_type"].lower()

View File

@@ -33,7 +33,7 @@ class BaseNotification(object):
self.config = config self.config = config
self.logger = logging.getLogger(__name__) 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) fp.seek(0)
pass pass
@@ -224,9 +224,9 @@ class EMailNotification(BaseNotification):
return soup 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." "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 # extract html text from email
self.logger.debug("{}: extraction email text from original email".format(queueid)) self.logger.debug("{}: extraction email text from original email".format(queueid))
@@ -244,21 +244,29 @@ class EMailNotification(BaseNotification):
# sanitizing email text of original email # sanitizing email text of original email
sanitized_text = self.sanitize(queueid, soup) sanitized_text = self.sanitize(queueid, soup)
# escape possible html entities in subject
subject = escape(subject)
# sending email notifications # sending email notifications
for recipient in recipients: for recipient in recipients:
self.logger.debug("{}: generating notification email for '{}'".format(queueid, recipient)) self.logger.debug("{}: generating notification email for '{}'".format(queueid, recipient))
self.logger.debug("{}: parsing email template".format(queueid)) self.logger.debug("{}: parsing email template".format(queueid))
htmltext = self.template.format( \ # generate dict containing all template variables
EMAIL_HTML_TEXT=sanitized_text, \ variables = {
EMAIL_FROM=escape(mailfrom), \ "EMAIL_HTML_TEXT": sanitized_text,
EMAIL_TO=escape(recipient), \ "EMAIL_FROM": escape(mailfrom),
EMAIL_SUBJECT=subject, \ "EMAIL_TO": escape(recipient),
EMAIL_QUARANTINE_ID=quarantine_id "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 = MIMEMultipart('alternative')
msg["Subject"] = self.subject msg["Subject"] = self.subject

View File

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