refactoring; more logging

This commit is contained in:
Dominik Chilla 2022-02-28 23:41:30 +01:00
parent c93916cf19
commit f06be7b0c3
16 changed files with 204 additions and 179 deletions

View File

@ -5,7 +5,7 @@ import random
import re import re
import email.utils import email.utils
import authres import authres
from lam_globals import g_config, g_policy_backend from lam_backends import g_config_backend, g_policy_backend
from lam_rex import g_rex_domain, g_rex_srs from lam_rex import g_rex_domain, g_rex_srs
from lam_logger import log_debug, log_info, log_warning, log_error from lam_logger import log_debug, log_info, log_warning, log_error
from lam_exceptions import LamSoftException, LamHardException from lam_exceptions import LamSoftException, LamHardException
@ -57,7 +57,7 @@ class LdapAclMilter(Milter.Base):
self.passed_dkim_results = [] self.passed_dkim_results = []
self.log_debug("reset(): {}".format(self.__dict__)) self.log_debug("reset(): {}".format(self.__dict__))
# https://stackoverflow.com/a/2257449 # https://stackoverflow.com/a/2257449
self.mconn_id = g_config.milter_name + ': ' + ''.join( self.mconn_id = g_config_backend.milter_name + ': ' + ''.join(
random.choice(string.ascii_lowercase + string.digits) for _ in range(8) random.choice(string.ascii_lowercase + string.digits) for _ in range(8)
) )
@ -69,12 +69,12 @@ class LdapAclMilter(Milter.Base):
smtp_code = None smtp_code = None
smtp_ecode = None smtp_ecode = None
if kwargs['action'] == 'reject': if kwargs['action'] == 'reject':
message = g_config.milter_reject_message message = g_config_backend.milter_reject_message
smtp_code = '550' smtp_code = '550'
smtp_ecode = '5.7.1' smtp_ecode = '5.7.1'
smfir = Milter.REJECT smfir = Milter.REJECT
elif kwargs['action'] == 'tmpfail': elif kwargs['action'] == 'tmpfail':
message = g_config.milter_tmpfail_message message = g_config_backend.milter_tmpfail_message
smtp_code = '450' smtp_code = '450'
smtp_ecode = '4.7.1' smtp_ecode = '4.7.1'
smfir = Milter.TEMPFAIL smfir = Milter.TEMPFAIL
@ -93,8 +93,8 @@ class LdapAclMilter(Milter.Base):
if 'reason' in kwargs: if 'reason' in kwargs:
message = "{0} - reason: {1}".format(message, kwargs['reason']) message = "{0} - reason: {1}".format(message, kwargs['reason'])
if kwargs['action'] == 'reject' or kwargs['action'] == 'tmpfail': if kwargs['action'] == 'reject' or kwargs['action'] == 'tmpfail':
self.log_info("milter_action={0} message={1}".format( self.log_info("{0} - milter_action={1} message={2}".format(
kwargs['action'], message self.mconn_id, kwargs['action'], message
)) ))
self.setreply(smtp_code, smtp_ecode, message) self.setreply(smtp_code, smtp_ecode, message)
return smfir return smfir
@ -119,7 +119,7 @@ class LdapAclMilter(Milter.Base):
def envfrom(self, mailfrom, *str): def envfrom(self, mailfrom, *str):
self.reset() self.reset()
self.proto_stage = 'FROM' self.proto_stage = 'FROM'
if g_config.milter_expect_auth: if g_config_backend.milter_expect_auth:
try: try:
# this may fail, if no x509 client certificate was used. # this may fail, if no x509 client certificate was used.
# postfix only passes this macro to milters if the TLS connection # postfix only passes this macro to milters if the TLS connection
@ -181,24 +181,28 @@ class LdapAclMilter(Milter.Base):
to = to.replace(">","") to = to.replace(">","")
to = to.lower() to = to.lower()
self.log_debug("5321.rcpt={}".format(to)) self.log_debug("5321.rcpt={}".format(to))
if to in g_config.milter_whitelisted_rcpts: if to in g_config_backend.milter_whitelisted_rcpts:
return self.milter_action(action = 'continue') return self.milter_action(action = 'continue')
if g_config.milter_dkim_enabled: if g_config_backend.milter_dkim_enabled:
# Collect all envelope-recipients for later # Collect all envelope-recipients for later
# investigation (EOM). Do not perform any # investigation (EOM). Do not perform any
# policy action at this protocol phase. # policy action at this protocol phase.
self.env_rcpts.append(to) self.env_rcpts.append(to)
else: else:
# DKIM disabled. Policy enforcement takes place here.
try: try:
ret = g_policy_backend.check_policy( g_policy_backend.check_policy(
from_addr=self.env_from, rcpt_addr=to, from_source='5321.from', lam_session=self from_addr = self.env_from,
rcpt_addr = to,
from_source = 'envelope',
lam_session = self
) )
self.log_info(ret) self.env_rcpts.append(to)
except LamSoftException as e: except LamSoftException as e:
if g_config.milter_mode == 'reject': if g_config_backend.milter_mode == 'reject':
return self.milter_action(action = 'tmpfail') return self.milter_action(action = 'tmpfail')
except LamHardException as e: except LamHardException as e:
if g_config.milter_mode == 'reject': if g_config_backend.milter_mode == 'reject':
return self.milter_action( return self.milter_action(
action = 'reject', action = 'reject',
reason = e.message reason = e.message
@ -210,7 +214,7 @@ class LdapAclMilter(Milter.Base):
def header(self, hname, hval): def header(self, hname, hval):
self.proto_stage = 'HDR' self.proto_stage = 'HDR'
self.queue_id = self.getsymval('i') self.queue_id = self.getsymval('i')
if g_config.milter_dkim_enabled == True: if g_config_backend.milter_dkim_enabled == True:
# Parse RFC-5322-From header # Parse RFC-5322-From header
if(hname.lower() == "From".lower()): if(hname.lower() == "From".lower()):
hdr_5322_from = email.utils.parseaddr(hval) hdr_5322_from = email.utils.parseaddr(hval)
@ -232,7 +236,7 @@ class LdapAclMilter(Milter.Base):
ar = authres.AuthenticationResultsHeader.parse( ar = authres.AuthenticationResultsHeader.parse(
"{0}: {1}".format(hname, hval) "{0}: {1}".format(hname, hval)
) )
if ar.authserv_id.lower() == g_config.milter_trusted_authservid.lower(): if ar.authserv_id.lower() == g_config_backend.milter_trusted_authservid.lower():
for ar_result in ar.results: for ar_result in ar.results:
if ar_result.method.lower() == 'dkim': if ar_result.method.lower() == 'dkim':
if ar_result.result.lower() == 'pass': if ar_result.result.lower() == 'pass':
@ -249,13 +253,13 @@ class LdapAclMilter(Milter.Base):
def eom(self): def eom(self):
self.proto_stage = 'EOM' self.proto_stage = 'EOM'
if g_config.milter_max_rcpt_enabled: if g_config_backend.milter_max_rcpt_enabled:
if len(self.env_rcpts) > int(g_config.milter_max_rcpt): if len(self.env_rcpts) > int(g_config_backend.milter_max_rcpt):
if g_config.milter_mode == 'reject': if g_config_backend.milter_mode == 'reject':
return self.milter_action(action='reject', reason='Too many recipients!') return self.milter_action(action='reject', reason='Too many recipients!')
else: else:
self.do_log("TEST-Mode: Too many recipients!") self.do_log("TEST-Mode: Too many recipients!")
if g_config.milter_dkim_enabled: if g_config_backend.milter_dkim_enabled:
self.log_info("5321.from={0} 5322.from={1} 5322.from_domain={2} 5321.rcpt={3}".format( self.log_info("5321.from={0} 5322.from={1} 5322.from_domain={2} 5321.rcpt={3}".format(
self.env_from, self.hdr_from, self.hdr_from_domain, self.env_rcpts self.env_from, self.hdr_from, self.hdr_from_domain, self.env_rcpts
)) ))
@ -272,42 +276,55 @@ class LdapAclMilter(Milter.Base):
for rcpt in self.env_rcpts: for rcpt in self.env_rcpts:
try: try:
# Check 5321.from against policy # Check 5321.from against policy
ret = g_policy_backend.check_policy( g_policy_backend.check_policy(
from_addr=self.env_from, rcpt_addr=rcpt, from_source='5321.from', lam_session=self from_addr=self.env_from,
rcpt_addr=rcpt,
from_source='envelope',
lam_session=self
)
self.log_info(
"action=pass 5321.from={0} 5321.rcpt={1}".format(self.env_from, rcpt)
) )
self.log_info(ret)
except LamSoftException as e: except LamSoftException as e:
self.log_info(str(e)) self.log_info(e.message)
if g_config.milter_mode == 'reject': if g_config_backend.milter_mode == 'reject':
return self.milter_action(action = 'tmpfail') return self.milter_action(action = 'tmpfail')
else: else:
self.log_info("TEST-Mode - tmpfail") self.log_info("TEST-Mode - tmpfail")
except LamHardException as e: except LamHardException as e:
self.log_info(e.message)
if self.dkim_aligned: if self.dkim_aligned:
try: try:
# Check 5322.from against policy # Check 5322.from against policy
ret = g_policy_backend.check_policy( g_policy_backend.check_policy(
from_addr=self.hdr_from, rcpt_addr=rcpt, from_source='5322.from', lam_session=self from_addr=self.hdr_from,
rcpt_addr=rcpt,
from_source='from-header',
lam_session=self
)
self.log_info(
"action=pass 5322.from={0} 5321.rcpt={1}".format(self.hdr_from, rcpt)
) )
self.log_info(ret)
self.log_info("5322.from={} authorized by DKIM signature".format(
self.hdr_from
))
except LamHardException as e: except LamHardException as e:
reject_message = True reject_message = True
else: else:
reject_message = True reject_message = True
if reject_message:
if g_config_backend.milter_mode == 'reject':
return self.milter_action(
action = 'reject',
reason = 'policy mismatch! Message rejected for all recipients!'
)
else:
self.log_info(
"TEST-Mode: policy mismatch! Message would be rejected for all recipients!"
)
else:
# * DKIM check disabled
# Iterate through all accepted envelope recipients and log
for rcpt in self.env_rcpts:
self.log_info("action=pass 5321.from={0} 5321.rcpt={1}".format(self.env_from, rcpt))
if reject_message:
if g_config.milter_mode == 'reject':
return self.milter_action(
action = 'reject',
reason = 'policy mismatch! Message rejected for all recipients!'
)
else:
self.log_info(
"TEST-Mode: policy mismatch! Message would be rejected for all recipients!"
)
return self.milter_action(action = 'continue') return self.milter_action(action = 'continue')
def abort(self): def abort(self):

30
app/lam_backends.py Normal file
View File

@ -0,0 +1,30 @@
import traceback
from lam_exceptions import (
LamInitException, LamPolicyBackendException, LamConfigBackendException
)
from lam_logger import init_logger
from lam_config_backend import LamConfigBackend
from lam_policy_backend import LamPolicyBackend
init_logger()
g_config_backend = None
try:
if g_config_backend is None:
g_config_backend = LamConfigBackend()
except LamConfigBackendException as e:
raise LamInitException(e.message) from e
except Exception as e:
raise LamInitException(traceback.format_exc()) from e
# Instantiate the LDAP policy backend and
# establish a permanent connection with the LDAP server
# which will be reused on any milter connection
g_policy_backend = None
try:
if g_policy_backend is None:
g_policy_backend = LamPolicyBackend(g_config_backend)
except LamPolicyBackendException as e:
raise LamInitException(e.message) from e
except Exception as e:
raise LamInitException(traceback.format_exc()) from e

View File

@ -1,23 +1,25 @@
import re import re
import os import os
from lam_logger import log_info from lam_logger import log_info
from lam_exceptions import LamConfigException from lam_exceptions import LamConfigBackendException
from lam_rex import g_rex_email from lam_rex import g_rex_email
class LamConfig(): class LamConfigBackend():
def __init__(self): def __init__(self):
self.milter_mode = 'test'
self.milter_name = 'ldap-acl-milter' self.milter_name = 'ldap-acl-milter'
self.milter_mode = 'test'
self.milter_socket = '/socket/{}'.format(self.milter_name) self.milter_socket = '/socket/{}'.format(self.milter_name)
self.milter_timeout = 60
self.milter_reject_message = 'Security policy violation!' self.milter_reject_message = 'Security policy violation!'
self.milter_tmpfail_message = 'Service temporarily not available! Please try again later.' self.milter_tmpfail_message = 'Service temporarily not available! Please try again later.'
self.ldap_server = 'ldap://127.0.0.1:389' self.ldap_server = 'ldap://127.0.0.1:389'
self.ldap_server_connect_timeout = 3
self.ldap_binddn = 'cn=ldap-reader,ou=binds,dc=example,dc=org' self.ldap_binddn = 'cn=ldap-reader,ou=binds,dc=example,dc=org'
self.ldap_bindpw = 'TopSecret;-)' self.ldap_bindpw = 'TopSecret;-)'
self.ldap_base = 'ou=lam,ou=services,dc=example,dc=org' self.ldap_base = 'ou=lam,ou=services,dc=example,dc=org'
self.ldap_query = '(&(mail=%rcpt%)(allowedEnvelopeSender=%from%))' self.ldap_query = '(&(mail=%rcpt%)(allowedEnvelopeSender=%from%))'
self.milter_schema = False self.milter_schema = False
self.milter_schema_wildcard_domain = False # works only if milter_schema == True self.milter_schema_wildcard_domain = False
self.milter_expect_auth = False self.milter_expect_auth = False
self.milter_whitelisted_rcpts = {} self.milter_whitelisted_rcpts = {}
self.milter_dkim_enabled = False self.milter_dkim_enabled = False
@ -25,75 +27,127 @@ class LamConfig():
self.milter_max_rcpt_enabled = False self.milter_max_rcpt_enabled = False
self.milter_max_rcpt = 1 self.milter_max_rcpt = 1
if 'MILTER_NAME' in os.environ:
self.milter_name = os.environ['MILTER_NAME']
if 'MILTER_MODE' in os.environ: if 'MILTER_MODE' in os.environ:
if re.match(r'^test|reject$',os.environ['MILTER_MODE'], re.IGNORECASE): if re.match(r'^test|reject$',os.environ['MILTER_MODE'], re.IGNORECASE):
self.milter_mode = os.environ['MILTER_MODE'].lower() self.milter_mode = os.environ['MILTER_MODE'].lower()
if 'MILTER_NAME' in os.environ: log_info("ENV[MILTER_MODE]: {}". format(self.milter_mode))
self.milter_name = os.environ['MILTER_NAME']
if 'MILTER_SOCKET' in os.environ:
self.milter_socket = os.environ['MILTER_SOCKET']
log_info("ENV[MILTER_SOCKET]: {}".format(self.milter_socket))
if 'MILTER_TIMEOUT' in os.environ:
if os.environ['MILTER_TIMEOUT'].isnumeric():
self.milter_timeout = int(os.environ['MILTER_TIMEOUT'])
else:
raise LamConfigBackendException("ENV[MILTER_TIMEOUT] must be numeric!")
log_info("ENV[MILTER_TIMEOUT]: {}".format(self.milter_timeout))
if 'MILTER_SCHEMA' in os.environ: if 'MILTER_SCHEMA' in os.environ:
if re.match(r'^true$', os.environ['MILTER_SCHEMA'], re.IGNORECASE): if re.match(r'^true$', os.environ['MILTER_SCHEMA'], re.IGNORECASE):
self.milter_schema = True self.milter_schema = True
if 'MILTER_SCHEMA_WILDCARD_DOMAIN' in os.environ: if 'MILTER_SCHEMA_WILDCARD_DOMAIN' in os.environ:
if re.match(r'^true$', os.environ['MILTER_SCHEMA_WILDCARD_DOMAIN'], re.IGNORECASE): if re.match(r'^true$', os.environ['MILTER_SCHEMA_WILDCARD_DOMAIN'], re.IGNORECASE):
self.milter_schema_wildcard_domain = True self.milter_schema_wildcard_domain = True
log_info("ENV[MILTER_SCHEMA]: {}".format(self.milter_schema))
log_info(
"ENV[MILTER_SCHEMA_WILDCARD_DOMAIN]: {}".format(
self.milter_schema_wildcard_domain
)
)
if 'LDAP_SERVER' not in os.environ: if 'LDAP_SERVER' not in os.environ:
raise LamConfigException("Missing ENV[LDAP_SERVER], e.g. {}".format(self.ldap_server)) raise LamConfigBackendException(
"Missing ENV[LDAP_SERVER], e.g. {}".format(self.ldap_server)
)
self.ldap_server = os.environ['LDAP_SERVER'] self.ldap_server = os.environ['LDAP_SERVER']
log_info("ENV[LDAP_SERVER]: {}".format(self.ldap_server))
if 'LDAP_SERVER_CONNECT_TIMEOUT' in os.environ:
if not os.environ['LDAP_SERVER_CONNECT_TIMEOUT'].isnumeric():
raise LamConfigBackendException(
"ENV[LDAP_SERVER_CONNECT_TIMEOUT] must be numeric!"
)
self.ldap_server_connect_timeout = int(os.environ['LDAP_SERVER_CONNECT_TIMEOUT'])
log_info("ENV[LDAP_SERVER_CONNECT_TIMEOUT]: {}".format(
self.ldap_server_connect_timeout
))
if 'LDAP_BINDDN' in os.environ: if 'LDAP_BINDDN' in os.environ:
self.ldap_binddn = os.environ['LDAP_BINDDN'] self.ldap_binddn = os.environ['LDAP_BINDDN']
if 'LDAP_BINDPW' in os.environ: if 'LDAP_BINDPW' in os.environ:
self.ldap_bindpw = os.environ['LDAP_BINDPW'] self.ldap_bindpw = os.environ['LDAP_BINDPW']
if 'LDAP_BASE' not in os.environ: if 'LDAP_BASE' not in os.environ:
raise LamConfigException( raise LamConfigBackendException(
"Missing ENV[LDAP_BASE], e.g. {}".format(self.ldap_base) "Missing ENV[LDAP_BASE], e.g. {}".format(self.ldap_base)
) )
self.ldap_base = os.environ['LDAP_BASE'] self.ldap_base = os.environ['LDAP_BASE']
log_info("ENV[LDAP_BASE]: {}".format(self.ldap_base))
if 'LDAP_QUERY' not in os.environ: if 'LDAP_QUERY' not in os.environ:
if self.milter_schema == False: if self.milter_schema == False:
raise LamConfigException( raise LamConfigBackendException(
"ENV[MILTER_SCHEMA] is disabled and ENV[LDAP_QUERY] is not set instead!" "ENV[MILTER_SCHEMA] is disabled and ENV[LDAP_QUERY] is not set instead!"
) )
if 'LDAP_QUERY' in os.environ: else:
self.ldap_query = os.environ['LDAP_QUERY'] self.ldap_query = os.environ['LDAP_QUERY']
if 'MILTER_SOCKET' in os.environ: log_info("ENV[LDAP_QUERY]: {}".format(self.ldap_query))
self.milter_socket = os.environ['MILTER_SOCKET']
if 'MILTER_REJECT_MESSAGE' in os.environ: if 'MILTER_REJECT_MESSAGE' in os.environ:
self.milter_reject_message = os.environ['MILTER_REJECT_MESSAGE'] self.milter_reject_message = os.environ['MILTER_REJECT_MESSAGE']
log_info("ENV[MILTER_REJECT_MESSAGE]: {}".format(self.milter_reject_message))
if 'MILTER_TMPFAIL_MESSAGE' in os.environ: if 'MILTER_TMPFAIL_MESSAGE' in os.environ:
self.milter_tmpfail_message = os.environ['MILTER_TMPFAIL_MESSAGE'] self.milter_tmpfail_message = os.environ['MILTER_TMPFAIL_MESSAGE']
log_info("ENV[MILTER_TMPFAIL_MESSAGE]: {}".format(self.milter_tmpfail_message))
if 'MILTER_EXPECT_AUTH' in os.environ: if 'MILTER_EXPECT_AUTH' in os.environ:
if re.match(r'^true$', os.environ['MILTER_EXPECT_AUTH'], re.IGNORECASE): if re.match(r'^true$', os.environ['MILTER_EXPECT_AUTH'], re.IGNORECASE):
self.milter_expect_auth = True self.milter_expect_auth = True
log_info("ENV[MILTER_EXPECT_AUTH]: {}".format(self.milter_expect_auth))
if 'MILTER_WHITELISTED_RCPTS' in os.environ: if 'MILTER_WHITELISTED_RCPTS' in os.environ:
# A blank separated list is expected # A blank separated list is expected
whitelisted_rcpts_str = os.environ['MILTER_WHITELISTED_RCPTS'] whitelisted_rcpts_str = os.environ['MILTER_WHITELISTED_RCPTS']
for whitelisted_rcpt in re.split(',|\s', whitelisted_rcpts_str): for whitelisted_rcpt in re.split(',|\s', whitelisted_rcpts_str):
if g_rex_email.match(whitelisted_rcpt) == None: if g_rex_email.match(whitelisted_rcpt) == None:
raise LamConfigException( raise LamConfigBackendException(
"ENV[MILTER_WHITELISTED_RCPTS]: invalid email address: {}" "ENV[MILTER_WHITELISTED_RCPTS]: invalid email address: {}"
.format(whitelisted_rcpt) .format(whitelisted_rcpt)
) )
else: else:
log_info("ENV[MILTER_WHITELISTED_RCPTS]: {}".format(
whitelisted_rcpt
))
self.milter_whitelisted_rcpts[whitelisted_rcpt] = {} self.milter_whitelisted_rcpts[whitelisted_rcpt] = {}
log_info(
"ENV[MILTER_WHITELISTED_RCPTS]: {}".format(
self.milter_whitelisted_rcpts
)
)
if 'MILTER_DKIM_ENABLED' in os.environ: if 'MILTER_DKIM_ENABLED' in os.environ:
self.milter_dkim_enabled = True self.milter_dkim_enabled = True
if 'MILTER_TRUSTED_AUTHSERVID' in os.environ: if 'MILTER_TRUSTED_AUTHSERVID' in os.environ:
self.milter_trusted_authservid = os.environ['MILTER_TRUSTED_AUTHSERVID'].lower() self.milter_trusted_authservid = os.environ['MILTER_TRUSTED_AUTHSERVID'].lower()
log_info("ENV[MILTER_TRUSTED_AUTHSERVID]: {0}".format( log_info(
self.milter_trusted_authservid "ENV[MILTER_TRUSTED_AUTHSERVID]: {}".format(
)) self.milter_trusted_authservid
)
)
else: else:
raise LamConfigException("ENV[MILTER_TRUSTED_AUTHSERVID] is mandatory!") raise LamConfigBackendException("ENV[MILTER_TRUSTED_AUTHSERVID] is mandatory!")
log_info("ENV[MILTER_DKIM_ENABLED]: {0}".format(self.milter_dkim_enabled)) log_info("ENV[MILTER_DKIM_ENABLED]: {}".format(self.milter_dkim_enabled))
if 'MILTER_MAX_RCPT_ENABLED' in os.environ: if 'MILTER_MAX_RCPT_ENABLED' in os.environ:
self.milter_max_rcpt_enabled = True self.milter_max_rcpt_enabled = True
if 'MILTER_MAX_RCPT' in os.environ: if 'MILTER_MAX_RCPT' in os.environ:
if os.environ['MILTER_MAX_RCPT'].isnumeric(): if os.environ['MILTER_MAX_RCPT'].isnumeric():
self.milter_max_rcpt = os.environ['MILTER_MAX_RCPT'] self.milter_max_rcpt = int(os.environ['MILTER_MAX_RCPT'])
log_info("ENV[MILTER_MAX_RCPT]: {}".format(self.milter_max_rcpt))
else: else:
raise LamConfigException("ENV[MILTER_MAX_RCPT] must be numeric!") raise LamConfigBackendException("ENV[MILTER_MAX_RCPT] must be numeric!")
log_info("ENV[MILTER_MAX_RCPT_ENABLED]: {}".format(self.milter_max_rcpt_enabled))

View File

@ -16,5 +16,5 @@ class LamHardException(LamException):
class LamPolicyBackendException(LamException): class LamPolicyBackendException(LamException):
pass pass
class LamConfigException(LamException): class LamConfigBackendException(LamException):
pass pass

View File

@ -1,26 +0,0 @@
import sys
from lam_exceptions import (
LamInitException, LamPolicyBackendException, LamConfigException
)
from lam_config import LamConfig
from lam_logger import init_logger
from lam_policy_backend import LamPolicyBackend
init_logger()
g_config = None
try:
if g_config is None:
g_config = LamConfig()
except LamConfigException as e:
raise LamInitException(e.message) from e
# Instantiate the LDAP policy backend and
# establish a permanent connection with the LDAP server
# which will be reused on any milter connection
g_policy_backend = None
try:
if g_policy_backend is None:
g_policy_backend = LamPolicyBackend(g_config)
except LamPolicyBackendException as e:
raise LamInitException(e.message) from e

View File

@ -17,8 +17,8 @@ class LamPolicyBackend():
set_config_parameter("RESTARTABLE_SLEEPTIME", 2) set_config_parameter("RESTARTABLE_SLEEPTIME", 2)
set_config_parameter("RESTARTABLE_TRIES", 2) set_config_parameter("RESTARTABLE_TRIES", 2)
server = Server( server = Server(
self.config.ldap_server, host = self.config.ldap_server,
connect_timeout = 3, connect_timeout = self.config.ldap_server_connect_timeout,
get_info = NONE get_info = NONE
) )
self.ldap_conn = Connection(server, self.ldap_conn = Connection(server,
@ -34,19 +34,19 @@ class LamPolicyBackend():
"Connection to LDAP-server failed: {}".format(str(e)) "Connection to LDAP-server failed: {}".format(str(e))
) from e ) from e
def check_policy(self, **kwargs) -> str: def check_policy(self, **kwargs):
from_addr = kwargs['from_addr'] from_addr = kwargs['from_addr']
rcpt_addr = kwargs['rcpt_addr'] rcpt_addr = kwargs['rcpt_addr']
from_source = kwargs['from_source'] from_source = kwargs['from_source']
lam_session = kwargs['lam_session'] lam_session = kwargs['lam_session']
mcid = "{}/Policy".format(lam_session.mconn_id)
m = g_rex_domain.match(from_addr) m = g_rex_domain.match(from_addr)
if m == None: if m == None:
log_info("Could not determine domain of from={0}".format( raise LamHardException(
from_addr "Could not determine domain of from={0}".format(from_addr)
)) )
raise LamSoftException()
from_domain = m.group(1) from_domain = m.group(1)
log_debug("from_domain={}".format(from_domain)) log_debug("{0} from_domain={1}".format(mcid, from_domain))
m = g_rex_domain.match(rcpt_addr) m = g_rex_domain.match(rcpt_addr)
if m == None: if m == None:
raise LamHardException( raise LamHardException(
@ -55,7 +55,7 @@ class LamPolicyBackend():
) )
) )
rcpt_domain = m.group(1) rcpt_domain = m.group(1)
log_debug("rcpt_domain={}".format(rcpt_domain)) log_debug("{0} rcpt_domain={1}".format(mcid, rcpt_domain))
try: try:
if self.config.milter_schema == True: if self.config.milter_schema == True:
# LDAP-ACL-Milter schema # LDAP-ACL-Milter schema
@ -77,7 +77,7 @@ class LamPolicyBackend():
) )
else: else:
auth_method = auth_method.replace('%X509_AUTH%','') auth_method = auth_method.replace('%X509_AUTH%','')
log_debug("auth_method: {}".format(auth_method)) log_debug("{0} auth_method: {1}".format(mcid, auth_method))
if self.config.milter_schema_wildcard_domain == True: if self.config.milter_schema_wildcard_domain == True:
# The asterisk (*) character is in term of local part # The asterisk (*) character is in term of local part
# RFC5322 compliant and expected as a wildcard literal in this code. # RFC5322 compliant and expected as a wildcard literal in this code.
@ -126,26 +126,28 @@ class LamPolicyBackend():
) )
if len(self.ldap_conn.entries) == 0: if len(self.ldap_conn.entries) == 0:
# Policy not found in LDAP # Policy not found in LDAP
ex = "policy mismatch: from={0} from_src={1} rcpt={2}".format( raise LamHardException(
from_addr, from_source, rcpt_addr "mismatch: from_src={0} from={1} rcpt={2}".format(
) from_source, from_addr, rcpt_addr
if self.config.milter_expect_auth == True:
ex = "policy mismatch: from={0} from_src={1} rcpt={2} auth_method={3}".format(
from_addr, from_source, rcpt_addr, auth_method
) )
raise LamHardException(ex) )
elif len(self.ldap_conn.entries) == 1: elif len(self.ldap_conn.entries) == 1:
if from_source == 'from-header':
log_info("{0} 5322.from={1} authorized by DKIM signature".format(
mcid, from_addr
))
# Policy found in LDAP, but which one? # Policy found in LDAP, but which one?
entry = self.ldap_conn.entries[0] entry = self.ldap_conn.entries[0]
return "policy match: '{0}' from_src={1}".format( log_info("{0} match: '{1}' from_src={2}".format(
entry.policyID.value, from_source mcid, entry.policyID.value, from_source
) ))
elif len(self.ldap_conn.entries) > 1: elif len(self.ldap_conn.entries) > 1:
# Something went wrong!? There shouldn´t be more than one entries! # Something went wrong!? There shouldn´t be more than one entries!
log_error("More than one policies found! from={0} rcpt={1} auth_method={2}".format( raise LamHardException(
from_addr, rcpt_addr, auth_method "More than one policies found! from={0} rcpt={1} auth_method={2}".format(
)) from_addr, rcpt_addr, auth_method
raise LamHardException("More than one policies found!") )
)
else: else:
# Custom LDAP schema # Custom LDAP schema
# 'build' a LDAP query per recipient # 'build' a LDAP query per recipient
@ -156,18 +158,14 @@ class LamPolicyBackend():
query = query.replace("%sasl_user%", lam_session.sasl_user) query = query.replace("%sasl_user%", lam_session.sasl_user)
query = query.replace("%from_domain%", from_domain) query = query.replace("%from_domain%", from_domain)
query = query.replace("%rcpt_domain%", rcpt_domain) query = query.replace("%rcpt_domain%", rcpt_domain)
log_debug("LDAP query: {}".format(query)) log_debug("{0} LDAP query: {1}".format(mcid, query))
self.ldap_conn.search(self.config.ldap_base, query) self.ldap_conn.search(self.config.ldap_base, query)
if len(self.ldap_conn.entries) == 0: if len(self.ldap_conn.entries) == 0:
log_info( raise LamHardException(
"policy mismatch from={0} from_src={1} rcpt={2}".format( "mismatch from_src={0} from={1} rcpt={2}".format(
from_addr, from_source, rcpt_addr from_source, from_addr, rcpt_addr
) )
) )
raise LamHardException("policy mismatch") log_info("{0} match from_src={1}".format(mcid, from_source))
return "policy match: '{0}' from_src={1}".format(
entry.policyID.value, from_source
)
except LDAPException as e: except LDAPException as e:
log_error("LDAP exception: {}".format(str(e)))
raise LamSoftException("LDAP exception: " + str(e)) from e raise LamSoftException("LDAP exception: " + str(e)) from e

