mirror of
https://github.com/chillout2k/ldap-acl-milter.git
synced 2025-12-11 02:30:17 +00:00
Compare commits
3 Commits
f6796d06ec
...
a516ded2c8
| Author | SHA1 | Date | |
|---|---|---|---|
| a516ded2c8 | |||
| 539b65e6f8 | |||
| 2d89583a07 |
@ -1,4 +1,3 @@
|
|||||||
from argparse import Action
|
|
||||||
import Milter
|
import Milter
|
||||||
from ldap3 import (
|
from ldap3 import (
|
||||||
Server, Connection, NONE, set_config_parameter
|
Server, Connection, NONE, set_config_parameter
|
||||||
@ -11,7 +10,6 @@ import logging
|
|||||||
import string
|
import string
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
from timeit import default_timer as timer
|
|
||||||
import email.utils
|
import email.utils
|
||||||
import authres
|
import authres
|
||||||
|
|
||||||
@ -29,9 +27,7 @@ g_ldap_query = '(&(mail=%rcpt%)(allowedEnvelopeSender=%from%))'
|
|||||||
g_re_domain = re.compile(r'^\S*@(\S+)$')
|
g_re_domain = re.compile(r'^\S*@(\S+)$')
|
||||||
# http://emailregex.com/ -> Python
|
# http://emailregex.com/ -> Python
|
||||||
g_re_email = re.compile(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)")
|
g_re_email = re.compile(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)")
|
||||||
g_loglevel = logging.INFO
|
|
||||||
g_milter_mode = 'test'
|
g_milter_mode = 'test'
|
||||||
g_milter_default_policy = 'reject'
|
|
||||||
g_milter_schema = False
|
g_milter_schema = False
|
||||||
g_milter_schema_wildcard_domain = False # works only if g_milter_schema == True
|
g_milter_schema_wildcard_domain = False # works only if g_milter_schema == True
|
||||||
g_milter_expect_auth = False
|
g_milter_expect_auth = False
|
||||||
@ -39,10 +35,14 @@ g_milter_whitelisted_rcpts = {}
|
|||||||
g_milter_dkim_enabled = False
|
g_milter_dkim_enabled = False
|
||||||
g_milter_trusted_authservid = None
|
g_milter_trusted_authservid = None
|
||||||
g_re_srs = re.compile(r"^SRS0=.+=.+=(\S+)=(\S+)\@.+$")
|
g_re_srs = re.compile(r"^SRS0=.+=.+=(\S+)=(\S+)\@.+$")
|
||||||
|
g_milter_max_rcpt_enabled = False
|
||||||
|
g_milter_max_rcpt = 1
|
||||||
|
|
||||||
class LamException(Exception):
|
class LamException(Exception):
|
||||||
def __init__(self, message="General exception message"):
|
def __init__(self, message="General exception message"):
|
||||||
self.message = message
|
self.message = message
|
||||||
|
def __str__(self):
|
||||||
|
return self.message
|
||||||
|
|
||||||
class LamSoftException(LamException):
|
class LamSoftException(LamException):
|
||||||
pass
|
pass
|
||||||
@ -56,8 +56,43 @@ class LdapAclMilter(Milter.Base):
|
|||||||
# client_addr gets overriden on any connect()
|
# client_addr gets overriden on any connect()
|
||||||
self.client_addr = None
|
self.client_addr = None
|
||||||
|
|
||||||
|
def do_log(self, **kwargs):
|
||||||
|
if 'level' not in kwargs:
|
||||||
|
print("do_log(): 'level' arg missing!")
|
||||||
|
sys.exit(1)
|
||||||
|
if 'log_message' not in kwargs:
|
||||||
|
print("do_log(): 'log_message' arg missing!")
|
||||||
|
sys.exit(1)
|
||||||
|
log_line = ''
|
||||||
|
if hasattr(self, 'mconn_id'):
|
||||||
|
log_line = "{}".format(self.mconn_id)
|
||||||
|
if self.queue_id != 'invalid':
|
||||||
|
log_line = "{0}/{1}".format(log_line, self.queue_id)
|
||||||
|
if self.proto_stage != 'invalid':
|
||||||
|
log_line = "{0}/{1}".format(log_line, self.proto_stage)
|
||||||
|
log_line = "{0} {1}".format(log_line, kwargs['log_message'])
|
||||||
|
if kwargs['level'] == 'error':
|
||||||
|
logging.error(log_line)
|
||||||
|
elif kwargs['level'] == 'warn' or kwargs['level'] == 'warning':
|
||||||
|
logging.warning(log_line)
|
||||||
|
elif kwargs['level'] == 'info':
|
||||||
|
logging.info(log_line)
|
||||||
|
elif kwargs['level'] == 'debug':
|
||||||
|
logging.debug(log_line)
|
||||||
|
else:
|
||||||
|
print("do_log(): invalid 'level' {}".format(kwargs['level']))
|
||||||
|
sys.exit(1)
|
||||||
|
def log_error(self, log_message):
|
||||||
|
self.do_log(level='error', log_message=log_message)
|
||||||
|
def log_warn(self, log_message):
|
||||||
|
self.do_log(level='warn', log_message=log_message)
|
||||||
|
def log_info(self, log_message):
|
||||||
|
self.do_log(level='info', log_message=log_message)
|
||||||
|
def log_debug(self, log_message):
|
||||||
|
self.do_log(level='debug', log_message=log_message)
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.proto_stage = 'proto-stage'
|
self.proto_stage = 'invalid'
|
||||||
self.env_from = None
|
self.env_from = None
|
||||||
self.sasl_user = None
|
self.sasl_user = None
|
||||||
self.x509_subject = None
|
self.x509_subject = None
|
||||||
@ -69,7 +104,7 @@ class LdapAclMilter(Milter.Base):
|
|||||||
self.dkim_valid = False
|
self.dkim_valid = False
|
||||||
self.dkim_aligned = False
|
self.dkim_aligned = False
|
||||||
self.passed_dkim_results = []
|
self.passed_dkim_results = []
|
||||||
logging.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_milter_name + ': ' + ''.join(
|
self.mconn_id = g_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)
|
||||||
@ -107,37 +142,33 @@ 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(
|
||||||
|
kwargs['action'], message
|
||||||
|
))
|
||||||
self.setreply(smtp_code, smtp_ecode, message)
|
self.setreply(smtp_code, smtp_ecode, message)
|
||||||
logging.info(self.mconn_id + "/" +
|
|
||||||
self.proto_stage + ": milter_action={0} message={1}".format(kwargs['action'], message)
|
|
||||||
)
|
|
||||||
return smfir
|
return smfir
|
||||||
|
|
||||||
def check_policy(self, from_addr, rcpt_addr):
|
def check_policy(self, **kwargs):
|
||||||
logging.info(self.mconn_id +
|
from_addr = kwargs['from_addr']
|
||||||
"/{0} from={1} rcpt={2}".format(
|
rcpt_addr = kwargs['rcpt_addr']
|
||||||
self.proto_stage, from_addr, rcpt_addr
|
from_source = kwargs['from_source']
|
||||||
)
|
|
||||||
)
|
|
||||||
m = g_re_domain.match(from_addr)
|
m = g_re_domain.match(from_addr)
|
||||||
if m == None:
|
if m == None:
|
||||||
logging.info(self.mconn_id +
|
self.log_info("Could not determine domain of from={0}".format(
|
||||||
"/{0} Could not determine domain of from={1}".format(
|
from_addr
|
||||||
self.proto_stage, from_addr
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
raise LamSoftException()
|
raise LamSoftException()
|
||||||
from_domain = m.group(1)
|
from_domain = m.group(1)
|
||||||
logging.debug(self.mconn_id +
|
self.log_debug("from_domain={}".format(from_domain))
|
||||||
"/{0} from_domain={1}".format(self.queue_id, from_domain)
|
|
||||||
)
|
|
||||||
m = g_re_domain.match(rcpt_addr)
|
m = g_re_domain.match(rcpt_addr)
|
||||||
if m == None:
|
if m == None:
|
||||||
raise LamSoftException("Could not determine domain of rcpt={}".format(rcpt_addr))
|
raise LamHardException(
|
||||||
|
"Could not determine domain of rcpt={0}".format(
|
||||||
|
rcpt_addr
|
||||||
|
)
|
||||||
|
)
|
||||||
rcpt_domain = m.group(1)
|
rcpt_domain = m.group(1)
|
||||||
logging.debug(self.mconn_id +
|
self.log_debug("rcpt_domain={}".format(rcpt_domain))
|
||||||
"/{0} rcpt_domain={1}".format(self.queue_id, rcpt_domain)
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
if g_milter_schema == True:
|
if g_milter_schema == True:
|
||||||
# LDAP-ACL-Milter schema
|
# LDAP-ACL-Milter schema
|
||||||
@ -159,9 +190,7 @@ class LdapAclMilter(Milter.Base):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
auth_method = auth_method.replace('%X509_AUTH%','')
|
auth_method = auth_method.replace('%X509_AUTH%','')
|
||||||
logging.debug(self.mconn_id +
|
self.log_debug("auth_method: {}".format(auth_method))
|
||||||
" auth_method: " + auth_method
|
|
||||||
)
|
|
||||||
if g_milter_schema_wildcard_domain == True:
|
if g_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.
|
||||||
@ -211,32 +240,29 @@ class LdapAclMilter(Milter.Base):
|
|||||||
if len(g_ldap_conn.entries) == 0:
|
if len(g_ldap_conn.entries) == 0:
|
||||||
# Policy not found in LDAP
|
# Policy not found in LDAP
|
||||||
if g_milter_expect_auth == True:
|
if g_milter_expect_auth == True:
|
||||||
logging.info(self.mconn_id + " " + "policy mismatch "
|
self.log_info(
|
||||||
"from=" + from_addr + ", rcpt=" + rcpt_addr +
|
"policy mismatch: from={0} from_src={1} rcpt={2} auth_method={3}".format(
|
||||||
", auth_method=" + auth_method
|
from_addr, from_source, rcpt_addr, auth_method
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logging.info(self.mconn_id + " " + "policy mismatch "
|
self.log_info(
|
||||||
"from=" + from_addr + ", rcpt=" + rcpt_addr
|
"policy mismatch: from={0} from_src={1} rcpt={2}".format(
|
||||||
)
|
from_addr, from_source, rcpt_addr
|
||||||
if g_milter_mode == 'reject':
|
)
|
||||||
raise LamHardException("policy not found!")
|
|
||||||
else:
|
|
||||||
logging.info(self.mconn_id + " TEST_MODE " +
|
|
||||||
g_milter_reject_message
|
|
||||||
)
|
)
|
||||||
|
raise LamHardException("policy mismatch!")
|
||||||
elif len(g_ldap_conn.entries) == 1:
|
elif len(g_ldap_conn.entries) == 1:
|
||||||
# Policy found in LDAP, but which one?
|
# Policy found in LDAP, but which one?
|
||||||
entry = g_ldap_conn.entries[0]
|
entry = g_ldap_conn.entries[0]
|
||||||
logging.info(self.mconn_id +
|
self.log_info("policy match: '{0}' from_src={1}".format(
|
||||||
"/{0} Policy match: {1}".format(self.proto_stage, entry.policyID.value)
|
entry.policyID.value, from_source
|
||||||
)
|
))
|
||||||
elif len(g_ldap_conn.entries) > 1:
|
elif len(g_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!
|
||||||
logging.warning(self.mconn_id + " More than one policies found! "+
|
self.log_warn("More than one policies found! from={0} rcpt={1} auth_method={2}".format(
|
||||||
"from=" + from_addr + ", rcpt=" + rcpt_addr +
|
from_addr, rcpt_addr, auth_method
|
||||||
", auth_method=" + auth_method
|
))
|
||||||
)
|
|
||||||
raise LamHardException("More than one policies found!")
|
raise LamHardException("More than one policies found!")
|
||||||
else:
|
else:
|
||||||
# Custom LDAP schema
|
# Custom LDAP schema
|
||||||
@ -248,21 +274,21 @@ class LdapAclMilter(Milter.Base):
|
|||||||
query = query.replace("%sasl_user%", self.sasl_user)
|
query = query.replace("%sasl_user%", self.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)
|
||||||
logging.debug(self.mconn_id + " " + query)
|
self.log_debug("LDAP query: {}".format(query))
|
||||||
g_ldap_conn.search(g_ldap_base, query)
|
g_ldap_conn.search(g_ldap_base, query)
|
||||||
if len(g_ldap_conn.entries) == 0:
|
if len(g_ldap_conn.entries) == 0:
|
||||||
logging.info(self.mconn_id + " " + "policy mismatch "
|
self.log_info(
|
||||||
"from: " + from_addr + " and rcpt: " + rcpt_addr
|
"policy mismatch from={0} from_src={1} rcpt={2}".format(
|
||||||
)
|
from_addr, from_source, rcpt_addr
|
||||||
if g_milter_mode == 'reject':
|
|
||||||
raise LamHardException("policy mismatch")
|
|
||||||
else:
|
|
||||||
logging.info(self.mconn_id + " TEST_MODE " +
|
|
||||||
g_milter_reject_message
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
raise LamHardException("policy mismatch")
|
||||||
|
self.log_info("policy match: '{0}' from_src={1}".format(
|
||||||
|
entry.policyID.value, from_source
|
||||||
|
))
|
||||||
except LDAPException as e:
|
except LDAPException as e:
|
||||||
logging.error(self.mconn_id + " LDAP: " + str(e))
|
self.log_error("LDAP exception: {}".format(str(e)))
|
||||||
raise LamSoftException(" LDAP: " + str(e)) from e;
|
raise LamSoftException("LDAP exception: " + str(e)) from e;
|
||||||
return self.milter_action(action = 'continue')
|
return self.milter_action(action = 'continue')
|
||||||
|
|
||||||
# Not registered/used callbacks
|
# Not registered/used callbacks
|
||||||
@ -277,8 +303,8 @@ class LdapAclMilter(Milter.Base):
|
|||||||
self.reset()
|
self.reset()
|
||||||
self.proto_stage = 'CONNECT'
|
self.proto_stage = 'CONNECT'
|
||||||
self.client_addr = hostaddr[0]
|
self.client_addr = hostaddr[0]
|
||||||
logging.debug(self.mconn_id +
|
self.log_debug("client_addr={0}, client_port={1}".format(
|
||||||
"/CONNECT client_addr=[" + self.client_addr + "]:" + str(hostaddr[1])
|
self.client_addr, hostaddr[1])
|
||||||
)
|
)
|
||||||
return self.milter_action(action = 'continue')
|
return self.milter_action(action = 'continue')
|
||||||
|
|
||||||
@ -295,29 +321,29 @@ class LdapAclMilter(Milter.Base):
|
|||||||
x509_subject = self.getsymval('{cert_subject}')
|
x509_subject = self.getsymval('{cert_subject}')
|
||||||
if x509_subject != None:
|
if x509_subject != None:
|
||||||
self.x509_subject = x509_subject
|
self.x509_subject = x509_subject
|
||||||
logging.debug(self.mconn_id + "/FROM x509_subject=" + self.x509_subject)
|
self.log_debug("x509_subject={}".format(self.x509_subject))
|
||||||
else:
|
else:
|
||||||
logging.debug(self.mconn_id + "/FROM No x509_subject registered")
|
self.log_debug("No x509_subject registered")
|
||||||
x509_issuer = self.getsymval('{cert_issuer}')
|
x509_issuer = self.getsymval('{cert_issuer}')
|
||||||
if x509_issuer != None:
|
if x509_issuer != None:
|
||||||
self.x509_issuer = x509_issuer
|
self.x509_issuer = x509_issuer
|
||||||
logging.debug(self.mconn_id + "/FROM x509_issuer=" + self.x509_issuer)
|
self.log_debug("x509_issuer={}".format(self.x509_issuer))
|
||||||
else:
|
else:
|
||||||
logging.debug(self.mconn_id + "/FROM No x509_issuer registered")
|
self.log_debug("No x509_issuer registered")
|
||||||
except:
|
except:
|
||||||
logging.error(self.mconn_id + "/FROM x509 " + traceback.format_exc())
|
self.log_error("x509 exception: {}".format(traceback.format_exc()))
|
||||||
try:
|
try:
|
||||||
# this may fail, if no SASL authentication preceded
|
# this may fail, if no SASL authentication preceded
|
||||||
sasl_user = self.getsymval('{auth_authen}')
|
sasl_user = self.getsymval('{auth_authen}')
|
||||||
if sasl_user != None:
|
if sasl_user != None:
|
||||||
self.sasl_user = sasl_user
|
self.sasl_user = sasl_user
|
||||||
logging.debug(self.mconn_id + "/FROM sasl_user=" + self.sasl_user)
|
self.log_debug("sasl_user={}".format(self.sasl_user))
|
||||||
else:
|
else:
|
||||||
logging.debug(self.mconn_id + "/FROM No sasl_user registered")
|
self.log_debug("No sasl_user registered")
|
||||||
except:
|
except:
|
||||||
logging.error(self.mconn_id + "/FROM sasl_user " + traceback.format_exc())
|
self.log_error("sasl_user exception: {}".format(traceback.format_exc()))
|
||||||
logging.info(self.mconn_id + "/FROM auth: " +
|
self.log_info(
|
||||||
"client_ip={0}, x509_subject={1}, x509_issuer={2}, sasl_user={3}".format(
|
"auth: client_ip={0} x509_subject={1} x509_issuer={2} sasl_user={3}".format(
|
||||||
self.client_addr, self.x509_subject, self.x509_issuer, self.sasl_user
|
self.client_addr, self.x509_subject, self.x509_issuer, self.sasl_user
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -329,20 +355,16 @@ class LdapAclMilter(Milter.Base):
|
|||||||
# SRS (https://www.libsrs2.org/srs/srs.pdf)
|
# SRS (https://www.libsrs2.org/srs/srs.pdf)
|
||||||
m_srs = g_re_srs.match(mailfrom)
|
m_srs = g_re_srs.match(mailfrom)
|
||||||
if m_srs != None:
|
if m_srs != None:
|
||||||
logging.info(self.mconn_id + "/FROM " +
|
self.log_info("Found SRS-encoded envelope-sender: {}".format(mailfrom))
|
||||||
"Found SRS-encoded envelope-sender: " + mailfrom
|
|
||||||
)
|
|
||||||
mailfrom = m_srs.group(2) + '@' + m_srs.group(1)
|
mailfrom = m_srs.group(2) + '@' + m_srs.group(1)
|
||||||
logging.info(self.mconn_id + "/FROM " +
|
self.log_info("SRS envelope-sender replaced with: {}".format(mailfrom))
|
||||||
"SRS envelope-sender replaced with: " + mailfrom
|
|
||||||
)
|
|
||||||
self.env_from = mailfrom.lower()
|
self.env_from = mailfrom.lower()
|
||||||
logging.debug(self.mconn_id + "/FROM 5321.from={}".format(self.env_from))
|
self.log_debug("5321.from={}".format(self.env_from))
|
||||||
m = g_re_domain.match(self.env_from)
|
m = g_re_domain.match(self.env_from)
|
||||||
if m == None:
|
if m == None:
|
||||||
return self.milter_action(
|
return self.milter_action(
|
||||||
action = 'tmpfail',
|
action = 'reject',
|
||||||
reason = "Could not determine domain of 5321.from=" + self.env_from
|
reason = "Could not determine domain of 5321.from={}".format(self.env_from)
|
||||||
)
|
)
|
||||||
return self.milter_action(action = 'continue')
|
return self.milter_action(action = 'continue')
|
||||||
|
|
||||||
@ -351,9 +373,7 @@ class LdapAclMilter(Milter.Base):
|
|||||||
to = to.replace("<","")
|
to = to.replace("<","")
|
||||||
to = to.replace(">","")
|
to = to.replace(">","")
|
||||||
to = to.lower()
|
to = to.lower()
|
||||||
logging.debug(self.mconn_id +
|
self.log_debug("5321.rcpt={}".format(to))
|
||||||
"/RCPT env_rcpt={}".format(to)
|
|
||||||
)
|
|
||||||
if to in g_milter_whitelisted_rcpts:
|
if to in g_milter_whitelisted_rcpts:
|
||||||
return self.milter_action(action = 'continue')
|
return self.milter_action(action = 'continue')
|
||||||
if g_milter_dkim_enabled:
|
if g_milter_dkim_enabled:
|
||||||
@ -363,14 +383,20 @@ class LdapAclMilter(Milter.Base):
|
|||||||
self.env_rcpts.append(to)
|
self.env_rcpts.append(to)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
return self.check_policy(self.env_from, to)
|
return self.check_policy(
|
||||||
except LamSoftException as e:
|
from_addr=self.env_from, rcpt_addr=to, from_source='5321.from'
|
||||||
return self.milter_action(action = 'tmpfail')
|
|
||||||
except LamHardException as e:
|
|
||||||
return self.milter_action(
|
|
||||||
action = 'reject',
|
|
||||||
reason = e.message
|
|
||||||
)
|
)
|
||||||
|
except LamSoftException as e:
|
||||||
|
if g_milter_mode == 'reject':
|
||||||
|
return self.milter_action(action = 'tmpfail')
|
||||||
|
except LamHardException as e:
|
||||||
|
if g_milter_mode == 'reject':
|
||||||
|
return self.milter_action(
|
||||||
|
action = 'reject',
|
||||||
|
reason = e.message
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.log_info("TEST-Mode: {}".format(e.message))
|
||||||
return self.milter_action(action = 'continue')
|
return self.milter_action(action = 'continue')
|
||||||
|
|
||||||
def header(self, hname, hval):
|
def header(self, hname, hval):
|
||||||
@ -388,11 +414,9 @@ class LdapAclMilter(Milter.Base):
|
|||||||
reason = "Could not determine domain-part of 5322.from=" + self.hdr_from
|
reason = "Could not determine domain-part of 5322.from=" + self.hdr_from
|
||||||
)
|
)
|
||||||
self.hdr_from_domain = m.group(1)
|
self.hdr_from_domain = m.group(1)
|
||||||
logging.info(self.mconn_id + "/" + str(self.queue_id) +
|
self.log_debug("5322.from={0}, 5322.from_domain={1}".format(
|
||||||
"/HDR: 5322.from={0}, 5322.from_domain={1}".format(
|
self.hdr_from, self.hdr_from_domain
|
||||||
self.hdr_from, self.hdr_from_domain
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
# Parse RFC-7601 Authentication-Results header
|
# Parse RFC-7601 Authentication-Results header
|
||||||
elif(hname.lower() == "Authentication-Results".lower()):
|
elif(hname.lower() == "Authentication-Results".lower()):
|
||||||
ar = None
|
ar = None
|
||||||
@ -405,61 +429,74 @@ class LdapAclMilter(Milter.Base):
|
|||||||
if ar_result.method.lower() == 'dkim':
|
if ar_result.method.lower() == 'dkim':
|
||||||
if ar_result.result.lower() == 'pass':
|
if ar_result.result.lower() == 'pass':
|
||||||
self.passed_dkim_results.append(ar_result.header_d.lower())
|
self.passed_dkim_results.append(ar_result.header_d.lower())
|
||||||
logging.debug(self.mconn_id + "/" + str(self.queue_id) +
|
self.log_debug("dkim=pass sdid={}".format(ar_result.header_d))
|
||||||
"/HDR: dkim=pass sdid={0}".format(ar_result.header_d)
|
|
||||||
)
|
|
||||||
self.dkim_valid = True
|
self.dkim_valid = True
|
||||||
else:
|
else:
|
||||||
logging.debug(self.mconn_id + "/" + str(self.queue_id) +
|
self.log_debug("Ignoring authentication results of {}".format(
|
||||||
"/HDR: Ignoring authentication results of {0}".format(ar.authserv_id)
|
ar.authserv_id)
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info(self.mconn_id + "/" + str(self.queue_id) +
|
self.log_info("AR-parse exception: {0}".format(str(e)))
|
||||||
"/HDR: AR-parse exception: {0}".format(str(e))
|
|
||||||
)
|
|
||||||
return self.milter_action(action = 'continue')
|
return self.milter_action(action = 'continue')
|
||||||
|
|
||||||
def eom(self):
|
def eom(self):
|
||||||
self.proto_stage = 'EOM'
|
self.proto_stage = 'EOM'
|
||||||
|
if g_milter_max_rcpt_enabled:
|
||||||
|
if len(self.env_rcpts) > int(g_milter_max_rcpt):
|
||||||
|
if g_milter_mode == 'reject':
|
||||||
|
return self.milter_action(action='reject', reason='Too many recipients!')
|
||||||
|
else:
|
||||||
|
self.do_log("TEST-Mode: Too many recipients!")
|
||||||
if g_milter_dkim_enabled:
|
if g_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
|
||||||
|
))
|
||||||
if self.dkim_valid:
|
if self.dkim_valid:
|
||||||
# There is at least one valid DKIM signature!
|
# There is at least one valid DKIM signature!
|
||||||
# Check if one of them is also aligned
|
# Check if one of them is also aligned
|
||||||
for passed_dkim_sdid in self.passed_dkim_results:
|
for passed_dkim_sdid in self.passed_dkim_results:
|
||||||
if self.hdr_from_domain.lower() == passed_dkim_sdid.lower():
|
if self.hdr_from_domain.lower() == passed_dkim_sdid.lower():
|
||||||
self.dkim_aligned = True
|
self.dkim_aligned = True
|
||||||
logging.info(self.mconn_id + "/" + str(self.queue_id) +
|
self.log_info("Found aligned DKIM signature for SDID: {0}".format(
|
||||||
"/EOM: Found aligned DKIM signature for SDID: {0}".format(
|
passed_dkim_sdid
|
||||||
passed_dkim_sdid
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
reject_message = False
|
reject_message = False
|
||||||
for rcpt in self.env_rcpts:
|
for rcpt in self.env_rcpts:
|
||||||
try:
|
try:
|
||||||
# Check 5321.sender against policy
|
# Check 5321.from against policy
|
||||||
self.check_policy(self.env_from, rcpt)
|
self.check_policy(
|
||||||
|
from_addr=self.env_from, rcpt_addr=rcpt, from_source='5321.from'
|
||||||
|
)
|
||||||
except LamSoftException as e:
|
except LamSoftException as e:
|
||||||
return self.milter_action(action = 'tmpfail')
|
if g_milter_mode == 'reject':
|
||||||
|
return self.milter_action(action = 'tmpfail')
|
||||||
|
else:
|
||||||
|
self.log_info("TEST-Mode: {}".format(e.message))
|
||||||
except LamHardException as e:
|
except LamHardException as e:
|
||||||
if self.dkim_aligned:
|
if self.dkim_aligned:
|
||||||
try:
|
try:
|
||||||
# Check 5322.sender against policy
|
# Check 5322.from against policy
|
||||||
self.check_policy(self.hdr_from, rcpt)
|
self.check_policy(
|
||||||
logging.info(self.mconn_id +
|
from_addr=self.hdr_from, rcpt_addr=rcpt, from_source='5322.from'
|
||||||
"/{0}/{1} from={2} authorized by DKIM signature".format(
|
|
||||||
self.queue_id, self.proto_stage, self.hdr_from
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
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 reject_message:
|
||||||
return self.milter_action(
|
if g_milter_mode == 'reject':
|
||||||
action = 'reject',
|
return self.milter_action(
|
||||||
reason = 'EOM - Policy mismatch! All recipients were rejected!'
|
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):
|
||||||
@ -475,30 +512,25 @@ class LdapAclMilter(Milter.Base):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
|
log_level = logging.INFO
|
||||||
if 'LOG_LEVEL' in os.environ:
|
if 'LOG_LEVEL' in os.environ:
|
||||||
if re.match(r'^info$', os.environ['LOG_LEVEL'], re.IGNORECASE):
|
if re.match(r'^info$', os.environ['LOG_LEVEL'], re.IGNORECASE):
|
||||||
g_loglevel = logging.INFO
|
log_level = logging.INFO
|
||||||
elif re.match(r'^warn|warning$', os.environ['LOG_LEVEL'], re.IGNORECASE):
|
elif re.match(r'^warn|warning$', os.environ['LOG_LEVEL'], re.IGNORECASE):
|
||||||
g_loglevel = logging.WARN
|
log_level = logging.WARN
|
||||||
elif re.match(r'^error$', os.environ['LOG_LEVEL'], re.IGNORECASE):
|
elif re.match(r'^error$', os.environ['LOG_LEVEL'], re.IGNORECASE):
|
||||||
g_loglevel = logging.ERROR
|
log_level = logging.ERROR
|
||||||
elif re.match(r'debug', os.environ['LOG_LEVEL'], re.IGNORECASE):
|
elif re.match(r'debug', os.environ['LOG_LEVEL'], re.IGNORECASE):
|
||||||
g_loglevel = logging.DEBUG
|
log_level = logging.DEBUG
|
||||||
|
log_format = '%(asctime)s: %(levelname)s %(message)s '
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
filename=None, # log to stdout
|
filename = None, # log to stdout
|
||||||
format='%(asctime)s: %(levelname)s %(message)s',
|
format = log_format,
|
||||||
level=g_loglevel
|
level = log_level
|
||||||
)
|
)
|
||||||
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):
|
||||||
g_milter_mode = os.environ['MILTER_MODE']
|
g_milter_mode = os.environ['MILTER_MODE'].lower()
|
||||||
if 'MILTER_DEFAULT_POLICY' in os.environ:
|
|
||||||
if re.match(r'^reject|permit$',os.environ['MILTER_DEFAULT_POLICY'], re.IGNORECASE):
|
|
||||||
g_milter_default_policy = str(os.environ['MILTER_DEFAULT_POLICY']).lower()
|
|
||||||
else:
|
|
||||||
logging.warning("MILTER_DEFAULT_POLICY invalid value: " +
|
|
||||||
os.environ['MILTER_DEFAULT_POLICY']
|
|
||||||
)
|
|
||||||
if 'MILTER_NAME' in os.environ:
|
if 'MILTER_NAME' in os.environ:
|
||||||
g_milter_name = os.environ['MILTER_NAME']
|
g_milter_name = os.environ['MILTER_NAME']
|
||||||
if 'MILTER_SCHEMA' in os.environ:
|
if 'MILTER_SCHEMA' in os.environ:
|
||||||
@ -508,7 +540,7 @@ if __name__ == "__main__":
|
|||||||
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):
|
||||||
g_milter_schema_wildcard_domain = True
|
g_milter_schema_wildcard_domain = True
|
||||||
if 'LDAP_SERVER' not in os.environ:
|
if 'LDAP_SERVER' not in os.environ:
|
||||||
logging.error("Missing ENV[LDAP_SERVER], e.g. " + g_ldap_server)
|
logging.error("Missing ENV[LDAP_SERVER], e.g. {}".format(g_ldap_server))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
g_ldap_server = os.environ['LDAP_SERVER']
|
g_ldap_server = os.environ['LDAP_SERVER']
|
||||||
if 'LDAP_BINDDN' in os.environ:
|
if 'LDAP_BINDDN' in os.environ:
|
||||||
@ -516,7 +548,7 @@ if __name__ == "__main__":
|
|||||||
if 'LDAP_BINDPW' in os.environ:
|
if 'LDAP_BINDPW' in os.environ:
|
||||||
g_ldap_bindpw = os.environ['LDAP_BINDPW']
|
g_ldap_bindpw = os.environ['LDAP_BINDPW']
|
||||||
if 'LDAP_BASE' not in os.environ:
|
if 'LDAP_BASE' not in os.environ:
|
||||||
logging.error("Missing ENV[LDAP_BASE], e.g. " + g_ldap_base)
|
logging.error("Missing ENV[LDAP_BASE], e.g. {}".format(g_ldap_base))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
g_ldap_base = os.environ['LDAP_BASE']
|
g_ldap_base = os.environ['LDAP_BASE']
|
||||||
if 'LDAP_QUERY' not in os.environ:
|
if 'LDAP_QUERY' not in os.environ:
|
||||||
@ -542,43 +574,57 @@ if __name__ == "__main__":
|
|||||||
for whitelisted_rcpt in re.split(',|\s', whitelisted_rcpts_str):
|
for whitelisted_rcpt in re.split(',|\s', whitelisted_rcpts_str):
|
||||||
if g_re_email.match(whitelisted_rcpt) == None:
|
if g_re_email.match(whitelisted_rcpt) == None:
|
||||||
logging.error(
|
logging.error(
|
||||||
"ENV[MILTER_WHITELISTED_RCPTS]: invalid email address: " +
|
"ENV[MILTER_WHITELISTED_RCPTS]: invalid email address: {}"
|
||||||
whitelisted_rcpt
|
.format(whitelisted_rcpt)
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
logging.info("ENV[MILTER_WHITELISTED_RCPTS]: " + whitelisted_rcpt)
|
logging.info("ENV[MILTER_WHITELISTED_RCPTS]: {}".format(
|
||||||
|
whitelisted_rcpt
|
||||||
|
))
|
||||||
g_milter_whitelisted_rcpts[whitelisted_rcpt] = {}
|
g_milter_whitelisted_rcpts[whitelisted_rcpt] = {}
|
||||||
if 'MILTER_DKIM_ENABLED' in os.environ:
|
if 'MILTER_DKIM_ENABLED' in os.environ:
|
||||||
g_milter_dkim_enabled = True
|
g_milter_dkim_enabled = True
|
||||||
if 'MILTER_TRUSTED_AUTHSERVID' in os.environ:
|
if 'MILTER_TRUSTED_AUTHSERVID' in os.environ:
|
||||||
g_milter_trusted_authservid = os.environ['MILTER_TRUSTED_AUTHSERVID'].lower()
|
g_milter_trusted_authservid = os.environ['MILTER_TRUSTED_AUTHSERVID'].lower()
|
||||||
logging.info("ENV[MILTER_TRUSTED_AUTHSERVID]: {0}".format(g_milter_trusted_authservid))
|
logging.info("ENV[MILTER_TRUSTED_AUTHSERVID]: {0}".format(
|
||||||
|
g_milter_trusted_authservid
|
||||||
|
))
|
||||||
else:
|
else:
|
||||||
logging.error("ENV[MILTER_TRUSTED_AUTHSERVID] is mandatory!")
|
logging.error("ENV[MILTER_TRUSTED_AUTHSERVID] is mandatory!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
logging.info("ENV[MILTER_DKIM_ENABLED]: {0}".format(g_milter_dkim_enabled))
|
logging.info("ENV[MILTER_DKIM_ENABLED]: {0}".format(g_milter_dkim_enabled))
|
||||||
set_config_parameter("RESTARTABLE_SLEEPTIME", 2)
|
if 'MILTER_MAX_RCPT_ENABLED' in os.environ:
|
||||||
set_config_parameter("RESTARTABLE_TRIES", 2)
|
g_milter_max_rcpt_enabled = True
|
||||||
server = Server(g_ldap_server, get_info=NONE)
|
if 'MILTER_MAX_RCPT' in os.environ:
|
||||||
g_ldap_conn = Connection(server,
|
if os.environ['MILTER_MAX_RCPT'].isnumeric():
|
||||||
g_ldap_binddn, g_ldap_bindpw,
|
g_milter_max_rcpt = os.environ['MILTER_MAX_RCPT']
|
||||||
auto_bind=True, raise_exceptions=True,
|
else:
|
||||||
client_strategy='RESTARTABLE'
|
print("ENV[MILTER_MAX_RCPT] must be numeric!")
|
||||||
)
|
sys.exit(1)
|
||||||
logging.info("Connected to LDAP-server: " + g_ldap_server)
|
try:
|
||||||
|
set_config_parameter("RESTARTABLE_SLEEPTIME", 2)
|
||||||
|
set_config_parameter("RESTARTABLE_TRIES", 2)
|
||||||
|
server = Server(g_ldap_server, get_info=NONE)
|
||||||
|
g_ldap_conn = Connection(server,
|
||||||
|
g_ldap_binddn, g_ldap_bindpw,
|
||||||
|
auto_bind=True, raise_exceptions=True,
|
||||||
|
client_strategy='RESTARTABLE'
|
||||||
|
)
|
||||||
|
logging.info("Connected to LDAP-server: " + g_ldap_server)
|
||||||
|
except LDAPException as e:
|
||||||
|
raise Exception("Connection to LDAP-server failed: {}".format(str(e))) from e
|
||||||
timeout = 600
|
timeout = 600
|
||||||
# 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)
|
||||||
logging.info("Startup " + g_milter_name +
|
logging.info("Starting {0}@socket: {1} in mode {2}".format(
|
||||||
"@socket: " + g_milter_socket +
|
g_milter_name, g_milter_socket, g_milter_mode
|
||||||
" in mode: " + g_milter_mode
|
))
|
||||||
)
|
|
||||||
Milter.runmilter(g_milter_name,g_milter_socket,timeout,True)
|
Milter.runmilter(g_milter_name,g_milter_socket,timeout,True)
|
||||||
logging.info("Shutdown " + g_milter_name)
|
logging.info("Shutdown {}".format(g_milter_name))
|
||||||
except:
|
except:
|
||||||
logging.error("MAIN-EXCEPTION: " + traceback.format_exc())
|
logging.error("MAIN-EXCEPTION: {}".format(traceback.format_exc()))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
56
tests/miltertest-ip-fail.lua
Normal file
56
tests/miltertest-ip-fail.lua
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
-- https://mopano.github.io/sendmail-filter-api/constant-values.html#com.sendmail.milter.MilterConstants
|
||||||
|
-- http://www.opendkim.org/miltertest.8.html
|
||||||
|
|
||||||
|
-- socket must be defined as miltertest global variable (-D)
|
||||||
|
conn = mt.connect(socket)
|
||||||
|
if conn == nil then
|
||||||
|
error "mt.connect() failed"
|
||||||
|
end
|
||||||
|
if mt.conninfo(conn, "blubb-ip.host", "127.6.6.6") ~= nil then
|
||||||
|
error "mt.conninfo() failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
mt.set_timeout(60)
|
||||||
|
|
||||||
|
-- 5321.FROM
|
||||||
|
if mt.mailfrom(conn, "tester-ip-fail@test.blah") ~= nil then
|
||||||
|
error "mt.mailfrom() failed"
|
||||||
|
end
|
||||||
|
if mt.getreply(conn) == SMFIR_CONTINUE then
|
||||||
|
mt.echo("FROM-continue")
|
||||||
|
elseif mt.getreply(conn) == SMFIR_REPLYCODE then
|
||||||
|
error("FROM-reject")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 5321.RCPT+MACROS
|
||||||
|
mt.macro(conn, SMFIC_RCPT, "i", "4CgSNs5Q9sz7SllQ")
|
||||||
|
if mt.rcptto(conn, "<rcpt-ip-fail@test.blubb>") ~= nil then
|
||||||
|
error "mt.rcptto() failed"
|
||||||
|
end
|
||||||
|
if mt.getreply(conn) == SMFIR_CONTINUE then
|
||||||
|
mt.echo("RCPT-continue")
|
||||||
|
elseif mt.getreply(conn) == SMFIR_REPLYCODE then
|
||||||
|
mt.echo("RCPT-reject")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 5322.HEADERS
|
||||||
|
if mt.header(conn, "fRoM", '"Blah Blubb" <tester-ip-fail@test.blah>') ~= nil then
|
||||||
|
error "mt.header(From) failed"
|
||||||
|
end
|
||||||
|
if mt.header(conn, "Authentication-REsuLTS", "my-auth-serv-id;\n dkim=fail header.d=test.blah header.s=selector1-test-blah header.b=mumble") ~= nil then
|
||||||
|
error "mt.header(Authentication-Results) failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- EOM
|
||||||
|
if mt.eom(conn) ~= nil then
|
||||||
|
error "mt.eom() failed"
|
||||||
|
end
|
||||||
|
mt.echo("EOM: " .. mt.getreply(conn))
|
||||||
|
if mt.getreply(conn) == SMFIR_CONTINUE then
|
||||||
|
mt.echo("EOM-continue")
|
||||||
|
elseif mt.getreply(conn) == SMFIR_REPLYCODE then
|
||||||
|
mt.echo("EOM-reject")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- DISCONNECT
|
||||||
|
mt.disconnect(conn)
|
||||||
@ -16,8 +16,10 @@ 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
|
if mt.getreply(conn) == SMFIR_CONTINUE then
|
||||||
error "mt.mailfrom() unexpected reply"
|
mt.echo("FROM-continue")
|
||||||
|
elseif mt.getreply(conn) == SMFIR_REPLYCODE then
|
||||||
|
error("FROM-reject")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 5321.RCPT+MACROS
|
-- 5321.RCPT+MACROS
|
||||||
@ -25,8 +27,10 @@ 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
|
if mt.getreply(conn) == SMFIR_CONTINUE then
|
||||||
error "mt.rcptto() unexpected reply"
|
mt.echo("RCPT-continue")
|
||||||
|
elseif mt.getreply(conn) == SMFIR_REPLYCODE then
|
||||||
|
mt.echo("RCPT-reject")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- 5322.HEADERS
|
-- 5322.HEADERS
|
||||||
|
|||||||
53
tests/miltertest-sasl-wildcard.lua
Normal file
53
tests/miltertest-sasl-wildcard.lua
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
-- https://mopano.github.io/sendmail-filter-api/constant-values.html#com.sendmail.milter.MilterConstants
|
||||||
|
-- http://www.opendkim.org/miltertest.8.html
|
||||||
|
|
||||||
|
-- socket must be defined as miltertest global variable (-D)
|
||||||
|
conn = mt.connect(socket)
|
||||||
|
if conn == nil then
|
||||||
|
error "mt.connect() failed"
|
||||||
|
end
|
||||||
|
if mt.conninfo(conn, "localhost", "::1") ~= nil then
|
||||||
|
error "mt.conninfo() failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
mt.set_timeout(60)
|
||||||
|
|
||||||
|
-- 5321.FROM+MACROS
|
||||||
|
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
|
||||||
|
error "mt.header(From) failed"
|
||||||
|
end
|
||||||
|
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=test.blah header.s=selector1-test-blah header.b=mumble") ~= nil then
|
||||||
|
error "mt.header(Authentication-Results) failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- EOM
|
||||||
|
if mt.eom(conn) ~= nil then
|
||||||
|
error "mt.eom() failed"
|
||||||
|
end
|
||||||
|
mt.echo("EOM: " .. mt.getreply(conn))
|
||||||
|
if mt.getreply(conn) == SMFIR_CONTINUE then
|
||||||
|
mt.echo("EOM-continue")
|
||||||
|
elseif mt.getreply(conn) == SMFIR_REPLYCODE then
|
||||||
|
mt.echo("EOM-reject")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- DISCONNECT
|
||||||
|
mt.disconnect(conn)
|
||||||
53
tests/miltertest-x509_5321-fail_dkim-pass.lua
Normal file
53
tests/miltertest-x509_5321-fail_dkim-pass.lua
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
-- https://mopano.github.io/sendmail-filter-api/constant-values.html#com.sendmail.milter.MilterConstants
|
||||||
|
-- http://www.opendkim.org/miltertest.8.html
|
||||||
|
|
||||||
|
-- socket must be defined as miltertest global variable (-D)
|
||||||
|
conn = mt.connect(socket)
|
||||||
|
if conn == nil then
|
||||||
|
error "mt.connect() failed"
|
||||||
|
end
|
||||||
|
if mt.conninfo(conn, "localhost", "::1") ~= nil then
|
||||||
|
error "mt.conninfo() failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
mt.set_timeout(60)
|
||||||
|
|
||||||
|
-- 5321.FROM+MACROS
|
||||||
|
mt.macro(conn, SMFIC_MAIL, "{cert_issuer}", "x509-issuer", "{cert_subject}", "x509-subject")
|
||||||
|
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
|
||||||
|
error "mt.header(From) failed"
|
||||||
|
end
|
||||||
|
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=test.blah header.s=selector1-test-blah header.b=mumble") ~= nil then
|
||||||
|
error "mt.header(Authentication-Results) failed"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- EOM
|
||||||
|
if mt.eom(conn) ~= nil then
|
||||||
|
error "mt.eom() failed"
|
||||||
|
end
|
||||||
|
mt.echo("EOM: " .. mt.getreply(conn))
|
||||||
|
if mt.getreply(conn) == SMFIR_CONTINUE then
|
||||||
|
mt.echo("EOM-continue")
|
||||||
|
elseif mt.getreply(conn) == SMFIR_REPLYCODE then
|
||||||
|
mt.echo("EOM-reject")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- DISCONNECT
|
||||||
|
mt.disconnect(conn)
|
||||||
Loading…
Reference in New Issue
Block a user