8 Commits
0.0.2 ... 0.0.7

6 changed files with 82 additions and 71 deletions

View File

@@ -45,7 +45,7 @@ The following configuration options are mandatory in each quarantine section:
* **whitelist_type** * **whitelist_type**
One of the whitelist types described below. One of the whitelist types described below.
* **smtp_host** * **smtp_host**
SMTP host to inject original e-mails. This is needed if not all recipients of an e-mail are whitelisted SMTP host used to release original e-mails from the quarantine.
* **smtp_port** * **smtp_port**
SMTP port SMTP port
@@ -97,6 +97,10 @@ The following configuration options are optional in each quarantine section:
Content of a named subgroup, 'subgroup_name' will be replaced by its 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_smtp_host**
SMTP host used to send notification e-mails.
* **notification_email_smtp_port**
SMTP port.
* **notification_email_envelope_from** * **notification_email_envelope_from**
Notification e-mail envelope from-address. Notification e-mail envelope from-address.
* **notification_email_from** * **notification_email_from**

View File

@@ -88,6 +88,18 @@ reject_reason = Message rejected
# #
notification_type = email notification_type = email
# Option: notification_email_smtp_host
# Notes: Set the SMTP host. It will be used to send notification e-mails.
# Values: [ HOSTNAME | IP_ADDRESS ]
#
notification_email_smtp_host = 127.0.0.1
# Option: notification_email_smtp_port
# Notes: Set the SMTP port.
# Values: [ PORT ]
#
notification_email_smtp_port = 25
# Option: notification_email_envelope_from # Option: notification_email_envelope_from
# Notes: Set the envelope-from address used when sending notification emails. # Notes: Set the envelope-from address used when sending notification emails.
# This option is needed by notification type 'email'. # This option is needed by notification type 'email'.

View File

@@ -118,7 +118,8 @@ def list_quarantine_emails(config, args):
metadata["date"])) metadata["date"]))
row["mailfrom"] = metadata["mailfrom"] row["mailfrom"] = metadata["mailfrom"]
row["recipient"] = metadata["recipients"].pop(0) row["recipient"] = metadata["recipients"].pop(0)
row["subject"] = emails[quarantine_id]["headers"]["subject"][:60] if "subject" not in emails[quarantine_id]["headers"].keys():
emails[quarantine_id]["headers"]["subject"] = ""
rows.append(row) rows.append(row)
if metadata["recipients"]: if metadata["recipients"]:

View File

@@ -26,6 +26,10 @@ process = None
def smtp_send(smtp_host, smtp_port, mailfrom, recipient, mail): def smtp_send(smtp_host, smtp_port, mailfrom, recipient, mail):
s = smtplib.SMTP(host=smtp_host, port=smtp_port) s = smtplib.SMTP(host=smtp_host, port=smtp_port)
s.ehlo()
if s.has_extn("STARTTLS"):
s.starttls()
s.ehlo()
s.sendmail(mailfrom, [recipient], mail) s.sendmail(mailfrom, [recipient], mail)
s.quit() s.quit()

View File

