add list condition
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class MilterMessage(MIMEPart):
|
|||||||
maintype, subtype = part.get_content_type().split("/")
|
maintype, subtype = part.get_content_type().split("/")
|
||||||
if maintype == "text":
|
if maintype == "text":
|
||||||
if subtype in preferencelist:
|
if subtype in preferencelist:
|
||||||
yield(preferencelist.index(subtype), parent)
|
yield (preferencelist.index(subtype), parent)
|
||||||
return
|
return
|
||||||
if maintype != "multipart" or not self.is_multipart():
|
if maintype != "multipart" or not self.is_multipart():
|
||||||
return
|
return
|
||||||
@@ -81,7 +81,7 @@ class MilterMessage(MIMEPart):
|
|||||||
subpart, preferencelist, part)
|
subpart, preferencelist, part)
|
||||||
return
|
return
|
||||||
if 'related' in preferencelist:
|
if 'related' in preferencelist:
|
||||||
yield(preferencelist.index('related'), parent)
|
yield (preferencelist.index('related'), parent)
|
||||||
candidate = None
|
candidate = None
|
||||||
start = part.get_param('start')
|
start = part.get_param('start')
|
||||||
if start:
|
if start:
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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.")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user