mirror of
https://github.com/chillout2k/ldap-acl-milter.git
synced 2025-12-12 19:00:19 +00:00
x509 authentication with subject and issuer
This commit is contained in:
parent
4ec4a6996a
commit
c4e21a38dd
@ -15,13 +15,6 @@ attributetype ( 1.3.6.1.4.1.53501.1.1.1
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128}
|
||||
SINGLE-VALUE)
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.53501.1.1.2
|
||||
NAME 'authType'
|
||||
DESC 'Authentication type: sasl_user, client_addr, none'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}
|
||||
SINGLE-VALUE)
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.53501.1.1.3
|
||||
NAME 'allowedSenders'
|
||||
DESC 'Allowed RFC5321.from'
|
||||
@ -78,8 +71,14 @@ attributetype ( 1.3.6.1.4.1.53501.1.1.10
|
||||
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'
|
||||
NAME 'allowedx509subject'
|
||||
DESC 'Allowed x509 Common Name - subject'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64})
|
||||
|
||||
attributetype ( 1.3.6.1.4.1.53501.1.1.12
|
||||
NAME 'allowedx509issuer'
|
||||
DESC 'Allowed x509 Common Name - issuer'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{64})
|
||||
|
||||
@ -89,7 +88,11 @@ attributetype ( 1.3.6.1.4.1.53501.1.1.11
|
||||
objectclass ( 1.3.6.1.4.1.53501.1.2.1
|
||||
NAME 'lamPolicy'
|
||||
DESC 'ldap-acl-milter policy'
|
||||
SUP top
|
||||
STRUCTURAL
|
||||
MUST ( policyID $ allowedRcpts $ allowedSenders )
|
||||
MAY ( authType $ deniedSenders $ deniedRcpts $ allowedClientAddr $ deniedClientAddr $ allowedSaslUser $ extBLOB $ allowedx509CN ))
|
||||
SUP top STRUCTURAL
|
||||
MUST policyID
|
||||
MAY ( allowedRcpts $ deniedRcpts $
|
||||
allowedSenders $ deniedSenders $
|
||||
allowedClientAddr $ deniedClientAddr $
|
||||
allowedSaslUser $ extBLOB $
|
||||
allowedx509subject $ allowedx509issuer )
|
||||
)
|
||||
|
||||
@ -40,7 +40,8 @@ class LdapAclMilter(Milter.Base):
|
||||
self.env_from = None
|
||||
self.env_from_domain = None
|
||||
self.sasl_user = None
|
||||
self.x509_cn = None
|
||||
self.x509_subject = None
|
||||
self.x509_issuer = None
|
||||
# recipients list
|
||||
self.env_rcpts = []
|
||||
# https://stackoverflow.com/a/2257449
|
||||
@ -75,12 +76,17 @@ class LdapAclMilter(Milter.Base):
|
||||
# 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)
|
||||
# Unfortunately, postfix only passes the CN-field of the subject/issuer DN :-/
|
||||
x509_subject = self.getsymval('{cert_subject}')
|
||||
if x509_subject != None:
|
||||
self.x509_subject = x509_subject
|
||||
logging.info(self.mconn_id + "/FROM x509_subject=" + self.x509_subject)
|
||||
x509_issuer = self.getsymval('{cert_issuer}')
|
||||
if x509_issuer != None:
|
||||
self.x509_issuer = x509_issuer
|
||||
logging.info(self.mconn_id + "/FROM x509_issuer=" + self.x509_issuer)
|
||||
except:
|
||||
logging.error(self.mconn_id + "/FROM x509_cn " + traceback.format_exc())
|
||||
logging.error(self.mconn_id + "/FROM x509 " + traceback.format_exc())
|
||||
try:
|
||||
# this may fail, if no SASL authentication preceded
|
||||
sasl_user = self.getsymval('{auth_authen}')
|
||||
@ -126,19 +132,22 @@ class LdapAclMilter(Milter.Base):
|
||||
# LDAP-ACL-Milter schema
|
||||
auth_method = ''
|
||||
if g_milter_expect_auth == True:
|
||||
auth_method = "(|(allowedClientAddr="+self.client_addr+")%SASL_AUTH%%X509CN_AUTH%)"
|
||||
auth_method = "(|(allowedClientAddr="+self.client_addr+")%SASL_AUTH%%X509_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+")"
|
||||
if self.x509_subject and self.x509_issuer:
|
||||
auth_method = auth_method.replace('%X509_AUTH%',
|
||||
"(&"+
|
||||
"(allowedx509subject="+self.x509_subject+")"+
|
||||
"(allowedx509issuer="+self.x509_issuer+")"+
|
||||
")"
|
||||
)
|
||||
else:
|
||||
auth_method = auth_method.replace('%X509CN_AUTH%','')
|
||||
auth_method = auth_method.replace('%X509_AUTH%','')
|
||||
logging.debug(self.mconn_id +
|
||||
" auth_method: " + auth_method
|
||||
)
|
||||
@ -150,16 +159,18 @@ class LdapAclMilter(Milter.Base):
|
||||
# for proper use in LDAP queries.
|
||||
# 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 "
|
||||
+ "Literal wildcard sender (*@<domain>) is not allowed in wildcard mode!"
|
||||
logging.info(self.mconn_id + "/RCPT REJECT " +
|
||||
"Literal wildcard sender (*@<domain>) is not " +
|
||||
"allowed in wildcard mode!"
|
||||
)
|
||||
self.setreply('550','5.7.1',
|
||||
g_milter_reject_message + ' (' + self.mconn_id + ')'
|
||||
)
|
||||
return Milter.REJECT
|
||||
if re.match(r'^\*@.+$', to, re.IGNORECASE):
|
||||
logging.info(self.mconn_id + "/RCPT REJECT "
|
||||
+ "Wildcard recipient (*@<domain>) is not allowed in wildcard mode!"
|
||||
logging.info(self.mconn_id + "/RCPT REJECT " +
|
||||
"Literal wildcard recipient (*@<domain>) is not " +
|
||||
"allowed in wildcard mode!"
|
||||
)
|
||||
self.setreply('550','5.7.1',
|
||||
g_milter_reject_message + ' (' + self.mconn_id + ')'
|
||||
@ -171,12 +182,15 @@ class LdapAclMilter(Milter.Base):
|
||||
"(|"+
|
||||
"(allowedRcpts="+to+")"+
|
||||
"(allowedRcpts=\\2a@"+rcpt_domain+")"+
|
||||
"(allowedRcpts=\\2a@\\2a)"+
|
||||
")"+
|
||||
"(|"+
|
||||
"(allowedSenders="+self.env_from+")"+
|
||||
"(allowedSenders=\\2a@"+self.env_from_domain+")"+
|
||||
")"+
|
||||
")"
|
||||
"(allowedSenders=\\2a@\\2a)"+
|
||||
")"+
|
||||
")",
|
||||
attributes=['policyID']
|
||||
)
|
||||
else:
|
||||
# Asterisk must be ASCII-HEX encoded for LDAP queries
|
||||
@ -187,12 +201,14 @@ class LdapAclMilter(Milter.Base):
|
||||
auth_method +
|
||||
"(allowedRcpts="+query_to+")" +
|
||||
"(allowedSenders="+query_from+")" +
|
||||
")"
|
||||
")",
|
||||
attributes=['policyID']
|
||||
)
|
||||
time_end = timer()
|
||||
if len(self.ldap_conn.entries) == 0:
|
||||
# Policy not found in LDAP
|
||||
self.env_rcpts.append({
|
||||
"rcpt": to, "reason": g_milter_reject_message,
|
||||
"rcpt": to, "action": g_milter_reject_message,
|
||||
"time_start":time_start, "time_end":time_end
|
||||
})
|
||||
if g_milter_expect_auth == True:
|
||||
@ -217,6 +233,22 @@ class LdapAclMilter(Milter.Base):
|
||||
g_milter_reject_message
|
||||
)
|
||||
return Milter.CONTINUE
|
||||
elif len(self.ldap_conn.entries) == 1:
|
||||
# Policy found in LDAP, but which one?
|
||||
entry = self.ldap_conn.entries[0]
|
||||
logging.info(self.mconn_id +
|
||||
"/RCPT Policy match: " + entry.policyID.value
|
||||
)
|
||||
elif len(self.ldap_conn.entries) > 1:
|
||||
# Something went wrong!? There shouldn´t be more than one entries!
|
||||
logging.warn(self.mconn_id + "/RCPT More than one policies found! "+
|
||||
"5321.from=" + self.env_from + ", 5321.rcpt=" + to +
|
||||
", auth_method=" + auth_method
|
||||
)
|
||||
self.setreply('550','5.7.1',
|
||||
g_milter_reject_message + ' (' + self.mconn_id + ')'
|
||||
)
|
||||
return Milter.REJECT
|
||||
else:
|
||||
# Custom LDAP schema
|
||||
# 'build' a LDAP query per recipient
|
||||
@ -232,7 +264,7 @@ class LdapAclMilter(Milter.Base):
|
||||
time_end = timer()
|
||||
if len(self.ldap_conn.entries) == 0:
|
||||
self.env_rcpts.append({
|
||||
"rcpt": to, "reason": g_milter_reject_message,
|
||||
"rcpt": to, "action": g_milter_reject_message,
|
||||
"time_start":time_start, "time_end":time_end
|
||||
})
|
||||
logging.info(self.mconn_id + "/RCPT " + "policy mismatch "
|
||||
@ -258,7 +290,7 @@ class LdapAclMilter(Milter.Base):
|
||||
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
|
||||
"rcpt": to, "action":'pass',"time_start":time_start,"time_end":time_end
|
||||
})
|
||||
return Milter.CONTINUE
|
||||
|
||||
@ -271,7 +303,7 @@ class LdapAclMilter(Milter.Base):
|
||||
duration = rcpt['time_end'] - rcpt['time_start']
|
||||
logging.info(self.mconn_id + "/DATA " + self.queue_id +
|
||||
": 5321.from=" + self.env_from + " 5321.rcpt=" +
|
||||
rcpt['rcpt'] + " reason=" + rcpt['reason'] +
|
||||
rcpt['rcpt'] + " action=" + rcpt['action'] +
|
||||
" duration=" + str(duration) + "sec."
|
||||
)
|
||||
except:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user