View File

@ -4,7 +4,7 @@ import traceback
from lam_exceptions import LamInitException from lam_exceptions import LamInitException
from lam_logger import log_info, log_error from lam_logger import log_info, log_error
try: try:
import lam_globals import lam_backends
except LamInitException as e: except LamInitException as e:
log_error("Init exception: {}".format(e.message)) log_error("Init exception: {}".format(e.message))
sys.exit(1) sys.exit(1)
@ -12,24 +12,22 @@ from lam import LdapAclMilter
if __name__ == "__main__": if __name__ == "__main__":
try: try:
timeout = 600 timeout = lam_backends.g_config_backend.milter_timeout
# Register to have the Milter factory create instances of your class: # Register to have the Milter factory create instances of your class:
Milter.factory = LdapAclMilter Milter.factory = LdapAclMilter
# Tell the MTA which features we use # Tell the MTA which features we use
flags = Milter.ADDHDRS flags = Milter.ADDHDRS
Milter.set_flags(flags) Milter.set_flags(flags)
log_info("Starting {0}@socket: {1} in mode {2}".format( log_info("Starting {}".format(
lam_globals.g_config.milter_name, lam_backends.g_config_backend.milter_name
lam_globals.g_config.milter_socket,
lam_globals.g_config.milter_mode
)) ))
Milter.runmilter( Milter.runmilter(
lam_globals.g_config.milter_name, lam_backends.g_config_backend.milter_name,
lam_globals.g_config.milter_socket, lam_backends.g_config_backend.milter_socket,
timeout, timeout,
True True
) )
log_info("Shutdown {}".format(lam_globals.g_config.milter_name)) log_info("Shutdown {}".format(lam_backends.g_config_backend.milter_name))
except: except:
log_error("MAIN-EXCEPTION: {}".format(traceback.format_exc())) log_error("MAIN-EXCEPTION: {}".format(traceback.format_exc()))
sys.exit(1) sys.exit(1)

