use jsonschema to validate config and massive refactor

This commit is contained in:
2021-09-30 01:07:21 +02:00
parent 4d9baa79f7
commit 08b6ae6377
12 changed files with 762 additions and 1104 deletions

View File

@@ -28,24 +28,26 @@ from time import gmtime
from pymodmilter.base import CustomLogger
from pymodmilter.conditions import Conditions
from pymodmilter.config import ActionConfig
from pymodmilter.notify import Notify
class BaseMailStorage:
"Mail storage base class"
_headersonly = True
def __init__(self, original=False, metadata=False, metavar=None):
def __init__(self, original=False, metadata=False, metavar=None,
pretend=False):
self.original = original
self.metadata = metadata
self.metavar = metavar
return
self.pretend = False
def add(self, data, qid, mailfrom="", recipients=[]):
"Add email to storage."
return ("", "")
def execute(self, milter, pretend=False,
logger=logging.getLogger(__name__)):
def execute(self, milter, logger):
return
def find(self, mailfrom=None, recipients=None, older_than=None):
@@ -69,9 +71,9 @@ class FileMailStorage(BaseMailStorage):
"Storage class to store mails on filesystem."
_headersonly = False
def __init__(self, directory, original=False, metadata=False,
metavar=None):
super().__init__(original, metadata, metavar)
def __init__(self, directory, original=False, metadata=False, metavar=None,
pretend=False):
super().__init__(original, metadata, metavar, pretend)
# check if directory exists and is writable
if not os.path.isdir(directory) or \
not os.access(directory, os.W_OK):
@@ -81,6 +83,15 @@ class FileMailStorage(BaseMailStorage):
self.directory = directory
self._metadata_suffix = ".metadata"
def __str__(self):
cfg = []
cfg.append(f"metadata={self.metadata}")
cfg.append(f"metavar={self.metavar}")
cfg.append(f"pretend={self.pretend}")
cfg.append(f"directory={self.directory}")
cfg.append(f"original={self.original}")
return "FileMailStorage(" + ", ".join(cfg) + ")"
def get_storageid(self, qid):
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
return f"{timestamp}_{qid}"
@@ -144,8 +155,7 @@ class FileMailStorage(BaseMailStorage):
return storage_id, metafile, datafile
def execute(self, milter, pretend=False,
logger=logging.getLogger(__name__)):
def execute(self, milter, logger):
if self.original:
milter.fp.seek(0)
data = milter.fp.read
@@ -158,7 +168,7 @@ class FileMailStorage(BaseMailStorage):
recipients = list(milter.msginfo["rcpts"])
subject = milter.msg["subject"] or ""
if not pretend:
if not self.pretend:
storage_id, metafile, datafile = self.add(
data(), milter.qid, mailfrom, recipients, subject)
logger.info(f"stored message in file {datafile}")
@@ -174,7 +184,7 @@ class FileMailStorage(BaseMailStorage):
def get_metadata(self, storage_id):
"Return metadata of email in storage."
super(FileMailStorage, self).get_metadata(storage_id)
super().get_metadata(storage_id)
if not self.metadata:
return None
@@ -197,7 +207,7 @@ class FileMailStorage(BaseMailStorage):
def find(self, mailfrom=None, recipients=None, older_than=None):
"Find emails in storage."
super(FileMailStorage, self).find(mailfrom, recipients, older_than)
super().find(mailfrom, recipients, older_than)
if isinstance(mailfrom, str):
mailfrom = [mailfrom]
if isinstance(recipients, str):
@@ -238,7 +248,7 @@ class FileMailStorage(BaseMailStorage):
def delete(self, storage_id, recipients=None):
"Delete email from storage."
super(FileMailStorage, self).delete(storage_id, recipients)
super().delete(storage_id, recipients)
if not recipients or not self.metadata:
self._remove(storage_id)
@@ -264,7 +274,7 @@ class FileMailStorage(BaseMailStorage):
self._save_metafile(metafile, metadata)
def get_mail(self, storage_id):
super(FileMailStorage, self).get_mail(storage_id)
super().get_mail(storage_id)
metadata = self.get_metadata(storage_id)
_, datafile = self._get_file_paths(storage_id)
@@ -275,31 +285,101 @@ class FileMailStorage(BaseMailStorage):
return (metadata, data)
class Store:
STORAGE_TYPES = {
"file": FileMailStorage}
def __init__(self, cfg, local_addrs, debug):
self.cfg = cfg
self.logger = logging.getLogger(cfg["name"])
self.logger.setLevel(cfg.get_loglevel(debug))
storage_type = cfg["args"]["type"]
del cfg["args"]["type"]
cfg["args"]["pretend"] = cfg["pretend"]
self._storage = self.STORAGE_TYPES[storage_type](
**cfg["args"])
self._headersonly = self._storage._headersonly
def __str__(self):
cfg = []
for key, value in self.cfg["args"].items():
cfg.append(f"{key}={value}")
class_name = type(self._storage).__name__
return f"{class_name}(" + ", ".join(cfg) + ")"
def execute(self, milter):
logger = CustomLogger(
self.logger, {"name": self.cfg["name"], "qid": milter.qid})
self._storage.execute(milter, logger)
class Quarantine:
"Quarantine class."
_headersonly = False
def __init__(self, storage, notification=None, whitelist=None,
milter_action=None, reject_reason="Message rejected"):
self.storage = storage.action(**storage.args, metadata=True)
self.storage_name = storage.name
self.storage_logger = storage.logger
def __init__(self, cfg, local_addrs, debug):
self.cfg = cfg
self.logger = logging.getLogger(cfg["name"])
self.logger.setLevel(cfg.get_loglevel(debug))
self.notification = notification
if self.notification is not None:
self.notification = notification.action(**notification.args)
self.notification_name = notification.name
self.notification_logger = notification.logger
self.whitelist = Conditions(whitelist)
self.milter_action = milter_action
self.reject_reason = reject_reason
store_cfg = ActionConfig({
"name": cfg["name"],
"loglevel": cfg["loglevel"],
"pretend": cfg["pretend"],
"type": "store",
"args": cfg["args"]["store"].get_config()})
self.store = Store(store_cfg, local_addrs, debug)
def execute(self, milter, pretend=False,
logger=logging.getLogger(__name__)):
self.notify = None
if "notify" in cfg["args"]:
notify_cfg = ActionConfig({
"name": cfg["name"],
"loglevel": cfg["loglevel"],
"pretend": cfg["pretend"],
"type": "notify",
"args": cfg["args"]["notify"].get_config()})
self.notify = Notify(notify_cfg, local_addrs, debug)
self.whitelist = None
if "whitelist" in cfg["args"]:
whitelist_cfg = cfg["args"]["whitelist"]
whitelist_cfg["name"] = cfg["name"]
whitelist_cfg["loglevel"] = cfg["loglevel"]
self.whitelist = Conditions(
whitelist_cfg,
local_addrs=[],
debug=debug)
self.milter_action = None
if "milter_action" in cfg["args"]:
self.milter_action = cfg["args"]["milter_action"]
self.reject_reason = None
if "reject_reason" in cfg["args"]:
self.reject_reason = cfg["args"]["reject_reason"]
def __str__(self):
cfg = []
cfg.append(f"store={str(self.store)}")
if self.notify is not None:
cfg.append(f"notify={str(self.notify)}")
if self.whitelist is not None:
cfg.append(f"whitelist={str(self.whitelist)}")
for key in ["milter_action", "reject_reason"]:
if key not in self.cfg["args"]:
continue
value = self.cfg["args"][key]
cfg.append(f"{key}={value}")
class_name = type(self).__name__
return f"{class_name}(" + ", ".join(cfg) + ")"
def execute(self, milter):
logger = CustomLogger(
self.logger, {"name": self.cfg["name"], "qid": milter.qid})
wl_rcpts = []
if self.whitelist:
wl_rcpts = self.whitelist.get_wl_rcpts(
milter.msginfo["mailfrom"], milter.msginfo["rcpts"])
milter.msginfo["mailfrom"], milter.msginfo["rcpts"], logger)
logger.info(f"whitelisted recipients: {wl_rcpts}")
rcpts = [
@@ -312,14 +392,10 @@ class Quarantine:
logger.info(f"add to quarantine for recipients: {rcpts}")
milter.msginfo["rcpts"] = rcpts
custom_logger = CustomLogger(
self.storage_logger, {"name": self.storage_name})
self.storage.execute(milter, pretend, custom_logger)
self.store.execute(milter)
if self.notification is not None:
custom_logger = CustomLogger(
self.notification_logger, {"name": self.notification_name})
self.notification.execute(milter, pretend, custom_logger)
if self.notify is not None:
self.notify.execute(milter)
milter.msginfo["rcpts"].extend(wl_rcpts)
milter.delrcpt(rcpts)