ENV[MILTER_EXPECT_AUTH]

This commit is contained in:
Dominik Chilla 2019-05-16 00:24:50 +02:00
parent 88668f525c
commit 4ec4a6996a
3 changed files with 56 additions and 21 deletions

View File

@ -77,6 +77,12 @@ attributetype ( 1.3.6.1.4.1.53501.1.1.10
SUBSTR caseIgnoreIA5SubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{4096})
attributetype ( 1.3.6.1.4.1.53501.1.1.11
NAME 'allowedx509CN'
DESC 'Allowed x509 Common Name - CN'
EQUALITY caseExactIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64})
#
# Objects: 1.3.6.1.4.1.53501.1.2
#
@ -86,4 +92,4 @@ objectclass ( 1.3.6.1.4.1.53501.1.2.1
SUP top
STRUCTURAL
MUST ( policyID $ allowedRcpts $ allowedSenders )
MAY ( authType $ deniedSenders $ deniedRcpts $ allowedClientAddr $ deniedClientAddr $ allowedSaslUser $ extBLOB ))
MAY ( authType $ deniedSenders $ deniedRcpts $ allowedClientAddr $ deniedClientAddr $ allowedSaslUser $ extBLOB $ allowedx509CN ))

View File

@ -76,6 +76,11 @@ services:
#MILTER_SOCKET: inet6:8020
#MILTER_REJECT_MESSAGE: Message rejected due to security policy
#MILTER_TMPFAIL_MESSAGE: Message temporary rejected. Please try again later ;)
# Expect authentication information from LDAP like allowedClientAddr,
# allowedSaslUser or allowedx509CN. This is usefull if the milter handles
# outbound email traffic, where senders must authenticate before submission.
# Default: False (inbound mode)
MILTER_EXPECT_AUTH: 'True'
hostname: ldap-acl-milter
volumes:
- "lam_socket:/socket/:rw"

View File