View File

@ -16,18 +16,12 @@ mt.set_timeout(60)
if mt.mailfrom(conn, "tester-ip@test.blah") ~= nil then if mt.mailfrom(conn, "tester-ip@test.blah") ~= nil then
error "mt.mailfrom() failed" error "mt.mailfrom() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.mailfrom() unexpected reply"
end
-- 5321.RCPT+MACROS -- 5321.RCPT+MACROS
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ") mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ")
if mt.rcptto(conn, "<rcpt-ip@test.blubb>") ~= nil then if mt.rcptto(conn, "<rcpt-ip@test.blubb>") ~= nil then
error "mt.rcptto() failed" error "mt.rcptto() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.rcptto() unexpected reply"
end
-- 5322.HEADERS -- 5322.HEADERS
if mt.header(conn, "fRoM", '"Blah Blubb" <tester-ip@test.blah>') ~= nil then if mt.header(conn, "fRoM", '"Blah Blubb" <tester-ip@test.blah>') ~= nil then
@ -54,18 +48,12 @@ end
if mt.mailfrom(conn, "tester-ip2@test.blah") ~= nil then if mt.mailfrom(conn, "tester-ip2@test.blah") ~= nil then
error "mt.mailfrom() failed" error "mt.mailfrom() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.mailfrom() unexpected reply"
end
-- 5321.RCPT+MACROS -- 5321.RCPT+MACROS
mt.macro(conn, SMFIC_RCPT, "i", "conn-reused-QID") mt.macro(conn, SMFIC_RCPT, "i", "conn-reused-QID")
if mt.rcptto(conn, "<rcpt-ip2@test.blubb>") ~= nil then if mt.rcptto(conn, "<rcpt-ip2@test.blubb>") ~= nil then
error "mt.rcptto() failed" error "mt.rcptto() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.rcptto() unexpected reply"
end
-- 5322.HEADERS -- 5322.HEADERS
if mt.header(conn, "fRoM", '"Blah Blubb" <tester-ip2@test.blah>') ~= nil then if mt.header(conn, "fRoM", '"Blah Blubb" <tester-ip2@test.blah>') ~= nil then

