Ability to embed custom images in notifications
This commit is contained in:
10
README.md
10
README.md
@@ -16,7 +16,7 @@ The project is currently in alpha status, but will soon be used in a productive
|
|||||||
* BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/>
|
* BeautifulSoup <https://www.crummy.com/software/BeautifulSoup/>
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
The pyquarantine module uses an INI-style configuration file. The sections are described below.
|
The pyquarantine module uses an INI-style configuration file. The sections are described below. If you have to specify a path in the config, you can always use a relative path to the last loaded config file.
|
||||||
|
|
||||||
### Section "global"
|
### Section "global"
|
||||||
Any available configuration option can be set in the global section as default instead of in a quarantine section.
|
Any available configuration option can be set in the global section as default instead of in a quarantine section.
|
||||||
@@ -85,10 +85,12 @@ The following configuration options are mandatory in each quarantine section:
|
|||||||
* **notification_email_subject**
|
* **notification_email_subject**
|
||||||
Notification e-mail subject.
|
Notification e-mail subject.
|
||||||
* **notification_email_template**
|
* **notification_email_template**
|
||||||
Notification e-mail template to use.
|
Path to the notification e-mail template. It is hold in memory during runtime.
|
||||||
* **notification_email_replacement_img**
|
* **notification_email_replacement_img**
|
||||||
An image to replace images in e-mail.
|
Path to the image to replace images in e-mails. It is hold in memory during runtime. Leave it empty to disable.
|
||||||
|
* **notification_email_embedded_imgs**
|
||||||
|
Comma-separated list of images to attach to the notification e-mail. The Content-ID of each image will be set to the filename, so you can reference it from the e-mail template. All images are hold in memory during runtime.
|
||||||
|
Leave empty to disable.
|
||||||
|
|
||||||
### Actions
|
### Actions
|
||||||
Every quarantine responds with a milter-action if an e-mail header matches the configured regular expression.
|
Every quarantine responds with a milter-action if an e-mail header matches the configured regular expression.
|
||||||
|
|||||||
@@ -94,18 +94,26 @@ notification_email_subject = Spam Quarantine Notification
|
|||||||
# Notes: Set the template used when sending notification emails.
|
# Notes: Set the template used when sending notification emails.
|
||||||
# A relative path to this config file can be used.
|
# A relative path to this config file can be used.
|
||||||
# This option is needed by notification type 'email'.
|
# This option is needed by notification type 'email'.
|
||||||
# Values: [ TEMPLATE_FILE ]
|
# Values: [ TEMPLATE_PATH ]
|
||||||
#
|
#
|
||||||
notification_email_template = templates/notification.template
|
notification_email_template = templates/notification.template
|
||||||
|
|
||||||
# Option: notification_email_replacement_img
|
# Option: notification_email_replacement_img
|
||||||
# Notes: Set the replacement image for images within emails.
|
# Notes: Set the path to the replacement image for img tags within emails.
|
||||||
# A relative path to this config file can be used.
|
# A relative path to this config file can be used.
|
||||||
# This option is needed by notification type 'email'.
|
# This option is needed by notification type 'email'.
|
||||||
# Values: [ IMAGE_FILE ]
|
# Values: [ IMAGE_PATH ]
|
||||||
#
|
#
|
||||||
notification_email_replacement_img = templates/removed.png
|
notification_email_replacement_img = templates/removed.png
|
||||||
|
|
||||||
|
# Option: notification_email_embedded_imgs
|
||||||
|
# Notes: Set a list of paths to images to embed in e-mails (comma-separated).
|
||||||
|
# Relative paths to this config file can be used.
|
||||||
|
# This option is needed by notification type 'email'.
|
||||||
|
# Values: [ IMAGE_PATH ]
|
||||||
|
#
|
||||||
|
notification_email_embedded_imgs = templates/logo.png
|
||||||
|
|
||||||
# Option: whitelist_type
|
# Option: whitelist_type
|
||||||
# Notes: Set the whitelist type.
|
# Notes: Set the whitelist type.
|
||||||
# Values: [ db | none ]
|
# Values: [ db | none ]
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from cgi import escape
|
|||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.mime.image import MIMEImage
|
from email.mime.image import MIMEImage
|
||||||
|
from os.path import basename
|
||||||
|
|
||||||
from pyquarantine import mailer
|
from pyquarantine import mailer
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ class EMailNotification(BaseNotification):
|
|||||||
super(EMailNotification, self).__init__(global_config, config, configtest)
|
super(EMailNotification, self).__init__(global_config, config, configtest)
|
||||||
|
|
||||||
# check if mandatory options are present in config
|
# check if mandatory options are present in config
|
||||||
for option in ["smtp_host", "smtp_port", "notification_email_from", "notification_email_subject", "notification_email_template", "notification_email_replacement_img"]:
|
for option in ["smtp_host", "smtp_port", "notification_email_from", "notification_email_subject", "notification_email_template", "notification_email_replacement_img", "notification_email_embedded_imgs"]:
|
||||||
if option not in self.config.keys() and option in self.global_config.keys():
|
if option not in self.config.keys() and option in self.global_config.keys():
|
||||||
self.config[option] = self.global_config[option]
|
self.config[option] = self.global_config[option]
|
||||||
if option not in self.config.keys():
|
if option not in self.config.keys():
|
||||||
@@ -122,13 +123,31 @@ class EMailNotification(BaseNotification):
|
|||||||
except IOError as e:
|
except IOError as e:
|
||||||
raise RuntimeError("error reading template: {}".format(e))
|
raise RuntimeError("error reading template: {}".format(e))
|
||||||
|
|
||||||
# read email replacement image
|
# read email replacement image if specified
|
||||||
try:
|
replacement_img_path = self.config["notification_email_replacement_img"].strip()
|
||||||
self.replacement_img = MIMEImage(open(self.config["notification_email_replacement_img"], "rb").read())
|
if replacement_img_path:
|
||||||
except IOError as e:
|
try:
|
||||||
raise RuntimeError("error reading replacement image: {}".format(e))
|
self.replacement_img = MIMEImage(open(replacement_img_path, "rb").read())
|
||||||
|
except IOError as e:
|
||||||
|
raise RuntimeError("error reading replacement image: {}".format(e))
|
||||||
|
else:
|
||||||
|
self.replacement_img.add_header("Content-ID", "<removed_for_security_reasons>")
|
||||||
else:
|
else:
|
||||||
self.replacement_img.add_header("Content-ID", "<removed_for_security_reasons>")
|
self.replacement_img = None
|
||||||
|
|
||||||
|
# read images to embed if specified
|
||||||
|
embedded_img_paths = [ p.strip() for p in self.config["notification_email_embedded_imgs"].split(",") if p]
|
||||||
|
self.embedded_imgs = []
|
||||||
|
for img_path in embedded_img_paths:
|
||||||
|
# read image
|
||||||
|
try:
|
||||||
|
img = MIMEImage(open(img_path, "rb").read())
|
||||||
|
except IOError as e:
|
||||||
|
raise RuntimeError("error reading image: {}".format(e))
|
||||||
|
else:
|
||||||
|
img.add_header("Content-ID", "<{}>".format(basename(img_path)))
|
||||||
|
self.embedded_imgs.append(img)
|
||||||
|
|
||||||
|
|
||||||
def get_text(self, queueid, part):
|
def get_text(self, queueid, part):
|
||||||
"Get the mail text in html form from email part."
|
"Get the mail text in html form from email part."
|
||||||
@@ -214,12 +233,13 @@ class EMailNotification(BaseNotification):
|
|||||||
soup = self.get_html_text_part(queueid, email.message_from_binary_file(fp))
|
soup = self.get_html_text_part(queueid, email.message_from_binary_file(fp))
|
||||||
|
|
||||||
# replace picture sources
|
# replace picture sources
|
||||||
picture_replaced = False
|
image_replaced = False
|
||||||
for element in soup("img"):
|
if self.replacement_img:
|
||||||
if "src" in element.attrs.keys():
|
for element in soup("img"):
|
||||||
self.logger.debug("{}: replacing image: {}".format(queueid, element["src"]))
|
if "src" in element.attrs.keys():
|
||||||
element["src"] = "cid:removed_for_security_reasons"
|
self.logger.debug("{}: replacing image: {}".format(queueid, element["src"]))
|
||||||
picture_replaced = True
|
element["src"] = "cid:removed_for_security_reasons"
|
||||||
|
image_replaced = True
|
||||||
|
|
||||||
# sanitizing email text of original email
|
# sanitizing email text of original email
|
||||||
sanitized_text = self.sanitize(queueid, soup)
|
sanitized_text = self.sanitize(queueid, soup)
|
||||||
@@ -247,10 +267,14 @@ class EMailNotification(BaseNotification):
|
|||||||
msg["Date"] = email.utils.formatdate()
|
msg["Date"] = email.utils.formatdate()
|
||||||
msg.attach(MIMEText(htmltext, "html", 'UTF-8'))
|
msg.attach(MIMEText(htmltext, "html", 'UTF-8'))
|
||||||
|
|
||||||
if picture_replaced:
|
if image_replaced:
|
||||||
self.logger.debug("{}: attaching notification_replacement_img".format(queueid))
|
self.logger.debug("{}: attaching notification_replacement_img".format(queueid))
|
||||||
msg.attach(self.replacement_img)
|
msg.attach(self.replacement_img)
|
||||||
|
|
||||||
|
for img in self.embedded_imgs:
|
||||||
|
self.logger.debug("{}: attaching imgage".format(queueid))
|
||||||
|
msg.attach(img)
|
||||||
|
|
||||||
self.logger.debug("{}: sending notification email to: {}".format(queueid, recipient))
|
self.logger.debug("{}: sending notification email to: {}".format(queueid, recipient))
|
||||||
if synchronous:
|
if synchronous:
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user