@ -29,6 +29,7 @@ g_milter_mode = 'test'
g_milter_default_policy = 'reject'
g_milter_schema = False
g_milter_schema_wildcard_domain = False # works only if g_milter_schema == True
g_milter_expect_auth = False
class LdapAclMilter(Milter.Base):
# Each new connection is handled in an own thread
@ -48,9 +49,9 @@ class LdapAclMilter(Milter.Base):
)
# Not registered/used callbacks
@Milter.nocallback
def hello(self, heloname):
return Milter.CONTINUE
#@Milter.nocallback
#def hello(self, heloname)
# return Milter.CONTINUE
@Milter.nocallback
def header(self, name, hval):
return Milter.CONTINUE
@ -70,13 +71,16 @@ class LdapAclMilter(Milter.Base):
def envfrom(self, mailfrom, *str):
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
# with the authenticating client was trusted in a x509 manner!
# http://postfix.1071664.n5.nabble.com/verification-levels-and-Milter-tp91634p91638.html
x509cn = self.getsymval('{cert_subject}')
if x509cn != None:
self.x509_cn = x509cn
logging.info(self.mconn_id + "/FROM x509_cn=" + self.x509_cn)
except:
logging.error(self.mconn_id + "/FROM " + traceback.format_exc())
logging.error(self.mconn_id + "/FROM x509_cn " + traceback.format_exc())
try:
# this may fail, if no SASL authentication preceded
sasl_user = self.getsymval('{auth_authen}')
@ -84,7 +88,7 @@ class LdapAclMilter(Milter.Base):
self.sasl_user = sasl_user
logging.info(self.mconn_id + "/FROM sasl_user=" + self.sasl_user)
except:
logging.error(self.mconn_id + "/FROM " + traceback.format_exc())
logging.error(self.mconn_id + "/FROM sasl_user " + traceback.format_exc())
mailfrom = mailfrom.replace("<","")
mailfrom = mailfrom.replace(">","")
self.env_from = mailfrom
@ -121,16 +125,23 @@ class LdapAclMilter(Milter.Base):
if g_milter_schema == True:
# LDAP-ACL-Milter schema
auth_method = ''
# Authentication order!
# 1. x509 client certificate
# 2. SASL authenticated
# if authType sasl_user, check if authenticated user matches sasl_user and
# check if sender/recipient pair match
# 3. Client-IP authenticated
# if authType client_addr, check if client-ip matches client_addr
# check if sender/recipient pair match
# 4. not authenticated
# ldap-search with excluded sasl_user and client_addr attributes!
if g_milter_expect_auth == True:
auth_method = "(|(allowedClientAddr="+self.client_addr+")%SASL_AUTH%%X509CN_AUTH%)"
if self.sasl_user:
auth_method = auth_method.replace(
'%SASL_AUTH%',"(allowedSaslUser="+self.sasl_user+")"
)
else:
auth_method = auth_method.replace('%SASL_AUTH%','')
if self.x509_cn:
auth_method = auth_method.replace(
'%X509CN_AUTH%',"(allowedx509CN="+self.x509_cn+")"
)
else:
auth_method = auth_method.replace('%X509CN_AUTH%','')
logging.debug(self.mconn_id +
" auth_method: " + auth_method
)
if g_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.
@ -140,7 +151,7 @@ class LdapAclMilter(Milter.Base):
# In this case *@<domain> cannot be a real address!
if re.match(r'^\*@.+$', self.env_from, re.IGNORECASE):
logging.info(self.mconn_id + "/RCPT REJECT "
+ "Wildcard sender (*@<domain>) is not allowed in wildcard mode!"
+ "Literal wildcard sender (*@<domain>) is not allowed in wildcard mode!"
)
self.setreply('550','5.7.1',
g_milter_reject_message + ' (' + self.mconn_id + ')'
@ -184,9 +195,15 @@ class LdapAclMilter(Milter.Base):
"rcpt": to, "reason": g_milter_reject_message,
"time_start":time_start, "time_end":time_end
})
logging.info(self.mconn_id + "/RCPT " + "policy mismatch "
"5321.from=" + self.env_from + ", 5321.rcpt=" + to
)
if g_milter_expect_auth == True:
logging.info(self.mconn_id + "/RCPT " + "policy mismatch "
"5321.from=" + self.env_from + ", 5321.rcpt=" + to +
", auth_method=" + auth_method
)
else:
logging.info(self.mconn_id + "/RCPT " + "policy mismatch "
"5321.from=" + self.env_from + ", 5321.rcpt=" + to
)
if g_milter_mode == 'reject':
logging.info(self.mconn_id + "/RCPT REJECT "
+ g_milter_reject_message
@ -236,6 +253,10 @@ class LdapAclMilter(Milter.Base):
logging.warn(self.mconn_id + "/RCPT LDAP: " + str(e))
self.setreply('451', '4.7.1', g_milter_tmpfail_message)
return Milter.TEMPFAIL
except:
logging.error(self.mconn_id + "/RCPT LDAP: " + traceback.format_exc())
self.setreply('451', '4.7.1', g_milter_tmpfail_message)
return Milter.TEMPFAIL
self.env_rcpts.append({
"rcpt": to, "reason":'pass',"time_start":time_start,"time_end":time_end
})
@ -338,6 +359,9 @@ if __name__ == "__main__":
g_milter_reject_message = os.environ['MILTER_REJECT_MESSAGE']
if 'MILTER_TMPFAIL_MESSAGE' in os.environ:
g_milter_tmpfail_message = os.environ['MILTER_TMPFAIL_MESSAGE']
if 'MILTER_EXPECT_AUTH' in os.environ:
if re.match(r'^true$', os.environ['MILTER_EXPECT_AUTH'], re.IGNORECASE):
g_milter_expect_auth = True
server = Server(g_ldap_server, get_info=NONE)
g_ldap_conn = Connection(server,
g_ldap_binddn, g_ldap_bindpw,