View File

@ -16,9 +16,6 @@ mt.set_timeout(60)
if mt.mailfrom(conn, "tester-ipx@test.blah") ~= nil then if mt.mailfrom(conn, "tester-ipx@test.blah") ~= nil then
error "mt.mailfrom() failed" error "mt.mailfrom() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.mailfrom() unexpected reply"
end
-- FIRST 5321.RCPT -- FIRST 5321.RCPT
if mt.rcptto(conn, "<rcpt-ip@test.blubb>") ~= nil then if mt.rcptto(conn, "<rcpt-ip@test.blubb>") ~= nil then

View File

@ -16,19 +16,12 @@ mt.set_timeout(60)
if mt.mailfrom(conn, "tester-noauth@test.blah") ~= nil then if mt.mailfrom(conn, "tester-noauth@test.blah") ~= nil then
error "mt.mailfrom() failed" error "mt.mailfrom() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.mailfrom() unexpected reply"
end
-- 5321.RCPT+MACROS -- 5321.RCPT+MACROS
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ") mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ")
if mt.rcptto(conn, "<rcpt-noauth@test.blubb>") ~= nil then if mt.rcptto(conn, "<rcpt-noauth@test.blubb>") ~= nil then
-- if mt.rcptto(conn, "<rcpt-noauth@>") ~= nil then
error "mt.rcptto() failed" error "mt.rcptto() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.rcptto() unexpected reply"
end
-- 5322.HEADERS -- 5322.HEADERS
if mt.header(conn, "fRoM", '"Blah Blubb" <tester-noauth@test.blah>') ~= nil then if mt.header(conn, "fRoM", '"Blah Blubb" <tester-noauth@test.blah>') ~= nil then

