add list condition

This commit is contained in:
2023-12-12 14:13:08 +01:00
parent f42860d900
commit 479c1513a3
7 changed files with 52 additions and 66 deletions

View File

@@ -12,7 +12,6 @@
# along with pyquarantine. If not, see <http://www.gnu.org/licenses/>. # along with pyquarantine. If not, see <http://www.gnu.org/licenses/>.
# #
from sys import version_info
import encodings import encodings

View File

@@ -174,7 +174,7 @@ def inject_body_part(part, content, subtype="plain"):
boundary = part.get_boundary() boundary = part.get_boundary()
p_subtype = part.get_content_subtype() p_subtype = part.get_content_subtype()
part.clear_content() part.clear_content()
if text_content != None: if text_content is not None:
part.set_content(text_content) part.set_content(text_content)
part.add_alternative(content, subtype=subtype) part.add_alternative(content, subtype=subtype)
else: else:

View File

@@ -271,6 +271,7 @@ def release(quarantines, args):
f"{args.quarantine}: released message with id {args.quarantine_id} " f"{args.quarantine}: released message with id {args.quarantine_id} "
f"for {rcpts}") f"for {rcpts}")
def copy(quarantines, args): def copy(quarantines, args):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
quarantine = _get_quarantine(quarantines, args.quarantine, args.debug) 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"{args.quarantine}: sent a copy of message with id {args.quarantine_id} "
f"to {args.recipient}") f"to {args.recipient}")
def delete(quarantines, args): def delete(quarantines, args):
storage = _get_quarantine(quarantines, args.quarantine, args.debug).storage storage = _get_quarantine(quarantines, args.quarantine, args.debug).storage
storage.delete(args.quarantine_id, args.recipient) storage.delete(args.quarantine_id, args.recipient)
@@ -455,7 +457,7 @@ def main():
dest="syslog", dest="syslog",
help="Disable syslog messages.", help="Disable syslog messages.",
action="store_false") action="store_false")
quar_copy_parser_grp.add_argument( quar_copy_parser.add_argument(
"-t", "--to", "-t", "--to",
dest="recipient", dest="recipient",
help="Release email for one recipient address.") help="Release email for one recipient address.")

View File

@@ -62,14 +62,12 @@ class Conditions:
else: else:
setattr(self, arg, cfg[arg]) setattr(self, arg, cfg[arg])
self.allowlist = cfg["allowlist"] if "allowlist" in cfg else None self.list = cfg["list"] if "list" in cfg else None
if self.allowlist is not None: if self.list is not None:
self.allowlist["name"] = f"{cfg['name']}: allowlist" if self.list["type"] == "db":
self.allowlist["loglevel"] = cfg["loglevel"] self.list = DatabaseList(self.list, debug)
if self.allowlist["type"] == "db":
self.allowlist = DatabaseList(self.allowlist, debug)
else: else:
raise RuntimeError("invalid allowlist type") raise RuntimeError("invalid list type")
def __str__(self): def __str__(self):
cfg = [] cfg = []
@@ -77,12 +75,12 @@ class Conditions:
"var", "metavar"): "var", "metavar"):
if arg in self.cfg: if arg in self.cfg:
cfg.append(f"{arg}={self.cfg[arg]}") cfg.append(f"{arg}={self.cfg[arg]}")
if self.allowlist is not None: if self.list is not None:
cfg.append(f"allowlist={self.allowlist}") cfg.append(f"list={self.list}")
return "Conditions(" + ", ".join(cfg) + ")" return "Conditions(" + ", ".join(cfg) + ")"
def get_allowlist(self): def get_list(self):
return self.allowlist return self.list
def match_host(self, host): def match_host(self, host):
logger = CustomLogger( logger = CustomLogger(
@@ -123,28 +121,6 @@ class Conditions:
return True 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): def match(self, milter):
logger = CustomLogger( logger = CustomLogger(
self.logger, {"qid": milter.qid, "name": self.cfg["name"]}) self.logger, {"qid": milter.qid, "name": self.cfg["name"]})
@@ -200,4 +176,14 @@ class Conditions:
if self.var not in milter.msginfo["vars"]: if self.var not in milter.msginfo["vars"]:
return False 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 return True

View File

@@ -22,7 +22,7 @@ __all__ = [
"RewriteLinksConfig", "RewriteLinksConfig",
"StoreConfig", "StoreConfig",
"NotifyConfig", "NotifyConfig",
"AllowListConfig", "ListConfig",
"QuarantineConfig", "QuarantineConfig",
"ActionConfig", "ActionConfig",
"RuleConfig", "RuleConfig",
@@ -89,7 +89,7 @@ class BaseConfig:
return self._config return self._config
class AllowListConfig(BaseConfig): class ListConfig(BaseConfig):
JSON_SCHEMA = { JSON_SCHEMA = {
"type": "object", "type": "object",
"required": ["type"], "required": ["type"],
@@ -121,14 +121,14 @@ class ConditionsConfig(BaseConfig):
"headers": {"type": "array", "headers": {"type": "array",
"items": {"type": "string"}}, "items": {"type": "string"}},
"var": {"type": "string"}, "var": {"type": "string"},
"allowlist": {"type": "object"}}} "list": {"type": "object"}}}
def __init__(self, config, rec=True): def __init__(self, config, rec=True):
super().__init__(config) super().__init__(config)
if not rec: if not rec:
return return
if "allowlist" in self: if "list" in self:
self["allowlist"] = AllowListConfig(self["allowlist"]) self["list"] = ListConfig(self["list"])
class AddHeaderConfig(BaseConfig): class AddHeaderConfig(BaseConfig):
@@ -257,8 +257,7 @@ class QuarantineConfig(BaseConfig):
if "notify" in self: if "notify" in self:
self["notify"] = NotifyConfig(self["notify"]) self["notify"] = NotifyConfig(self["notify"])
if "allowlist" in self: if "allowlist" in self:
self["allowlist"] = ConditionsConfig( self["allowlist"] = ListConfig(self["allowlist"])
{"allowlist": self["allowlist"]}, rec)
class ActionConfig(BaseConfig): class ActionConfig(BaseConfig):

