diff --git a/pyquarantine/__init__.py b/pyquarantine/__init__.py
index cebfe38..8725185 100644
--- a/pyquarantine/__init__.py
+++ b/pyquarantine/__init__.py
@@ -12,6 +12,24 @@
# along with PyQuarantineMilter. If not, see .
#
+import Milter
+import configparser
+import logging
+import os
+import re
+
+from Milter.utils import parse_addr
+from collections import defaultdict
+from email.policy import default as default_policy
+from email.parser import BytesHeaderParser
+from io import BytesIO
+from itertools import groupby
+from netaddr import IPAddress, IPNetwork, AddrFormatError
+from pyquarantine import mailer
+from pyquarantine import notifications
+from pyquarantine import storages
+from pyquarantine import whitelists
+
__all__ = [
"Quarantine",
"QuarantineMilter",
@@ -25,32 +43,12 @@ __all__ = [
"version",
"whitelists"]
-name = "pyquarantine"
-
-import Milter
-import configparser
-import logging
-import os
-import re
-import sys
-
-from Milter.utils import parse_addr
-from collections import defaultdict
-from email.policy import default as default_policy
-from email.parser import BytesHeaderParser
-from io import BytesIO
-from itertools import groupby
-from netaddr import IPAddress, IPNetwork
-from pyquarantine import mailer
-from pyquarantine import notifications
-from pyquarantine import storages
-from pyquarantine import whitelists
-
class Quarantine(object):
"""Quarantine class suitable for QuarantineMilter
- The class holds all the objects and functions needed for QuarantineMilter quarantine.
+ The class holds all the objects and functions needed
+ for QuarantineMilter quarantine.
"""
@@ -61,8 +59,8 @@ class Quarantine(object):
"DISCARD": Milter.DISCARD}
def __init__(self, name, index=0, regex=None, storage=None, whitelist=None,
- host_whitelist=[], notification=None, action="ACCEPT",
- reject_reason=None):
+ host_whitelist=[], notification=None, action="ACCEPT",
+ reject_reason=None):
self.logger = logging.getLogger(__name__)
self.name = name
self.index = index
@@ -98,7 +96,8 @@ class Quarantine(object):
cfg[opt] = defaults[opt]
else:
raise RuntimeError(
- f"mandatory option '{opt}' not present in config section '{self.name}' or 'global'")
+ f"mandatory option '{opt}' not present in "
+ f"config section '{self.name}' or 'global'")
# pre-compile regex
self.logger.debug(
@@ -113,7 +112,8 @@ class Quarantine(object):
storage_type = cfg["storage_type"].lower()
if storage_type in storages.TYPES:
self.logger.debug(
- f"{self.name}: initializing storage type '{storage_type.upper()}'")
+ f"{self.name}: initializing storage "
+ f"type '{storage_type.upper()}'")
self.storage = storages.TYPES[storage_type](
self.name, global_cfg, cfg, test)
elif storage_type == "none":
@@ -127,11 +127,12 @@ class Quarantine(object):
whitelist_type = cfg["whitelist_type"].lower()
if whitelist_type in whitelists.TYPES:
self.logger.debug(
- f"{self.name}: initializing whitelist type '{whitelist_type.upper()}'")
+ f"{self.name}: initializing whitelist "
+ f"type '{whitelist_type.upper()}'")
self.whitelist = whitelists.TYPES[whitelist_type](
self.name, global_cfg, cfg, test)
elif whitelist_type == "none":
- logger.debug(f"{self.name}: whitelist is NONE")
+ self.logger.debug(f"{self.name}: whitelist is NONE")
self.whitelist = None
else:
raise RuntimeError(
@@ -141,7 +142,8 @@ class Quarantine(object):
notification_type = cfg["notification_type"].lower()
if notification_type in notifications.TYPES:
self.logger.debug(
- f"{self.name}: initializing notification type '{notification_type.upper()}'")
+ f"{self.name}: initializing notification "
+ f"type '{notification_type.upper()}'")
self.notification = notifications.TYPES[notification_type](
self.name, global_cfg, cfg, test)
elif notification_type == "none":
@@ -149,7 +151,8 @@ class Quarantine(object):
self.notification = None
else:
raise RuntimeError(
- f"{self.name}: unknown notification type '{notification_type}'")
+ f"{self.name}: unknown notification "
+ f"type '{notification_type}'")
# determining milter action for this quarantine
action = cfg["action"].upper()
@@ -166,7 +169,7 @@ class Quarantine(object):
# create host/network whitelist
self.host_whitelist = []
host_whitelist = set([p.strip()
- for p in cfg["host_whitelist"].split(",") if p])
+ for p in cfg["host_whitelist"].split(",") if p])
for host in host_whitelist:
if not host:
continue
@@ -174,7 +177,8 @@ class Quarantine(object):
try:
net = IPNetwork(host)
except AddrFormatError as e:
- raise RuntimeError(f"{self.name}: error parsing host_whitelist: {e}")
+ raise RuntimeError(
+ f"{self.name}: error parsing host_whitelist: {e}")
else:
self.host_whitelist.append(net)
if self.host_whitelist:
@@ -190,7 +194,8 @@ class Quarantine(object):
if not self.notification:
raise RuntimeError(
- "notification type is set to None, unable to send notification")
+ "notification type is set to None, "
+ "unable to send notification")
fp, metadata = self.storage.get_mail(storage_id)
@@ -255,7 +260,7 @@ class Quarantine(object):
ip = IPAddress(hostaddr[0])
for entry in self.host_whitelist:
if ip in entry:
- return true
+ return True
return False
def match(self, header):
@@ -265,11 +270,12 @@ class Quarantine(object):
class QuarantineMilter(Milter.Base):
"""QuarantineMilter based on Milter.Base to implement milter communication
- The class variable quarantines needs to be filled by runng the setup_milter function.
+ The class variable quarantines needs to be filled by
+ runng the setup_milter function.
"""
quarantines = []
- preferred_quarantine_action = "first"
+ preferred_action = "first"
# list of default config files
_cfg_files = [
@@ -284,8 +290,8 @@ class QuarantineMilter(Milter.Base):
def _get_preferred_quarantine(self):
matching_quarantines = [
- q for q in self.recipients_quarantines.values() if q]
- if self.preferred_quarantine_action == "first":
+ q for q in self.rcpts_quarantines.values() if q]
+ if self.preferred_action == "first":
quarantine = sorted(
matching_quarantines,
key=lambda q: q.index)[0]
@@ -307,16 +313,18 @@ class QuarantineMilter(Milter.Base):
def connect(self, hostname, family, hostaddr):
self.hostaddr = hostaddr
self.logger.debug(
- f"accepted milter connection from {hostaddr[0]} port {hostaddr[1]}")
+ f"accepted milter connection from {hostaddr[0]} "
+ f"port {hostaddr[1]}")
for quarantine in self.quarantines.copy():
if quarantine.host_in_whitelist(hostaddr):
self.logger.debug(
- f"host {hostaddr[0]} is in whitelist of quarantine {quarantine['name']}")
+ f"host {hostaddr[0]} is in whitelist of "
+ f"quarantine {quarantine['name']}")
self.quarantines.remove(quarantine)
if not self.quarantines:
self.logger.debug(
- f"host {hostaddr[0]} is in whitelist of all quarantines, "
- f"skip further processing")
+ f"host {hostaddr[0]} is in whitelist of all "
+ f"quarantines, skip further processing")
return Milter.ACCEPT
return Milter.CONTINUE
@@ -362,93 +370,106 @@ class QuarantineMilter(Milter.Base):
self.fp.seek(0)
self.headers = BytesHeaderParser(
policy=default_policy).parse(self.fp).items()
- self.whitelist_cache = whitelists.WhitelistCache()
+ self.wl_cache = whitelists.WhitelistCache()
# initialize dicts to set quaranines per recipient and keep matches
- self.recipients_quarantines = {}
- self.quarantines_matches = {}
+ self.rcpts_quarantines = {}
+ self.matches = {}
# iterate email headers
- recipients_to_check = self.recipients.copy()
+ rcpts_to_check = self.recipients.copy()
for name, value in self.headers:
header = f"{name}: {value}"
self.logger.debug(
- f"{self.qid}: checking header against configured quarantines: {header}")
+ f"{self.qid}: checking header against configured "
+ f"quarantines: {header}")
# iterate quarantines
for quarantine in self.quarantines:
- if len(self.recipients_quarantines) == len(
+ if len(self.rcpts_quarantines) == len(
self.recipients):
# every recipient matched a quarantine already
if quarantine.index >= max(
- [q.index for q in self.recipients_quarantines.values()]):
+ [q.index for q in
+ self.rcpts_quarantines.values()]):
# all recipients matched a quarantine with at least
# the same precedence already, skip checks against
# quarantines with lower precedence
self.logger.debug(
- f"{self.qid}: {quarantine.name}: skip further checks of this header")
+ f"{self.qid}: {quarantine.name}: skip further "
+ f"checks of this header")
break
# check email header against quarantine regex
self.logger.debug(
- f"{self.qid}: {quarantine.name}: checking header against regex '{quarantine.regex}'")
+ f"{self.qid}: {quarantine.name}: checking header "
+ f"against regex '{quarantine.regex}'")
match = quarantine.match(header)
if match:
self.logger.debug(
- f"{self.qid}: {quarantine.name}: header matched regex")
+ f"{self.qid}: {quarantine.name}: "
+ f"header matched regex")
# check for whitelisted recipients
whitelist = quarantine.get_whitelist()
if whitelist:
try:
- whitelisted_recipients = self.whitelist_cache.get_whitelisted_recipients(
- whitelist, self.mailfrom, recipients_to_check)
+ wl_recipients = self.wl_cache.get_recipients(
+ whitelist,
+ self.mailfrom,
+ rcpts_to_check)
except RuntimeError as e:
self.logger.error(
- f"{self.qid}: {quarantine.name}: unable to query whitelist: {e}")
+ f"{self.qid}: {quarantine.name}: unable "
+ f"to query whitelist: {e}")
return Milter.TEMPFAIL
else:
- whitelisted_recipients = {}
+ wl_recipients = {}
# iterate recipients
- for recipient in recipients_to_check.copy():
- if recipient in whitelisted_recipients:
+ for rcpt in rcpts_to_check.copy():
+ if rcpt in wl_recipients:
# recipient is whitelisted in this quarantine
self.logger.debug(
- f"{self.qid}: {quarantine.name}: recipient '{recipient}' is whitelisted")
+ f"{self.qid}: {quarantine.name}: "
+ f"recipient '{rcpt}' is whitelisted")
continue
- if recipient not in self.recipients_quarantines.keys() or \
- self.recipients_quarantines[recipient].index > quarantine.index:
+ if rcpt not in self.rcpts_quarantines or \
+ self.rcpts_quarantines[rcpt].index > \
+ quarantine.index:
self.logger.debug(
- f"{self.qid}: {quarantine.name}: set quarantine for recipient '{recipient}'")
+ f"{self.qid}: {quarantine.name}: set "
+ f"quarantine for recipient '{rcpt}'")
# save match for later use as template
# variables
- self.quarantines_matches[quarantine.name] = match
- self.recipients_quarantines[recipient] = quarantine
+ self.matches[quarantine.name] = match
+ self.rcpts_quarantines[rcpt] = quarantine
if quarantine.index == 0:
# we do not need to check recipients which
# matched the quarantine with the highest
# precedence already
- recipients_to_check.remove(recipient)
+ rcpts_to_check.remove(rcpt)
else:
self.logger.debug(
- f"{self.qid}: {quarantine.name}: a quarantine with same or higher "
- f"precedence matched already for recipient '{recipient}'")
+ f"{self.qid}: {quarantine.name}: a "
+ f"quarantine with same or higher "
+ f"precedence matched already for "
+ f"recipient '{rcpt}'")
- if not recipients_to_check:
+ if not rcpts_to_check:
self.logger.debug(
- f"{self.qid}: all recipients matched the first quarantine, "
- f"skipping all remaining header checks")
+ f"{self.qid}: all recipients matched the first "
+ f"quarantine, skipping all remaining header checks")
break
# check if no quarantine has matched for all recipients
- if not self.recipients_quarantines:
+ if not self.rcpts_quarantines:
# accept email
self.logger.info(
f"{self.qid}: passed clean for all recipients")
return Milter.ACCEPT
# check if the mail body is needed
- for recipient, quarantine in self.recipients_quarantines.items():
+ for recipient, quarantine in self.rcpts_quarantines.items():
if quarantine.get_storage() or quarantine.get_notification():
# mail body is needed, continue processing
return Milter.CONTINUE
@@ -457,7 +478,7 @@ class QuarantineMilter(Milter.Base):
# quarantines, just return configured action
quarantine = self._get_preferred_quarantine()
self.logger.info(
- f"{self.qid}: {self.preferred_quarantine_action} "
+ f"{self.qid}: {self.preferred_action} "
f"matching quarantine is '{quarantine.name}', performing "
f"milter action {quarantine.action}")
if quarantine.action == "REJECT":
@@ -484,9 +505,9 @@ class QuarantineMilter(Milter.Base):
# processing recipients grouped by quarantines
quarantines = []
for quarantine, recipients in groupby(
- sorted(self.recipients_quarantines,
- key=lambda x: self.recipients_quarantines[x].index),
- lambda x: self.recipients_quarantines[x]):
+ sorted(self.rcpts_quarantines,
+ key=lambda x: self.rcpts_quarantines[x].index),
+ lambda x: self.rcpts_quarantines[x]):
quarantines.append((quarantine, list(recipients)))
# iterate quarantines sorted by index
@@ -495,9 +516,9 @@ class QuarantineMilter(Milter.Base):
headers = defaultdict(str)
for name, value in self.headers:
headers[name.lower()] = value
- subgroups = self.quarantines_matches[quarantine.name].groups(
+ subgroups = self.matches[quarantine.name].groups(
default="")
- named_subgroups = self.quarantines_matches[quarantine.name].groupdict(
+ named_subgroups = self.matches[quarantine.name].groupdict(
default="")
rcpts = ", ".join(recipients)
@@ -508,14 +529,16 @@ class QuarantineMilter(Milter.Base):
if storage:
# add email to quarantine
self.logger.info(
- f"{self.qid}: adding to quarantine '{quarantine.name}' for: {rcpts}")
+ f"{self.qid}: adding to quarantine "
+ f"'{quarantine.name}' for: {rcpts}")
try:
storage_id = storage.add(
- self.qid, self.mailfrom, recipients, headers, self.fp,
- subgroups, named_subgroups)
+ self.qid, self.mailfrom, recipients, headers,
+ self.fp, subgroups, named_subgroups)
except RuntimeError as e:
self.logger.error(
- f"{self.qid}: unable to add to quarantine '{quarantine.name}': {e}")
+ f"{self.qid}: unable to add to quarantine "
+ f"'{quarantine.name}': {e}")
return Milter.TEMPFAIL
# check if a notification is configured
@@ -551,7 +574,7 @@ class QuarantineMilter(Milter.Base):
# return configured action
quarantine = self._get_preferred_quarantine()
self.logger.info(
- f"{self.qid}: {self.preferred_quarantine_action} matching "
+ f"{self.qid}: {self.preferred_action} matching "
f"quarantine is '{quarantine.name}', performing milter "
f"action {quarantine.action}")
if quarantine.action == "REJECT":
@@ -589,15 +612,16 @@ def setup_milter(test=False, cfg_files=[]):
if "global" not in parser.sections():
raise RuntimeError(
"mandatory section 'global' not present in config file")
- for option in ["quarantines", "preferred_quarantine_action"]:
+ for option in ["quarantines", "preferred_action"]:
if not parser.has_option("global", option):
raise RuntimeError(
- f"mandatory option '{option}' not present in config section 'global'")
+ f"mandatory option '{option}' not present in config "
+ f"section 'global'")
# read global config section
global_cfg = dict(parser.items("global"))
- preferred_quarantine_action = global_cfg["preferred_quarantine_action"].lower()
- if preferred_quarantine_action not in ["first", "last"]:
+ preferred_action = global_cfg["preferred_quarantine_action"].lower()
+ if preferred_action not in ["first", "last"]:
raise RuntimeError(
"option preferred_quarantine_action has illegal value")
@@ -606,11 +630,13 @@ def setup_milter(test=False, cfg_files=[]):
q.strip() for q in global_cfg["quarantines"].split(",")]
if len(quarantines) != len(set(quarantines)):
raise RuntimeError(
- "at least one quarantine is specified multiple times in quarantines option")
+ "at least one quarantine is specified multiple times "
+ "in quarantines option")
if "global" in quarantines:
quarantines.remove("global")
logger.warning(
- "removed illegal quarantine name 'global' from list of active quarantines")
+ "removed illegal quarantine name 'global' from list of "
+ "active quarantines")
if not quarantines:
raise RuntimeError("no quarantines configured")
@@ -628,7 +654,7 @@ def setup_milter(test=False, cfg_files=[]):
quarantine.setup_from_cfg(global_cfg, cfg, test)
milter_quarantines.append(quarantine)
- QuarantineMilter.preferred_quarantine_action = preferred_quarantine_action
+ QuarantineMilter.preferred_action = preferred_action
QuarantineMilter.quarantines = milter_quarantines
diff --git a/pyquarantine/cli.py b/pyquarantine/cli.py
index edc8e1c..cdf901d 100644
--- a/pyquarantine/cli.py
+++ b/pyquarantine/cli.py
@@ -20,11 +20,10 @@ import logging.handlers
import sys
import time
-from email.header import decode_header
-
from pyquarantine import QuarantineMilter, setup_milter
from pyquarantine.version import __version__ as version
+
def _get_quarantine(quarantines, name):
try:
quarantine = next((q for q in quarantines if q.name == name))
@@ -32,6 +31,7 @@ def _get_quarantine(quarantines, name):
raise RuntimeError(f"invalid quarantine 'name'")
return quarantine
+
def _get_storage(quarantines, name):
quarantine = _get_quarantine(quarantines, name)
storage = quarantine.get_storage()
@@ -40,6 +40,7 @@ def _get_storage(quarantines, name):
"storage type is set to NONE")
return storage
+
def _get_notification(quarantines, name):
quarantine = _get_quarantine(quarantines, name)
notification = quarantine.get_notification()
@@ -48,6 +49,7 @@ def _get_notification(quarantines, name):
"notification type is set to NONE")
return notification
+
def _get_whitelist(quarantines, name):
quarantine = _get_quarantine(quarantines, name)
whitelist = quarantine.get_whitelist()
@@ -56,6 +58,7 @@ def _get_whitelist(quarantines, name):
"whitelist type is set to NONE")
return whitelist
+
def print_table(columns, rows):
if not rows:
return
diff --git a/pyquarantine/mailer.py b/pyquarantine/mailer.py
index 83791ae..803dd64 100644
--- a/pyquarantine/mailer.py
+++ b/pyquarantine/mailer.py
@@ -14,7 +14,6 @@
import logging
import smtplib
-import sys
from multiprocessing import Process, Queue
@@ -50,7 +49,8 @@ def mailprocess():
smtp_send(smtp_host, smtp_port, mailfrom, recipient, mail)
except Exception as e:
logger.error(
- f"{qid}: error while sending {emailtype} to '{recipient}': {e}")
+ f"{qid}: error while sending {emailtype} "
+ f"to '{recipient}': {e}")
else:
logger.info(
f"{qid}: successfully sent {emailtype} to: {recipient}")
@@ -82,5 +82,5 @@ def sendmail(smtp_host, smtp_port, qid, mailfrom, recipients, mail,
(smtp_host, smtp_port, qid, mailfrom, recipient, mail,
emailtype),
timeout=30)
- except Queue.Full as e:
+ except Queue.Full:
raise RuntimeError("email queue is full")
diff --git a/pyquarantine/notifications.py b/pyquarantine/notifications.py
index 8c5eaf2..c163406 100644
--- a/pyquarantine/notifications.py
+++ b/pyquarantine/notifications.py
@@ -20,7 +20,6 @@ from bs4 import BeautifulSoup
from cgi import escape
from collections import defaultdict
from email import policy
-from email.header import decode_header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
@@ -136,7 +135,8 @@ class EMailNotification(BaseNotification):
cfg[opt] = defaults[opt]
else:
raise RuntimeError(
- f"mandatory option '{opt}' not present in config section '{self.name}' or 'global'")
+ f"mandatory option '{opt}' not present in config "
+ f"section '{self.name}' or 'global'")
self.smtp_host = cfg["notification_email_smtp_host"]
self.smtp_port = cfg["notification_email_smtp_port"]
@@ -176,11 +176,13 @@ class EMailNotification(BaseNotification):
elif strip_images in ["FALSE", "OFF", "NO"]:
self.strip_images = False
else:
- raise RuntimeError("error parsing notification_email_strip_images: unknown value")
+ raise RuntimeError(
+ "error parsing notification_email_strip_images: unknown value")
self.parser_lib = cfg["notification_email_parser_lib"].strip()
if self.parser_lib not in ["lxml", "html.parser"]:
- raise RuntimeError("error parsing notification_email_parser_lib: unknown value")
+ raise RuntimeError(
+ "error parsing notification_email_parser_lib: unknown value")
# read email replacement image if specified
replacement_img = cfg["notification_email_replacement_img"].strip()
@@ -198,8 +200,10 @@ class EMailNotification(BaseNotification):
self.replacement_img = None
# read images to embed if specified
- embedded_img_paths = [
- p.strip() for p in cfg["notification_email_embedded_imgs"].split(",") if p]
+ embedded_img_paths = []
+ for p in cfg["notification_email_embedded_imgs"].split(","):
+ if p:
+ embedded_img_paths.append(p.strip())
self.embedded_imgs = []
for img_path in embedded_img_paths:
# read image
@@ -219,7 +223,9 @@ class EMailNotification(BaseNotification):
try:
body = msg.get_body(preferencelist=("html", "plain"))
except Exception as e:
- self.logger.error(f"{qid}: an error occured in email.message.EmailMessage.get_body: {e}")
+ self.logger.error(
+ f"{qid}: an error occured in "
+ f"email.message.EmailMessage.get_body: {e}")
body = None
if body:
@@ -228,13 +234,16 @@ class EMailNotification(BaseNotification):
try:
content = content.decode(encoding=charset, errors="replace")
except LookupError:
- self.logger.info(f"{qid}: unknown encoding '{charset}', falling back to UTF-8")
+ self.logger.info(
+ f"{qid}: unknown encoding '{charset}', "
+ f"falling back to UTF-8")
content = content.decode("utf-8", errors="replace")
content_type = body.get_content_type()
if content_type == "text/plain":
# convert text/plain to text/html
self.logger.debug(
- f"{qid}: content type is {content_type}, converting to text/html")
+ f"{qid}: content type is {content_type}, "
+ f"converting to text/html")
content = re.sub(r"^(.*)$", r"\1
",
escape(content), flags=re.MULTILINE)
else:
@@ -248,7 +257,8 @@ class EMailNotification(BaseNotification):
# create BeautifulSoup object
length = len(content)
self.logger.debug(
- f"{qid}: trying to create BeatufilSoup object with parser lib {self.parser_lib}, "
+ f"{qid}: trying to create BeatufilSoup object with "
+ f"parser lib {self.parser_lib}, "
f"text length is {length} bytes")
soup = BeautifulSoup(content, self.parser_lib)
self.logger.debug(
@@ -263,7 +273,8 @@ class EMailNotification(BaseNotification):
# completly remove bad elements
for element in soup(EMailNotification._bad_tags):
self.logger.debug(
- f"{qid}: removing dangerous tag '{element.name}' and its content")
+ f"{qid}: removing dangerous tag '{element.name}' "
+ f"and its content")
element.extract()
# remove not whitelisted elements, but keep their content
@@ -279,11 +290,13 @@ class EMailNotification(BaseNotification):
if attribute not in EMailNotification._good_attributes:
if element.name == "a" and attribute == "href":
self.logger.debug(
- f"{qid}: setting attribute href to '#' on tag '{element.name}'")
+ f"{qid}: setting attribute href to '#' "
+ f"on tag '{element.name}'")
element["href"] = "#"
else:
self.logger.debug(
- f"{qid}: removing attribute '{attribute}' from tag '{element.name}'")
+ f"{qid}: removing attribute '{attribute}' "
+ f"from tag '{element.name}'")
del(element.attrs[attribute])
return soup
@@ -338,16 +351,17 @@ class EMailNotification(BaseNotification):
self.logger.debug(f"{qid}: parsing email template")
# generate dict containing all template variables
- variables = defaultdict(str,
- EMAIL_HTML_TEXT=sanitized_text,
- EMAIL_FROM=escape(headers["from"]),
- EMAIL_ENVELOPE_FROM=escape(mailfrom),
- EMAIL_ENVELOPE_FROM_URL=escape(quote(mailfrom)),
- EMAIL_TO=escape(headers["to"]),
- EMAIL_ENVELOPE_TO=escape(recipient),
- EMAIL_ENVELOPE_TO_URL=escape(quote(recipient)),
- EMAIL_SUBJECT=escape(headers["subject"]),
- EMAIL_QUARANTINE_ID=storage_id)
+ variables = defaultdict(
+ str,
+ EMAIL_HTML_TEXT=sanitized_text,
+ EMAIL_FROM=escape(headers["from"]),
+ EMAIL_ENVELOPE_FROM=escape(mailfrom),
+ EMAIL_ENVELOPE_FROM_URL=escape(quote(mailfrom)),
+ EMAIL_TO=escape(headers["to"]),
+ EMAIL_ENVELOPE_TO=escape(recipient),
+ EMAIL_ENVELOPE_TO_URL=escape(quote(recipient)),
+ EMAIL_SUBJECT=escape(headers["subject"]),
+ EMAIL_QUARANTINE_ID=storage_id)
if subgroups:
number = 0
diff --git a/pyquarantine/run.py b/pyquarantine/run.py
index 876fac0..d9b507b 100644
--- a/pyquarantine/run.py
+++ b/pyquarantine/run.py
@@ -24,6 +24,7 @@ import pyquarantine
from pyquarantine.version import __version__ as version
+
def main():
"Run PyQuarantine-Milter."
# parse command line
diff --git a/pyquarantine/storages.py b/pyquarantine/storages.py
index 63f0861..8114473 100644
--- a/pyquarantine/storages.py
+++ b/pyquarantine/storages.py
@@ -73,14 +73,16 @@ class FileMailStorage(BaseMailStorage):
cfg[opt] = defaults[opt]
else:
raise RuntimeError(
- f"mandatory option '{opt}' not present in config section '{self.name}' or 'global'")
+ f"mandatory option '{opt}' not present in config "
+ f"section '{self.name}' or 'global'")
self.directory = cfg["storage_directory"]
# check if quarantine directory exists and is writable
if not os.path.isdir(self.directory) or not os.access(
self.directory, os.W_OK):
raise RuntimeError(
- f"file quarantine directory '{self.directory}' does not exist or is not writable")
+ f"file quarantine directory '{self.directory}' does "
+ f"not exist or is not writable")
self._metadata_suffix = ".metadata"
def _save_datafile(self, storage_id, fp):
@@ -203,7 +205,8 @@ class FileMailStorage(BaseMailStorage):
if len(recipients) == 1 and \
recipients[0] not in metadata["recipients"]:
continue
- elif len(set(recipients + metadata["recipients"])) == len(recipients + metadata["recipients"]):
+ elif len(set(recipients + metadata["recipients"])) == \
+ len(recipients + metadata["recipients"]):
continue
emails[storage_id] = metadata
diff --git a/pyquarantine/whitelists.py b/pyquarantine/whitelists.py
index e182bb4..014b67f 100644
--- a/pyquarantine/whitelists.py
+++ b/pyquarantine/whitelists.py
@@ -15,7 +15,6 @@
import logging
import peewee
import re
-import sys
from datetime import datetime
from playhouse.db_url import connect
@@ -88,7 +87,8 @@ class DatabaseWhitelist(WhitelistBase):
defaults = {}
# check config
- for opt in ["whitelist_db_connection", "whitelist_db_table"] + list(defaults.keys()):
+ for opt in ["whitelist_db_connection",
+ "whitelist_db_table"] + list(defaults.keys()):
if opt in cfg:
continue
if opt in global_cfg:
@@ -97,7 +97,8 @@ class DatabaseWhitelist(WhitelistBase):
cfg[opt] = defaults[opt]
else:
raise RuntimeError(
- f"mandatory option '{opt}' not present in config section '{self.name}' or 'global'")
+ f"mandatory option '{opt}' not present in config "
+ f"section '{self.name}' or 'global'")
tablename = cfg["whitelist_db_table"]
connection_string = cfg["whitelist_db_connection"]
@@ -168,7 +169,8 @@ class DatabaseWhitelist(WhitelistBase):
# generate list of possible mailfroms
self.logger.debug(
- f"query database for whitelist entries from <{mailfrom}> to <{recipient}>")
+ f"query database for whitelist entries from <{mailfrom}> "
+ f"to <{recipient}>")
mailfroms = [""]
if "@" in mailfrom and not mailfrom.startswith("@"):
domain = mailfrom.split("@")[1]
@@ -221,7 +223,8 @@ class DatabaseWhitelist(WhitelistBase):
try:
for entry in list(self.model.select()):
if older_than is not None:
- if (datetime.now() - entry.last_used).total_seconds() < (older_than * 86400):
+ delta = (datetime.now() - entry.last_used).total_seconds()
+ if delta < (older_than * 86400):
continue
if mailfrom is not None:
@@ -290,10 +293,11 @@ class WhitelistCache(object):
mailfrom, recipient)
return self.cache[whitelist][recipient]
- def get_whitelisted_recipients(self, whitelist, mailfrom, recipients):
+ def get_recipients(self, whitelist, mailfrom, recipients):
self.load(whitelist, mailfrom, recipients)
- return list(
- filter(lambda x: self.cache[whitelist][x], self.cache[whitelist].keys()))
+ return list(filter(
+ lambda x: self.cache[whitelist][x],
+ self.cache[whitelist].keys()))
# list of whitelist types and their related whitelist classes