View File

View File

@ -17,18 +17,12 @@ mt.macro(conn, SMFIC_MAIL, "{auth_authen}", "blubb-user-wild")
if mt.mailfrom(conn, "tester-invalid@test.blah") ~= nil then if mt.mailfrom(conn, "tester-invalid@test.blah") ~= nil then
error "mt.mailfrom() failed" error "mt.mailfrom() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.mailfrom() unexpected reply"
end
-- 5321.RCPT+MACROS -- 5321.RCPT+MACROS
mt.macro(conn, SMFIC_RCPT, "i", "test-wildcard-qid") mt.macro(conn, SMFIC_RCPT, "i", "test-wildcard-qid")
if mt.rcptto(conn, "<anybody-xyz@out.there>") ~= nil then if mt.rcptto(conn, "<anybody-xyz@out.there>") ~= nil then
error "mt.rcptto() failed" error "mt.rcptto() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.rcptto() unexpected reply"
end
-- 5322.HEADERS -- 5322.HEADERS
if mt.header(conn, "fRoM", '"Blah Blubb" <tester@test.blah>') ~= nil then if mt.header(conn, "fRoM", '"Blah Blubb" <tester@test.blah>') ~= nil then

View File

@ -17,18 +17,12 @@ mt.macro(conn, SMFIC_MAIL, "{auth_authen}", "blubb-user1")
if mt.mailfrom(conn, "tester@test.blah") ~= nil then if mt.mailfrom(conn, "tester@test.blah") ~= nil then
error "mt.mailfrom() failed" error "mt.mailfrom() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.mailfrom() unexpected reply"
end
-- 5321.RCPT+MACROS -- 5321.RCPT+MACROS
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ") mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ")
if mt.rcptto(conn, "<rcpt@test.blubb>") ~= nil then if mt.rcptto(conn, "<rcpt@test.blubb>") ~= nil then
error "mt.rcptto() failed" error "mt.rcptto() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.rcptto() unexpected reply"
end
-- 5322.HEADERS -- 5322.HEADERS
if mt.header(conn, "fRoM", '"Blah Blubb" <tester@test.blah>') ~= nil then if mt.header(conn, "fRoM", '"Blah Blubb" <tester@test.blah>') ~= nil then