View File

@@ -223,7 +223,7 @@ class EMailNotification(BaseNotification):
logger.debug( logger.debug(
f"removing attribute '{attribute}' " f"removing attribute '{attribute}' "
f"from tag '{element.name}'") f"from tag '{element.name}'")
del(element.attrs[attribute]) del element.attrs[attribute]
return soup return soup
def notify(self, msg, qid, mailfrom, recipients, logger, def notify(self, msg, qid, mailfrom, recipients, logger,

View File

@@ -31,8 +31,8 @@ from time import gmtime
from pyquarantine import mailer from pyquarantine import mailer
from pyquarantine.base import CustomLogger, MilterMessage from pyquarantine.base import CustomLogger, MilterMessage
from pyquarantine.conditions import Conditions
from pyquarantine.config import ActionConfig from pyquarantine.config import ActionConfig
from pyquarantine.list import DatabaseList
from pyquarantine.notify import Notify from pyquarantine.notify import Notify
@@ -334,7 +334,7 @@ class FileMailStorage(BaseMailStorage):
metafile, _ = self._get_file_paths(storage_id) metafile, _ = self._get_file_paths(storage_id)
if type(recipients) == str: if isinstance(recipients, str):
recipients = [recipients] recipients = [recipients]
for recipient in recipients: for recipient in recipients:
@@ -430,13 +430,11 @@ class Quarantine:
self._allowlist = None self._allowlist = None
if "allowlist" in cfg["options"]: if "allowlist" in cfg["options"]:
allowlist_cfg = cfg["options"]["allowlist"] allowlist = cfg["options"]["allowlist"]
allowlist_cfg["name"] = cfg["name"] if allowlist["type"] == "db":
allowlist_cfg["loglevel"] = cfg["loglevel"] self._allowlist = DatabaseList(allowlist, debug)
self._allowlist = Conditions( else:
allowlist_cfg, raise RuntimeError("invalid allowlist type")
local_addrs=[],
debug=debug)
self._milter_action = None self._milter_action = None
if "milter_action" in cfg["options"]: if "milter_action" in cfg["options"]:
@@ -482,9 +480,7 @@ class Quarantine:
@property @property
def allowlist(self): def allowlist(self):
if self._allowlist is None: return self._allowlist
return None
return self._allowlist.get_allowlist()
@property @property
def milter_action(self): def milter_action(self):
@@ -512,7 +508,7 @@ class Quarantine:
def release(self, storage_id, recipients=None): def release(self, storage_id, recipients=None):
metadata, msg = self.storage.get_mail(storage_id) metadata, msg = self.storage.get_mail(storage_id)
if recipients and type(recipients) == str: if recipients and isinstance(recipients, str):
recipients = [recipients] recipients = [recipients]
else: else:
recipients = metadata["recipients"] recipients = metadata["recipients"]
@@ -552,14 +548,18 @@ class Quarantine:
def execute(self, milter): def execute(self, milter):
logger = CustomLogger( logger = CustomLogger(
self.logger, {"name": self.cfg["name"], "qid": milter.qid}) self.logger, {"name": self.cfg["name"], "qid": milter.qid})
rcpts = milter.msginfo["rcpts"] rcpts = milter.msginfo["rcpts"]
wl_rcpts = [] allowed_rcpts = []
if self._allowlist: if self._allowlist:
wl_rcpts = self._allowlist.get_wl_rcpts( allowed_rcpts = []
milter.msginfo["mailfrom"], rcpts, logger) for rcpt in rcpts:
if wl_rcpts: if self._allowlist.check(
logger.info(f"allowed recipients: {wl_rcpts}") milter.msginfo["mailfrom"], rcpt, logger):
rcpts = [rcpt for rcpt in rcpts if rcpt not in wl_rcpts] 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: if not rcpts:
# all recipients allowed # all recipients allowed
return return
@@ -573,7 +573,7 @@ class Quarantine:
if self._notification is not None: if self._notification is not None:
self._notification.execute(milter) self._notification.execute(milter)
milter.msginfo["rcpts"].extend(wl_rcpts) milter.msginfo["rcpts"].extend(allowed_rcpts)
if self._milter_action is not None: if self._milter_action is not None:
milter.delrcpt(rcpts) milter.delrcpt(rcpts)