@@ -115,8 +115,8 @@ class EMailNotification(BaseNotification):
# check if mandatory options are present in config # check if mandatory options are present in config
for option in [ for option in [
"smtp_host", "notification_email_smtp_host",
"smtp_port", "notification_email_smtp_port",
"notification_email_envelope_from", "notification_email_envelope_from",
"notification_email_from", "notification_email_from",
"notification_email_subject", "notification_email_subject",
@@ -142,8 +142,8 @@ class EMailNotification(BaseNotification):
if option not in config.keys(): if option not in config.keys():
config[option] = defaults[option] config[option] = defaults[option]
self.smtp_host = self.config["smtp_host"] self.smtp_host = self.config["notification_email_smtp_host"]
self.smtp_port = self.config["smtp_port"] self.smtp_port = self.config["notification_email_smtp_port"]
self.mailfrom = self.config["notification_email_envelope_from"] self.mailfrom = self.config["notification_email_envelope_from"]
self.from_header = self.config["notification_email_from"] self.from_header = self.config["notification_email_from"]
self.subject = self.config["notification_email_subject"] self.subject = self.config["notification_email_subject"]
@@ -215,48 +215,47 @@ class EMailNotification(BaseNotification):
img.add_header("Content-ID", "<{}>".format(basename(img_path))) img.add_header("Content-ID", "<{}>".format(basename(img_path)))
self.embedded_imgs.append(img) self.embedded_imgs.append(img)
def get_text(self, queueid, part): def get_decoded_email_body(self, queueid, msg, preferred=_html_text):
"Get the mail text in html form from email part." "Find and decode email body."
mimetype = part.get_content_type() # try to find the body part
self.logger.debug("{}: trying to find email body".format(queueid))
body = None
for part in msg.walk():
content_type = part.get_content_type()
if content_type in [EMailNotification._plain_text,
EMailNotification._html_text]:
body = part
if content_type == preferred:
break
self.logger.debug( if body is not None:
"{}: extracting content of email text part".format(queueid)) # get the character set, fallback to utf-8 if not defined in header
text = part.get_payload(decode=True) charset = body.get_content_charset()
if charset is None:
charset = "utf-8"
if mimetype == EMailNotification._plain_text: # decode content
content = body.get_payload(decode=True).decode(
encoding=charset, errors="replace")
content_type = body.get_content_type()
if content_type == EMailNotification._plain_text:
# convert text/plain to text/html
self.logger.debug( self.logger.debug(
"{}: content mimetype is {}, converting to {}".format( "{}: content type is {}, converting to {}".format(
queueid, mimetype, self._html_text)) queueid, content_type, EMailNotification._html_text))
text = re.sub(r"^(.*)$", r"\1<br/>", content = re.sub(r"^(.*)$", r"\1<br/>",
escape(text.decode()), flags=re.MULTILINE) escape(content), flags=re.MULTILINE)
else: else:
self.logger.debug( self.logger.debug(
"{}: content mimetype is {}".format( "{}: content type is {}".format(
queueid, mimetype)) queueid, content_type))
self.logger.debug( else:
"{}: trying to create BeatufilSoup object with parser lib {}, " self.logger.error(
"text length is {} bytes".format( "{}: unable to find email body".format(queueid))
queueid, self.parser_lib, len(text))) content = "ERROR: unable to find email body"
soup = BeautifulSoup(text, self.parser_lib)
self.logger.debug(
"{}: sucessfully created BeautifulSoup object".format(queueid))
return soup
def get_text_multipart(self, queueid, msg, preferred=_html_text): return content
"Get the mail text of a multipart email in html form."
soup = None
for part in msg.get_payload():
mimetype = part.get_content_type()
if mimetype in [EMailNotification._plain_text,
EMailNotification._html_text]:
soup = self.get_text(queueid, part)
elif mimetype.startswith("multipart"):
soup = self.get_text_multipart(queueid, part, preferred)
if soup is not None and mimetype == preferred:
break
return soup
def sanitize(self, queueid, soup): def sanitize(self, queueid, soup):
"Sanitize mail html text." "Sanitize mail html text."
@@ -293,27 +292,6 @@ class EMailNotification(BaseNotification):
del(element.attrs[attribute]) del(element.attrs[attribute])
return soup return soup
def get_html_text_part(self, queueid, msg):
"Get the mail text of an email in html form."
soup = None
mimetype = msg.get_content_type()
self.logger.debug(
"{}: trying to find text part of email".format(queueid))
if mimetype in [EMailNotification._plain_text,
EMailNotification._html_text]:
soup = self.get_text(queueid, msg)
elif mimetype.startswith("multipart"):
soup = self.get_text_multipart(queueid, msg)
if soup is None:
self.logger.error(
"{}: unable to extract text part of email".format(queueid))
text = "ERROR: unable to extract text from email body"
soup = BeautifulSoup(text, "lxml", "UTF-8")
return soup
def notify(self, queueid, quarantine_id, mailfrom, recipients, headers, fp, def notify(self, queueid, quarantine_id, mailfrom, recipients, headers, fp,
subgroups=None, named_subgroups=None, synchronous=False): subgroups=None, named_subgroups=None, synchronous=False):
"Notify recipients via email." "Notify recipients via email."
@@ -330,12 +308,19 @@ class EMailNotification(BaseNotification):
named_subgroups, named_subgroups,
synchronous) synchronous)
# extract html text from email # extract body from email
self.logger.debug( content = self.get_decoded_email_body(
"{}: extraction email text from original email".format(queueid))
soup = self.get_html_text_part(
queueid, email.message_from_binary_file(fp)) queueid, email.message_from_binary_file(fp))
# create BeautifulSoup object
self.logger.debug(
"{}: trying to create BeatufilSoup object with parser lib {}, "
"text length is {} bytes".format(
queueid, self.parser_lib, len(content)))
soup = BeautifulSoup(content, self.parser_lib)
self.logger.debug(
"{}: sucessfully created BeautifulSoup object".format(queueid))
# replace picture sources # replace picture sources
image_replaced = False image_replaced = False
if self.strip_images: if self.strip_images:
@@ -368,7 +353,12 @@ class EMailNotification(BaseNotification):
"{}: generating notification email for '{}'".format( "{}: generating notification email for '{}'".format(
queueid, recipient)) queueid, recipient))
self.logger.debug("{}: parsing email template".format(queueid)) self.logger.debug("{}: parsing email template".format(queueid))
if "from" not in headers.keys():
headers["from"] = ""
if "to" not in headers.keys():
headers["to"] = ""
if "subject" not in headers.keys():
headers["subject"] = ""
# generate dict containing all template variables # generate dict containing all template variables
variables = defaultdict(str, variables = defaultdict(str,
EMAIL_HTML_TEXT=sanitized_text, EMAIL_HTML_TEXT=sanitized_text,

View File

@@ -1 +1 @@
__version__ = "0.0.2" __version__ = "0.0.7"