View File

View File

@ -18,18 +18,12 @@ mt.macro(conn, SMFIC_MAIL, "{cert_issuer}", "x509-issuer", "{cert_subject}", "x5
if mt.mailfrom(conn, "tester-x509@test.blah") ~= nil then if mt.mailfrom(conn, "tester-x509@test.blah") ~= nil then
error "mt.mailfrom() failed" error "mt.mailfrom() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.mailfrom() unexpected reply"
end
-- 5321.RCPT+MACROS -- 5321.RCPT+MACROS
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ") mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ")
if mt.rcptto(conn, "<rcpt-x509@test.blubb>") ~= nil then if mt.rcptto(conn, "<rcpt-x509@test.blubb>") ~= nil then
error "mt.rcptto() failed" error "mt.rcptto() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.rcptto() unexpected reply"
end
-- 5322.HEADERS -- 5322.HEADERS
if mt.header(conn, "fRoM", '"Blah Blubb" <tester@test.blah>') ~= nil then if mt.header(conn, "fRoM", '"Blah Blubb" <tester@test.blah>') ~= nil then

View File

@ -17,18 +17,12 @@ mt.macro(conn, SMFIC_MAIL, "{cert_issuer}", "x509-issuer", "{cert_subject}", "x5
if mt.mailfrom(conn, "tester-x509-invalid@test.blah") ~= nil then if mt.mailfrom(conn, "tester-x509-invalid@test.blah") ~= nil then
error "mt.mailfrom() failed" error "mt.mailfrom() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.mailfrom() unexpected reply"
end
-- 5321.RCPT+MACROS -- 5321.RCPT+MACROS
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ") mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ")
if mt.rcptto(conn, "<rcpt-x509@test.blubb>") ~= nil then if mt.rcptto(conn, "<rcpt-x509@test.blubb>") ~= nil then
error "mt.rcptto() failed" error "mt.rcptto() failed"
end end
if mt.getreply(conn) ~= SMFIR_CONTINUE then
error "mt.rcptto() unexpected reply"
end
-- 5322.HEADERS -- 5322.HEADERS
if mt.header(conn, "fRoM", '"Blah Blubb" <tester-x509@test.blah>') ~= nil then if mt.header(conn, "fRoM", '"Blah Blubb" <tester-x509@test.blah>') ~= nil then