mirror of
https://github.com/chillout2k/ldap-acl-milter.git
synced 2025-12-11 02:30:17 +00:00
refactoring; more logging
This commit is contained in:
parent
c93916cf19
commit
f06be7b0c3
99
app/lam.py
99
app/lam.py
@ -5,7 +5,7 @@ import random
|
||||
import re
|
||||
import email.utils
|
||||
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_logger import log_debug, log_info, log_warning, log_error
|
||||
from lam_exceptions import LamSoftException, LamHardException
|
||||
@ -57,7 +57,7 @@ class LdapAclMilter(Milter.Base):
|
||||
self.passed_dkim_results = []
|
||||
self.log_debug("reset(): {}".format(self.__dict__))
|
||||
# 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)
|
||||
)
|
||||
|
||||
@ -69,12 +69,12 @@ class LdapAclMilter(Milter.Base):
|
||||
smtp_code = None
|
||||
smtp_ecode = None
|
||||
if kwargs['action'] == 'reject':
|
||||
message = g_config.milter_reject_message
|
||||
message = g_config_backend.milter_reject_message
|
||||
smtp_code = '550'
|
||||
smtp_ecode = '5.7.1'
|
||||
smfir = Milter.REJECT
|
||||
elif kwargs['action'] == 'tmpfail':
|
||||
message = g_config.milter_tmpfail_message
|
||||
message = g_config_backend.milter_tmpfail_message
|
||||
smtp_code = '450'
|
||||
smtp_ecode = '4.7.1'
|
||||
smfir = Milter.TEMPFAIL
|
||||
@ -93,8 +93,8 @@ class LdapAclMilter(Milter.Base):
|
||||
if 'reason' in kwargs:
|
||||
message = "{0} - reason: {1}".format(message, kwargs['reason'])
|
||||
if kwargs['action'] == 'reject' or kwargs['action'] == 'tmpfail':
|
||||
self.log_info("milter_action={0} message={1}".format(
|
||||
kwargs['action'], message
|
||||
self.log_info("{0} - milter_action={1} message={2}".format(
|
||||
self.mconn_id, kwargs['action'], message
|
||||
))
|
||||
self.setreply(smtp_code, smtp_ecode, message)
|
||||
return smfir
|
||||
@ -119,7 +119,7 @@ class LdapAclMilter(Milter.Base):
|
||||
def envfrom(self, mailfrom, *str):
|
||||
self.reset()
|
||||
self.proto_stage = 'FROM'
|
||||
if g_config.milter_expect_auth:
|
||||
if g_config_backend.milter_expect_auth:
|
||||
try:
|
||||
# this may fail, if no x509 client certificate was used.
|
||||
# postfix only passes this macro to milters if the TLS connection
|
||||
@ -181,24 +181,28 @@ class LdapAclMilter(Milter.Base):
|
||||
to = to.replace(">","")
|
||||
to = to.lower()
|
||||
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')
|
||||
if g_config.milter_dkim_enabled:
|
||||
if g_config_backend.milter_dkim_enabled:
|
||||
# Collect all envelope-recipients for later
|
||||
# investigation (EOM). Do not perform any
|
||||
# policy action at this protocol phase.
|
||||
self.env_rcpts.append(to)
|
||||
else:
|
||||
# DKIM disabled. Policy enforcement takes place here.
|
||||
try:
|
||||
ret = g_policy_backend.check_policy(
|
||||
from_addr=self.env_from, rcpt_addr=to, from_source='5321.from', lam_session=self
|
||||
g_policy_backend.check_policy(
|
||||
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:
|
||||
if g_config.milter_mode == 'reject':
|
||||
if g_config_backend.milter_mode == 'reject':
|
||||
return self.milter_action(action = 'tmpfail')
|
||||
except LamHardException as e:
|
||||
if g_config.milter_mode == 'reject':
|
||||
if g_config_backend.milter_mode == 'reject':
|
||||
return self.milter_action(
|
||||
action = 'reject',
|
||||
reason = e.message
|
||||
@ -210,7 +214,7 @@ class LdapAclMilter(Milter.Base):
|
||||
def header(self, hname, hval):
|
||||
self.proto_stage = 'HDR'
|
||||
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
|
||||
if(hname.lower() == "From".lower()):
|
||||
hdr_5322_from = email.utils.parseaddr(hval)
|
||||
@ -232,7 +236,7 @@ class LdapAclMilter(Milter.Base):
|
||||
ar = authres.AuthenticationResultsHeader.parse(
|
||||
"{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:
|
||||
if ar_result.method.lower() == 'dkim':
|
||||
if ar_result.result.lower() == 'pass':
|
||||
@ -249,13 +253,13 @@ class LdapAclMilter(Milter.Base):
|
||||
|
||||
def eom(self):
|
||||
self.proto_stage = 'EOM'
|
||||
if g_config.milter_max_rcpt_enabled:
|
||||
if len(self.env_rcpts) > int(g_config.milter_max_rcpt):
|
||||
if g_config.milter_mode == 'reject':
|
||||
if g_config_backend.milter_max_rcpt_enabled:
|
||||
if len(self.env_rcpts) > int(g_config_backend.milter_max_rcpt):
|
||||
if g_config_backend.milter_mode == 'reject':
|
||||
return self.milter_action(action='reject', reason='Too many recipients!')
|
||||
else:
|
||||
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.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:
|
||||
try:
|
||||
# Check 5321.from against policy
|
||||
ret = g_policy_backend.check_policy(
|
||||
from_addr=self.env_from, rcpt_addr=rcpt, from_source='5321.from', lam_session=self
|
||||
g_policy_backend.check_policy(
|
||||
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:
|
||||
self.log_info(str(e))
|
||||
if g_config.milter_mode == 'reject':
|
||||
self.log_info(e.message)
|
||||
if g_config_backend.milter_mode == 'reject':
|
||||
return self.milter_action(action = 'tmpfail')
|
||||
else:
|
||||
self.log_info("TEST-Mode - tmpfail")
|
||||
except LamHardException as e:
|
||||
self.log_info(e.message)
|
||||
if self.dkim_aligned:
|
||||
try:
|
||||
# Check 5322.from against policy
|
||||
ret = g_policy_backend.check_policy(
|
||||
from_addr=self.hdr_from, rcpt_addr=rcpt, from_source='5322.from', lam_session=self
|
||||
g_policy_backend.check_policy(
|
||||
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:
|
||||
reject_message = True
|
||||
else:
|
||||
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')
|
||||
|
||||
def abort(self):
|
||||
|
||||
30
app/lam_backends.py
Normal file
30
app/lam_backends.py
Normal 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
|
||||
@ -1,23 +1,25 @@
|
||||
import re
|
||||
import os
|
||||
from lam_logger import log_info
|
||||
from lam_exceptions import LamConfigException
|
||||
from lam_exceptions import LamConfigBackendException
|
||||
from lam_rex import g_rex_email
|
||||
|
||||
class LamConfig():
|
||||
class LamConfigBackend():
|
||||
def __init__(self):
|
||||
self.milter_mode = 'test'
|
||||
self.milter_name = 'ldap-acl-milter'
|
||||
self.milter_mode = 'test'
|
||||
self.milter_socket = '/socket/{}'.format(self.milter_name)
|
||||
self.milter_timeout = 60
|
||||
self.milter_reject_message = 'Security policy violation!'
|
||||
self.milter_tmpfail_message = 'Service temporarily not available! Please try again later.'
|
||||
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_bindpw = 'TopSecret;-)'
|
||||
self.ldap_base = 'ou=lam,ou=services,dc=example,dc=org'
|
||||
self.ldap_query = '(&(mail=%rcpt%)(allowedEnvelopeSender=%from%))'
|
||||
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_whitelisted_rcpts = {}
|
||||
self.milter_dkim_enabled = False
|
||||
@ -25,75 +27,127 @@ class LamConfig():
|
||||
self.milter_max_rcpt_enabled = False
|
||||
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 re.match(r'^test|reject$',os.environ['MILTER_MODE'], re.IGNORECASE):
|
||||
self.milter_mode = os.environ['MILTER_MODE'].lower()
|
||||
if 'MILTER_NAME' in os.environ:
|
||||
self.milter_name = os.environ['MILTER_NAME']
|
||||
log_info("ENV[MILTER_MODE]: {}". format(self.milter_mode))
|
||||
|
||||
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 re.match(r'^true$', os.environ['MILTER_SCHEMA'], re.IGNORECASE):
|
||||
self.milter_schema = True
|
||||
if 'MILTER_SCHEMA_WILDCARD_DOMAIN' in os.environ:
|
||||
if re.match(r'^true$', os.environ['MILTER_SCHEMA_WILDCARD_DOMAIN'], re.IGNORECASE):
|
||||
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:
|
||||
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']
|
||||
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:
|
||||
self.ldap_binddn = os.environ['LDAP_BINDDN']
|
||||
if 'LDAP_BINDPW' in os.environ:
|
||||
self.ldap_bindpw = os.environ['LDAP_BINDPW']
|
||||
|
||||
if 'LDAP_BASE' not in os.environ:
|
||||
raise LamConfigException(
|
||||
raise LamConfigBackendException(
|
||||
"Missing ENV[LDAP_BASE], e.g. {}".format(self.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 self.milter_schema == False:
|
||||
raise LamConfigException(
|
||||
raise LamConfigBackendException(
|
||||
"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']
|
||||
if 'MILTER_SOCKET' in os.environ:
|
||||
self.milter_socket = os.environ['MILTER_SOCKET']
|
||||
log_info("ENV[LDAP_QUERY]: {}".format(self.ldap_query))
|
||||
|
||||
if 'MILTER_REJECT_MESSAGE' in os.environ:
|
||||
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:
|
||||
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 re.match(r'^true$', os.environ['MILTER_EXPECT_AUTH'], re.IGNORECASE):
|
||||
self.milter_expect_auth = True
|
||||
log_info("ENV[MILTER_EXPECT_AUTH]: {}".format(self.milter_expect_auth))
|
||||
|
||||
if 'MILTER_WHITELISTED_RCPTS' in os.environ:
|
||||
# A blank separated list is expected
|
||||
whitelisted_rcpts_str = os.environ['MILTER_WHITELISTED_RCPTS']
|
||||
for whitelisted_rcpt in re.split(',|\s', whitelisted_rcpts_str):
|
||||
if g_rex_email.match(whitelisted_rcpt) == None:
|
||||
raise LamConfigException(
|
||||
raise LamConfigBackendException(
|
||||
"ENV[MILTER_WHITELISTED_RCPTS]: invalid email address: {}"
|
||||
.format(whitelisted_rcpt)
|
||||
)
|
||||
else:
|
||||
log_info("ENV[MILTER_WHITELISTED_RCPTS]: {}".format(
|
||||
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:
|
||||
self.milter_dkim_enabled = True
|
||||
if 'MILTER_TRUSTED_AUTHSERVID' in os.environ:
|
||||
self.milter_trusted_authservid = os.environ['MILTER_TRUSTED_AUTHSERVID'].lower()
|
||||
log_info("ENV[MILTER_TRUSTED_AUTHSERVID]: {0}".format(
|
||||
self.milter_trusted_authservid
|
||||
))
|
||||
log_info(
|
||||
"ENV[MILTER_TRUSTED_AUTHSERVID]: {}".format(
|
||||
self.milter_trusted_authservid
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise LamConfigException("ENV[MILTER_TRUSTED_AUTHSERVID] is mandatory!")
|
||||
log_info("ENV[MILTER_DKIM_ENABLED]: {0}".format(self.milter_dkim_enabled))
|
||||
raise LamConfigBackendException("ENV[MILTER_TRUSTED_AUTHSERVID] is mandatory!")
|
||||
log_info("ENV[MILTER_DKIM_ENABLED]: {}".format(self.milter_dkim_enabled))
|
||||
|
||||
if 'MILTER_MAX_RCPT_ENABLED' in os.environ:
|
||||
self.milter_max_rcpt_enabled = True
|
||||
if 'MILTER_MAX_RCPT' in os.environ:
|
||||
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:
|
||||
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))
|
||||
|
||||
|
||||
@ -16,5 +16,5 @@ class LamHardException(LamException):
|
||||
class LamPolicyBackendException(LamException):
|
||||
pass
|
||||
|
||||
class LamConfigException(LamException):
|
||||
class LamConfigBackendException(LamException):
|
||||
pass
|
||||
@ -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
|
||||
@ -17,8 +17,8 @@ class LamPolicyBackend():
|
||||
set_config_parameter("RESTARTABLE_SLEEPTIME", 2)
|
||||
set_config_parameter("RESTARTABLE_TRIES", 2)
|
||||
server = Server(
|
||||
self.config.ldap_server,
|
||||
connect_timeout = 3,
|
||||
host = self.config.ldap_server,
|
||||
connect_timeout = self.config.ldap_server_connect_timeout,
|
||||
get_info = NONE
|
||||
)
|
||||
self.ldap_conn = Connection(server,
|
||||
@ -34,19 +34,19 @@ class LamPolicyBackend():
|
||||
"Connection to LDAP-server failed: {}".format(str(e))
|
||||
) from e
|
||||
|
||||
def check_policy(self, **kwargs) -> str:
|
||||
def check_policy(self, **kwargs):
|
||||
from_addr = kwargs['from_addr']
|
||||
rcpt_addr = kwargs['rcpt_addr']
|
||||
from_source = kwargs['from_source']
|
||||
lam_session = kwargs['lam_session']
|
||||
mcid = "{}/Policy".format(lam_session.mconn_id)
|
||||
m = g_rex_domain.match(from_addr)
|
||||
if m == None:
|
||||
log_info("Could not determine domain of from={0}".format(
|
||||
from_addr
|
||||
))
|
||||
raise LamSoftException()
|
||||
raise LamHardException(
|
||||
"Could not determine domain of from={0}".format(from_addr)
|
||||
)
|
||||
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)
|
||||
if m == None:
|
||||
raise LamHardException(
|
||||
@ -55,7 +55,7 @@ class LamPolicyBackend():
|
||||
)
|
||||
)
|
||||
rcpt_domain = m.group(1)
|
||||
log_debug("rcpt_domain={}".format(rcpt_domain))
|
||||
log_debug("{0} rcpt_domain={1}".format(mcid, rcpt_domain))
|
||||
try:
|
||||
if self.config.milter_schema == True:
|
||||
# LDAP-ACL-Milter schema
|
||||
@ -77,7 +77,7 @@ class LamPolicyBackend():
|
||||
)
|
||||
else:
|
||||
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:
|
||||
# The asterisk (*) character is in term of local part
|
||||
# RFC5322 compliant and expected as a wildcard literal in this code.
|
||||
@ -126,26 +126,28 @@ class LamPolicyBackend():
|
||||
)
|
||||
if len(self.ldap_conn.entries) == 0:
|
||||
# Policy not found in LDAP
|
||||
ex = "policy mismatch: from={0} from_src={1} rcpt={2}".format(
|
||||
from_addr, from_source, 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(
|
||||
"mismatch: from_src={0} from={1} rcpt={2}".format(
|
||||
from_source, from_addr, rcpt_addr
|
||||
)
|
||||
raise LamHardException(ex)
|
||||
)
|
||||
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?
|
||||
entry = self.ldap_conn.entries[0]
|
||||
return "policy match: '{0}' from_src={1}".format(
|
||||
entry.policyID.value, from_source
|
||||
)
|
||||
log_info("{0} match: '{1}' from_src={2}".format(
|
||||
mcid, entry.policyID.value, from_source
|
||||
))
|
||||
elif len(self.ldap_conn.entries) > 1:
|
||||
# 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(
|
||||
from_addr, rcpt_addr, auth_method
|
||||
))
|
||||
raise LamHardException("More than one policies found!")
|
||||
raise LamHardException(
|
||||
"More than one policies found! from={0} rcpt={1} auth_method={2}".format(
|
||||
from_addr, rcpt_addr, auth_method
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Custom LDAP schema
|
||||
# 'build' a LDAP query per recipient
|
||||
@ -156,18 +158,14 @@ class LamPolicyBackend():
|
||||
query = query.replace("%sasl_user%", lam_session.sasl_user)
|
||||
query = query.replace("%from_domain%", from_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)
|
||||
if len(self.ldap_conn.entries) == 0:
|
||||
log_info(
|
||||
"policy mismatch from={0} from_src={1} rcpt={2}".format(
|
||||
from_addr, from_source, rcpt_addr
|
||||
raise LamHardException(
|
||||
"mismatch from_src={0} from={1} rcpt={2}".format(
|
||||
from_source, from_addr, rcpt_addr
|
||||
)
|
||||
)
|
||||
raise LamHardException("policy mismatch")
|
||||
return "policy match: '{0}' from_src={1}".format(
|
||||
entry.policyID.value, from_source
|
||||
)
|
||||
log_info("{0} match from_src={1}".format(mcid, from_source))
|
||||
except LDAPException as e:
|
||||
log_error("LDAP exception: {}".format(str(e)))
|
||||
raise LamSoftException("LDAP exception: " + str(e)) from e
|
||||
|
||||
@ -4,7 +4,7 @@ import traceback
|
||||
from lam_exceptions import LamInitException
|
||||
from lam_logger import log_info, log_error
|
||||
try:
|
||||
import lam_globals
|
||||
import lam_backends
|
||||
except LamInitException as e:
|
||||
log_error("Init exception: {}".format(e.message))
|
||||
sys.exit(1)
|
||||
@ -12,24 +12,22 @@ from lam import LdapAclMilter
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
timeout = 600
|
||||
timeout = lam_backends.g_config_backend.milter_timeout
|
||||
# Register to have the Milter factory create instances of your class:
|
||||
Milter.factory = LdapAclMilter
|
||||
# Tell the MTA which features we use
|
||||
flags = Milter.ADDHDRS
|
||||
Milter.set_flags(flags)
|
||||
log_info("Starting {0}@socket: {1} in mode {2}".format(
|
||||
lam_globals.g_config.milter_name,
|
||||
lam_globals.g_config.milter_socket,
|
||||
lam_globals.g_config.milter_mode
|
||||
log_info("Starting {}".format(
|
||||
lam_backends.g_config_backend.milter_name
|
||||
))
|
||||
Milter.runmilter(
|
||||
lam_globals.g_config.milter_name,
|
||||
lam_globals.g_config.milter_socket,
|
||||
lam_backends.g_config_backend.milter_name,
|
||||
lam_backends.g_config_backend.milter_socket,
|
||||
timeout,
|
||||
True
|
||||
)
|
||||
log_info("Shutdown {}".format(lam_globals.g_config.milter_name))
|
||||
log_info("Shutdown {}".format(lam_backends.g_config_backend.milter_name))
|
||||
except:
|
||||
log_error("MAIN-EXCEPTION: {}".format(traceback.format_exc()))
|
||||
sys.exit(1)
|
||||
|
||||
@ -16,18 +16,12 @@ mt.set_timeout(60)
|
||||
if mt.mailfrom(conn, "tester-ip@test.blah") ~= nil then
|
||||
error "mt.mailfrom() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.mailfrom() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5321.RCPT+MACROS
|
||||
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ")
|
||||
if mt.rcptto(conn, "<rcpt-ip@test.blubb>") ~= nil then
|
||||
error "mt.rcptto() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.rcptto() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5322.HEADERS
|
||||
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
|
||||
error "mt.mailfrom() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.mailfrom() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5321.RCPT+MACROS
|
||||
mt.macro(conn, SMFIC_RCPT, "i", "conn-reused-QID")
|
||||
if mt.rcptto(conn, "<rcpt-ip2@test.blubb>") ~= nil then
|
||||
error "mt.rcptto() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.rcptto() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5322.HEADERS
|
||||
if mt.header(conn, "fRoM", '"Blah Blubb" <tester-ip2@test.blah>') ~= nil then
|
||||
|
||||
@ -16,9 +16,6 @@ mt.set_timeout(60)
|
||||
if mt.mailfrom(conn, "tester-ipx@test.blah") ~= nil then
|
||||
error "mt.mailfrom() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.mailfrom() unexpected reply"
|
||||
end
|
||||
|
||||
-- FIRST 5321.RCPT
|
||||
if mt.rcptto(conn, "<rcpt-ip@test.blubb>") ~= nil then
|
||||
|
||||
@ -16,19 +16,12 @@ mt.set_timeout(60)
|
||||
if mt.mailfrom(conn, "tester-noauth@test.blah") ~= nil then
|
||||
error "mt.mailfrom() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.mailfrom() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5321.RCPT+MACROS
|
||||
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ")
|
||||
if mt.rcptto(conn, "<rcpt-noauth@test.blubb>") ~= nil then
|
||||
-- if mt.rcptto(conn, "<rcpt-noauth@>") ~= nil then
|
||||
error "mt.rcptto() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.rcptto() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5322.HEADERS
|
||||
if mt.header(conn, "fRoM", '"Blah Blubb" <tester-noauth@test.blah>') ~= nil then
|
||||
|
||||
0
tests/miltertest-null_sender.lua
Normal file
0
tests/miltertest-null_sender.lua
Normal 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
|
||||
error "mt.mailfrom() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.mailfrom() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5321.RCPT+MACROS
|
||||
mt.macro(conn, SMFIC_RCPT, "i", "test-wildcard-qid")
|
||||
if mt.rcptto(conn, "<anybody-xyz@out.there>") ~= nil then
|
||||
error "mt.rcptto() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.rcptto() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5322.HEADERS
|
||||
if mt.header(conn, "fRoM", '"Blah Blubb" <tester@test.blah>') ~= nil then
|
||||
|
||||
@ -17,18 +17,12 @@ mt.macro(conn, SMFIC_MAIL, "{auth_authen}", "blubb-user1")
|
||||
if mt.mailfrom(conn, "tester@test.blah") ~= nil then
|
||||
error "mt.mailfrom() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.mailfrom() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5321.RCPT+MACROS
|
||||
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ")
|
||||
if mt.rcptto(conn, "<rcpt@test.blubb>") ~= nil then
|
||||
error "mt.rcptto() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.rcptto() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5322.HEADERS
|
||||
if mt.header(conn, "fRoM", '"Blah Blubb" <tester@test.blah>') ~= nil then
|
||||
|
||||
0
tests/miltertest-whitelisted_sender.lua
Normal file
0
tests/miltertest-whitelisted_sender.lua
Normal 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
|
||||
error "mt.mailfrom() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.mailfrom() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5321.RCPT+MACROS
|
||||
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ")
|
||||
if mt.rcptto(conn, "<rcpt-x509@test.blubb>") ~= nil then
|
||||
error "mt.rcptto() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.rcptto() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5322.HEADERS
|
||||
if mt.header(conn, "fRoM", '"Blah Blubb" <tester@test.blah>') ~= nil then
|
||||
|
||||
@ -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
|
||||
error "mt.mailfrom() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.mailfrom() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5321.RCPT+MACROS
|
||||
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ")
|
||||
if mt.rcptto(conn, "<rcpt-x509@test.blubb>") ~= nil then
|
||||
error "mt.rcptto() failed"
|
||||
end
|
||||
if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
||||
error "mt.rcptto() unexpected reply"
|
||||
end
|
||||
|
||||
-- 5322.HEADERS
|
||||
if mt.header(conn, "fRoM", '"Blah Blubb" <tester-x509@test.blah>') ~= nil then
|
||||
|
||||
Loading…
Reference in New Issue
Block a user