Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
182ca2bad7
|
|||
|
1508d39ed8
|
|||
| 42536befdb | |||
| d09a453f3d | |||
| 983362a69a | |||
| f4399312b4 | |||
|
b40e835215
|
|||
|
057e66f945
|
12
README.md
12
README.md
@@ -77,11 +77,17 @@ The following configuration options are optional in each quarantine section:
|
||||
|
||||
The following template variables are available:
|
||||
* **{EMAIL_ENVELOPE_FROM}**
|
||||
E-mail from-address received by the milter.
|
||||
E-mail sender address received by the milter.
|
||||
* **{EMAIL_ENVELOPE_FROM_URL}**
|
||||
Like EMAIL_ENVELOPE_FROM, but URL encoded
|
||||
* **{EMAIL_FROM}**
|
||||
Value of the from header of the original e-mail.
|
||||
* **{EMAIL_TO}**
|
||||
Value of the FROM header of the original e-mail.
|
||||
* **{EMAIL_ENVELOPE_TO}**
|
||||
E-mail recipient address of this notification.
|
||||
* **{EMAIL_ENVELOPE_TO_URL}**
|
||||
Like EMAIL_ENVELOPE_TO, but URL encoded
|
||||
* **{EMAIL_TO}**
|
||||
Value of the TO header of the original e-mail.
|
||||
* **{EMAIL_SUBJECT}**
|
||||
Configured e-mail subject.
|
||||
* **{EMAIL_QUARANTINE_ID}**
|
||||
|
||||
4
docs/templates/notification.template
vendored
4
docs/templates/notification.template
vendored
@@ -10,6 +10,10 @@
|
||||
<td><b>From:</b></td>
|
||||
<td>{EMAIL_FROM}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Envelope-To:</b></td>
|
||||
<td>{EMAIL_ENVELOPE_TO}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>To:</b></td>
|
||||
<td>{EMAIL_TO}</td>
|
||||
|
||||
@@ -95,7 +95,8 @@ class QuarantineMilter(Milter.Base):
|
||||
def set_configfiles(config_files):
|
||||
QuarantineMilter._config_files = config_files
|
||||
|
||||
def connect(self, IPname, family, hostaddr):
|
||||
def connect(self, hostname, family, hostaddr):
|
||||
self.hostaddr = hostaddr
|
||||
self.logger.debug(
|
||||
"accepted milter connection from {} port {}".format(
|
||||
*hostaddr))
|
||||
@@ -134,15 +135,33 @@ class QuarantineMilter(Milter.Base):
|
||||
"{}: received queue-id from MTA".format(self.queueid))
|
||||
self.recipients = list(self.recipients)
|
||||
self.headers = []
|
||||
self.logger.debug(
|
||||
"{}: initializing memory buffer to save email data".format(
|
||||
self.queueid))
|
||||
# initialize memory buffer to save email data
|
||||
self.fp = BytesIO()
|
||||
return Milter.CONTINUE
|
||||
|
||||
@Milter.noreply
|
||||
def header(self, name, value):
|
||||
self.headers.append((name, value))
|
||||
try:
|
||||
# write email header to memory buffer
|
||||
self.fp.write("{}: {}\r\n".format(name, value).encode(
|
||||
encoding="ascii", errors="surrogateescape"))
|
||||
# keep copy of header without surrogates for later use
|
||||
self.headers.append((
|
||||
name.encode(errors="surrogateescape").decode(errors="replace"),
|
||||
value.encode(errors="surrogateescape").decode(errors="replace")))
|
||||
except Exception as e:
|
||||
self.logger.exception(
|
||||
"an exception occured in header function: {}".format(e))
|
||||
return Milter.TEMPFAIL
|
||||
|
||||
return Milter.CONTINUE
|
||||
|
||||
def eoh(self):
|
||||
try:
|
||||
self.fp.write("\r\n".encode(encoding="ascii"))
|
||||
self.whitelist_cache = whitelists.WhitelistCache()
|
||||
|
||||
# initialize dicts to set quaranines per recipient and keep matches
|
||||
@@ -238,38 +257,24 @@ class QuarantineMilter(Milter.Base):
|
||||
self.queueid))
|
||||
return Milter.ACCEPT
|
||||
|
||||
# check if the email body is needed
|
||||
keep_body = False
|
||||
# check if the mail body is needed
|
||||
for recipient, quarantine in self.recipients_quarantines.items():
|
||||
if quarantine["quarantine_obj"] or quarantine["notification_obj"]:
|
||||
keep_body = True
|
||||
break
|
||||
# mail body is needed, continue processing
|
||||
return Milter.CONTINUE
|
||||
|
||||
if keep_body:
|
||||
self.logger.debug(
|
||||
"{}: initializing memory buffer to save email data".format(
|
||||
self.queueid))
|
||||
# initialize memory buffer to save email data
|
||||
self.fp = BytesIO()
|
||||
# write email headers to memory buffer
|
||||
for name, value in self.headers:
|
||||
self.fp.write("{}: {}\n".format(name, value).encode())
|
||||
self.fp.write("\n".encode())
|
||||
else:
|
||||
# quarantine and notification are disabled on all matching
|
||||
# quarantines, return configured action
|
||||
quarantine = self._get_preferred_quarantine()
|
||||
self.logger.info(
|
||||
"{}: {} matching quarantine is '{}', performing milter action {}".format(
|
||||
self.queueid,
|
||||
self.global_config["preferred_quarantine_action"],
|
||||
quarantine["name"],
|
||||
quarantine["action"].upper()))
|
||||
if quarantine["action"] == "reject":
|
||||
self.setreply("554", "5.7.0", quarantine["reject_reason"])
|
||||
return quarantine["milter_action"]
|
||||
|
||||
return Milter.CONTINUE
|
||||
# quarantine and notification are disabled on all matching
|
||||
# quarantines, just return configured action
|
||||
quarantine = self._get_preferred_quarantine()
|
||||
self.logger.info(
|
||||
"{}: {} matching quarantine is '{}', performing milter action {}".format(
|
||||
self.queueid,
|
||||
self.global_config["preferred_quarantine_action"],
|
||||
quarantine["name"],
|
||||
quarantine["action"].upper()))
|
||||
if quarantine["action"] == "reject":
|
||||
self.setreply("554", "5.7.0", quarantine["reject_reason"])
|
||||
return quarantine["milter_action"]
|
||||
|
||||
except Exception as e:
|
||||
self.logger.exception(
|
||||
@@ -372,6 +377,11 @@ class QuarantineMilter(Milter.Base):
|
||||
"an exception occured in eom function: {}".format(e))
|
||||
return Milter.TEMPFAIL
|
||||
|
||||
def close(self):
|
||||
self.logger.debug(
|
||||
"disconnect from {} port {}".format(
|
||||
*self.hostaddr))
|
||||
|
||||
|
||||
def generate_milter_config(configtest=False, config_files=[]):
|
||||
"Generate the configuration for QuarantineMilter class."
|
||||
|
||||
@@ -20,6 +20,8 @@ import logging.handlers
|
||||
import sys
|
||||
import time
|
||||
|
||||
from email.header import decode_header, make_header
|
||||
|
||||
import pyquarantine
|
||||
|
||||
from pyquarantine.version import __version__ as version
|
||||
@@ -56,7 +58,7 @@ def print_table(columns, rows):
|
||||
# get the length of the longest value
|
||||
lengths.append(
|
||||
len(str(max(rows, key=lambda x: len(str(x[key])))[key])))
|
||||
# use the the longer one
|
||||
# use the longer one
|
||||
length = max(lengths)
|
||||
column_lengths.append(length)
|
||||
column_formats.append("{{:<{}}}".format(length))
|
||||
@@ -120,6 +122,9 @@ def list_quarantine_emails(config, args):
|
||||
row["recipient"] = metadata["recipients"].pop(0)
|
||||
if "subject" not in emails[quarantine_id]["headers"].keys():
|
||||
emails[quarantine_id]["headers"]["subject"] = ""
|
||||
row["subject"] = str(make_header(decode_header(
|
||||
emails[quarantine_id]["headers"]["subject"])))[:60].replace(
|
||||
"\r", "").replace("\n", "").strip()
|
||||
rows.append(row)
|
||||
|
||||
if metadata["recipients"]:
|
||||
|
||||
@@ -19,10 +19,12 @@ import re
|
||||
from bs4 import BeautifulSoup
|
||||
from cgi import escape
|
||||
from collections import defaultdict
|
||||
from email.header import decode_header, make_header
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.image import MIMEImage
|
||||
from os.path import basename
|
||||
from urllib.parse import quote
|
||||
|
||||
from pyquarantine import mailer
|
||||
|
||||
@@ -229,15 +231,12 @@ class EMailNotification(BaseNotification):
|
||||
break
|
||||
|
||||
if body is not None:
|
||||
# get the character set, fallback to utf-8 if not defined in header
|
||||
charset = body.get_content_charset()
|
||||
if charset is None:
|
||||
charset = "utf-8"
|
||||
|
||||
# decode content
|
||||
content = body.get_payload(decode=True).decode(
|
||||
encoding=charset, errors="replace")
|
||||
|
||||
charset = body.get_content_charset() or "utf-8"
|
||||
content = body.get_payload(decode=True)
|
||||
try:
|
||||
content = content.decode(encoding=charset, errors="replace")
|
||||
except LookupError:
|
||||
content = content.decode("utf-8", errors="replace")
|
||||
content_type = body.get_content_type()
|
||||
if content_type == EMailNotification._plain_text:
|
||||
# convert text/plain to text/html
|
||||
@@ -353,19 +352,26 @@ class EMailNotification(BaseNotification):
|
||||
"{}: generating notification email for '{}'".format(
|
||||
queueid, recipient))
|
||||
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"] = ""
|
||||
# decode some headers
|
||||
decoded_headers = {}
|
||||
for header in ["from", "to", "subject"]:
|
||||
if header in headers:
|
||||
decoded_headers[header] = str(
|
||||
make_header(decode_header(headers[header])))
|
||||
else:
|
||||
headers[header] = ""
|
||||
decoded_headers[header] = ""
|
||||
|
||||
# generate dict containing all template variables
|
||||
variables = defaultdict(str,
|
||||
EMAIL_HTML_TEXT=sanitized_text,
|
||||
EMAIL_FROM=escape(headers["from"]),
|
||||
EMAIL_FROM=escape(decoded_headers["from"]),
|
||||
EMAIL_ENVELOPE_FROM=escape(mailfrom),
|
||||
EMAIL_TO=escape(recipient),
|
||||
EMAIL_SUBJECT=escape(headers["subject"]),
|
||||
EMAIL_ENVELOPE_FROM_URL=escape(quote(mailfrom)),
|
||||
EMAIL_TO=escape(decoded_headers["to"]),
|
||||
EMAIL_ENVELOPE_TO=escape(recipient),
|
||||
EMAIL_ENVELOPE_TO_URL=escape(quote(recipient)),
|
||||
EMAIL_SUBJECT=escape(decoded_headers["subject"]),
|
||||
EMAIL_QUARANTINE_ID=quarantine_id)
|
||||
|
||||
if subgroups:
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.0.7"
|
||||
__version__ = "0.0.9"
|
||||
|
||||
Reference in New Issue
Block a user