diff --git a/pyquarantine/_runtime_patches.py b/pyquarantine/_runtime_patches.py index 7d59ce7..206543d 100644 --- a/pyquarantine/_runtime_patches.py +++ b/pyquarantine/_runtime_patches.py @@ -12,7 +12,6 @@ # along with pyquarantine. If not, see . # -from sys import version_info import encodings diff --git a/pyquarantine/base.py b/pyquarantine/base.py index 60c781c..3bfd323 100644 --- a/pyquarantine/base.py +++ b/pyquarantine/base.py @@ -71,7 +71,7 @@ class MilterMessage(MIMEPart): maintype, subtype = part.get_content_type().split("/") if maintype == "text": if subtype in preferencelist: - yield(preferencelist.index(subtype), parent) + yield (preferencelist.index(subtype), parent) return if maintype != "multipart" or not self.is_multipart(): return @@ -81,7 +81,7 @@ class MilterMessage(MIMEPart): subpart, preferencelist, part) return if 'related' in preferencelist: - yield(preferencelist.index('related'), parent) + yield (preferencelist.index('related'), parent) candidate = None start = part.get_param('start') if start: @@ -174,7 +174,7 @@ def inject_body_part(part, content, subtype="plain"): boundary = part.get_boundary() p_subtype = part.get_content_subtype() part.clear_content() - if text_content != None: + if text_content is not None: part.set_content(text_content) part.add_alternative(content, subtype=subtype) else: diff --git a/pyquarantine/cli.py b/pyquarantine/cli.py index 0cb7ea3..693d262 100644 --- a/pyquarantine/cli.py +++ b/pyquarantine/cli.py @@ -271,6 +271,7 @@ def release(quarantines, args): f"{args.quarantine}: released message with id {args.quarantine_id} " f"for {rcpts}") + def copy(quarantines, args): logger = logging.getLogger(__name__) quarantine = _get_quarantine(quarantines, args.quarantine, args.debug) @@ -279,6 +280,7 @@ def copy(quarantines, args): f"{args.quarantine}: sent a copy of message with id {args.quarantine_id} " f"to {args.recipient}") + def delete(quarantines, args): storage = _get_quarantine(quarantines, args.quarantine, args.debug).storage storage.delete(args.quarantine_id, args.recipient) @@ -455,7 +457,7 @@ def main(): dest="syslog", help="Disable syslog messages.", action="store_false") - quar_copy_parser_grp.add_argument( + quar_copy_parser.add_argument( "-t", "--to", dest="recipient", help="Release email for one recipient address.") diff --git a/pyquarantine/conditions.py b/pyquarantine/conditions.py index 848e30e..038c5df 100644 --- a/pyquarantine/conditions.py +++ b/pyquarantine/conditions.py @@ -62,14 +62,12 @@ class Conditions: else: setattr(self, arg, cfg[arg]) - self.allowlist = cfg["allowlist"] if "allowlist" in cfg else None - if self.allowlist is not None: - self.allowlist["name"] = f"{cfg['name']}: allowlist" - self.allowlist["loglevel"] = cfg["loglevel"] - if self.allowlist["type"] == "db": - self.allowlist = DatabaseList(self.allowlist, debug) + self.list = cfg["list"] if "list" in cfg else None + if self.list is not None: + if self.list["type"] == "db": + self.list = DatabaseList(self.list, debug) else: - raise RuntimeError("invalid allowlist type") + raise RuntimeError("invalid list type") def __str__(self): cfg = [] @@ -77,12 +75,12 @@ class Conditions: "var", "metavar"): if arg in self.cfg: cfg.append(f"{arg}={self.cfg[arg]}") - if self.allowlist is not None: - cfg.append(f"allowlist={self.allowlist}") + if self.list is not None: + cfg.append(f"list={self.list}") return "Conditions(" + ", ".join(cfg) + ")" - def get_allowlist(self): - return self.allowlist + def get_list(self): + return self.list def match_host(self, host): logger = CustomLogger( @@ -123,28 +121,6 @@ class Conditions: return True - def get_wl_rcpts(self, mailfrom, rcpts, logger): - if not self.allowlist: - return {} - - wl_rcpts = [] - for rcpt in rcpts: - if self.allowlist.check(mailfrom, rcpt, logger): - wl_rcpts.append(rcpt) - - return wl_rcpts - - def update_msginfo_from_match(self, milter, match): - if self.metavar is None: - return - - named_subgroups = match.groupdict(default=None) - for group, value in named_subgroups.items(): - if value is None: - continue - name = f"{self.metavar}_{group}" - milter.msginfo["vars"][name] = value - def match(self, milter): logger = CustomLogger( self.logger, {"qid": milter.qid, "name": self.cfg["name"]}) @@ -200,4 +176,14 @@ class Conditions: if self.var not in milter.msginfo["vars"]: return False + if self.list is not None: + envfrom = milter.msginfo["mailfrom"] + envto = milter.msginfo["rcpts"] + if not isinstance(envto, list): + envto = [envto] + + for to in envto: + if not self.list.check(envfrom, to, logger): + return False + return True diff --git a/pyquarantine/config.py b/pyquarantine/config.py index 531611f..ed72bd6 100644 --- a/pyquarantine/config.py +++ b/pyquarantine/config.py @@ -22,7 +22,7 @@ __all__ = [ "RewriteLinksConfig", "StoreConfig", "NotifyConfig", - "AllowListConfig", + "ListConfig", "QuarantineConfig", "ActionConfig", "RuleConfig", @@ -89,7 +89,7 @@ class BaseConfig: return self._config -class AllowListConfig(BaseConfig): +class ListConfig(BaseConfig): JSON_SCHEMA = { "type": "object", "required": ["type"], @@ -121,14 +121,14 @@ class ConditionsConfig(BaseConfig): "headers": {"type": "array", "items": {"type": "string"}}, "var": {"type": "string"}, - "allowlist": {"type": "object"}}} + "list": {"type": "object"}}} def __init__(self, config, rec=True): super().__init__(config) if not rec: return - if "allowlist" in self: - self["allowlist"] = AllowListConfig(self["allowlist"]) + if "list" in self: + self["list"] = ListConfig(self["list"]) class AddHeaderConfig(BaseConfig): @@ -257,8 +257,7 @@ class QuarantineConfig(BaseConfig): if "notify" in self: self["notify"] = NotifyConfig(self["notify"]) if "allowlist" in self: - self["allowlist"] = ConditionsConfig( - {"allowlist": self["allowlist"]}, rec) + self["allowlist"] = ListConfig(self["allowlist"]) class ActionConfig(BaseConfig): diff --git a/pyquarantine/notify.py b/pyquarantine/notify.py index c063ecb..586b457 100644 --- a/pyquarantine/notify.py +++ b/pyquarantine/notify.py @@ -223,7 +223,7 @@ class EMailNotification(BaseNotification): logger.debug( f"removing attribute '{attribute}' " f"from tag '{element.name}'") - del(element.attrs[attribute]) + del element.attrs[attribute] return soup def notify(self, msg, qid, mailfrom, recipients, logger, diff --git a/pyquarantine/storage.py b/pyquarantine/storage.py index c68daea..2e6270c 100644 --- a/pyquarantine/storage.py +++ b/pyquarantine/storage.py @@ -31,8 +31,8 @@ from time import gmtime from pyquarantine import mailer from pyquarantine.base import CustomLogger, MilterMessage -from pyquarantine.conditions import Conditions from pyquarantine.config import ActionConfig +from pyquarantine.list import DatabaseList from pyquarantine.notify import Notify @@ -334,7 +334,7 @@ class FileMailStorage(BaseMailStorage): metafile, _ = self._get_file_paths(storage_id) - if type(recipients) == str: + if isinstance(recipients, str): recipients = [recipients] for recipient in recipients: @@ -430,13 +430,11 @@ class Quarantine: self._allowlist = None if "allowlist" in cfg["options"]: - allowlist_cfg = cfg["options"]["allowlist"] - allowlist_cfg["name"] = cfg["name"] - allowlist_cfg["loglevel"] = cfg["loglevel"] - self._allowlist = Conditions( - allowlist_cfg, - local_addrs=[], - debug=debug) + allowlist = cfg["options"]["allowlist"] + if allowlist["type"] == "db": + self._allowlist = DatabaseList(allowlist, debug) + else: + raise RuntimeError("invalid allowlist type") self._milter_action = None if "milter_action" in cfg["options"]: @@ -482,9 +480,7 @@ class Quarantine: @property def allowlist(self): - if self._allowlist is None: - return None - return self._allowlist.get_allowlist() + return self._allowlist @property def milter_action(self): @@ -512,7 +508,7 @@ class Quarantine: def release(self, storage_id, recipients=None): metadata, msg = self.storage.get_mail(storage_id) - if recipients and type(recipients) == str: + if recipients and isinstance(recipients, str): recipients = [recipients] else: recipients = metadata["recipients"] @@ -552,14 +548,18 @@ class Quarantine: def execute(self, milter): logger = CustomLogger( self.logger, {"name": self.cfg["name"], "qid": milter.qid}) + rcpts = milter.msginfo["rcpts"] - wl_rcpts = [] + allowed_rcpts = [] if self._allowlist: - wl_rcpts = self._allowlist.get_wl_rcpts( - milter.msginfo["mailfrom"], rcpts, logger) - if wl_rcpts: - logger.info(f"allowed recipients: {wl_rcpts}") - rcpts = [rcpt for rcpt in rcpts if rcpt not in wl_rcpts] + allowed_rcpts = [] + for rcpt in rcpts: + if self._allowlist.check( + milter.msginfo["mailfrom"], rcpt, logger): + allowed_rcpts.append(rcpt) + if allowed_rcpts: + logger.info(f"allowed recipients: {allowed_rcpts}") + rcpts = [rcpt for rcpt in rcpts if rcpt not in allowed_rcpts] if not rcpts: # all recipients allowed return @@ -573,7 +573,7 @@ class Quarantine: if self._notification is not None: self._notification.execute(milter) - milter.msginfo["rcpts"].extend(wl_rcpts) + milter.msginfo["rcpts"].extend(allowed_rcpts) if self._milter_action is not None: milter.delrcpt(rcpts)