mirror of
https://github.com/chillout2k/ExOTA-Milter.git
synced 2025-12-14 18:30:19 +00:00
Compare commits
No commits in common. "3bd81eaefa348d403e074e33be04af92a17bc26d" and "78e82b51571bd10e73173a772c43736e42c22c6a" have entirely different histories.
3bd81eaefa
...
78e82b5157
@ -19,7 +19,7 @@ RUN chown -R exota-milter /app /cmd \
|
|||||||
&& chmod -R +x /app /cmd
|
&& chmod -R +x /app /cmd
|
||||||
|
|
||||||
# Default file policy path: /data/policy.json
|
# Default file policy path: /data/policy.json
|
||||||
VOLUME [ "/socket", "/data" ]
|
VOLUME [ "/data" ]
|
||||||
|
|
||||||
USER exota-milter
|
USER exota-milter
|
||||||
CMD [ "/cmd" ]
|
CMD [ "/cmd" ]
|
||||||
@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
The **ExOTA-[Milter](https://en.wikipedia.org/wiki/Milter)** application is written in python3 and derives from **[sdgathman´s pymilter](https://github.com/sdgathman/pymilter)**.
|
The **ExOTA-[Milter](https://en.wikipedia.org/wiki/Milter)** application is written in python3 and derives from **[sdgathman´s pymilter](https://github.com/sdgathman/pymilter)**.
|
||||||
|
|
||||||
|
# Synopsis
|
||||||
|
TODO
|
||||||
|
|
||||||
|
# Table of contents
|
||||||
|
TODO
|
||||||
|
|
||||||
# Abstract/problem/motivation
|
# Abstract/problem/motivation
|
||||||
Fact is that more and more companies are migrating their Outlook/Exchange environments to the [Microsoft cloud](https://www.microsoft.com/microsoft-365).
|
Fact is that more and more companies are migrating their Outlook/Exchange environments to the [Microsoft cloud](https://www.microsoft.com/microsoft-365).
|
||||||
|
|
||||||
|
|||||||
@ -49,19 +49,18 @@ g_re_domain = re.compile(r'^.*@(\S+)$', re.IGNORECASE)
|
|||||||
class ExOTAMilter(Milter.Base):
|
class ExOTAMilter(Milter.Base):
|
||||||
# Each new connection is handled in an own thread
|
# Each new connection is handled in an own thread
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.x509_client_valid = False
|
|
||||||
self.client_ip = None
|
|
||||||
self.reset_milter()
|
self.reset_milter()
|
||||||
|
|
||||||
def reset_milter(self):
|
def reset_milter(self):
|
||||||
self.conn_reused = False
|
self.conn_reused = False
|
||||||
|
self.client_ip = None
|
||||||
self.hdr_from = None
|
self.hdr_from = None
|
||||||
self.hdr_from_domain = None
|
self.hdr_from_domain = None
|
||||||
self.hdr_tenant_id = None
|
self.hdr_tenant_id = None
|
||||||
self.hdr_tenant_id_count = 0
|
self.hdr_tenant_id_count = 0
|
||||||
self.dkim_results = []
|
self.dkim_results = []
|
||||||
self.dkim_valid = False
|
self.dkim_valid = False
|
||||||
self.xar_hdr_count = 0
|
self.x509_client_valid = False
|
||||||
# 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)
|
||||||
@ -72,13 +71,16 @@ class ExOTAMilter(Milter.Base):
|
|||||||
message = g_milter_reject_message
|
message = g_milter_reject_message
|
||||||
if 'message' in kwargs:
|
if 'message' in kwargs:
|
||||||
message = kwargs['message']
|
message = kwargs['message']
|
||||||
if 'queue_id' in kwargs:
|
|
||||||
message = "queue_id: {0} - {1}".format(kwargs['queue_id'], message)
|
|
||||||
if 'reason' in kwargs:
|
|
||||||
message = "{0} - reason: {1}".format(message, kwargs['reason'])
|
|
||||||
self.setreply('550','5.7.1', message)
|
self.setreply('550','5.7.1', message)
|
||||||
return Milter.REJECT
|
return Milter.REJECT
|
||||||
|
|
||||||
|
def smfir_tmpfail(self, **kwargs):
|
||||||
|
message = g_milter_tmpfail_message
|
||||||
|
if 'message' in kwargs:
|
||||||
|
message = kwargs['message']
|
||||||
|
self.setreply('450','4.7.1', message)
|
||||||
|
return Milter.TEMPFAIL
|
||||||
|
|
||||||
def smfir_continue(self):
|
def smfir_continue(self):
|
||||||
return Milter.CONTINUE
|
return Milter.CONTINUE
|
||||||
|
|
||||||
@ -129,7 +131,7 @@ class ExOTAMilter(Milter.Base):
|
|||||||
logging.error(self.mconn_id + "/" + str(self.getsymval('i')) + "/HDR " +
|
logging.error(self.mconn_id + "/" + str(self.getsymval('i')) + "/HDR " +
|
||||||
"Could not determine domain-part of 5322.from=" + self.hdr_from
|
"Could not determine domain-part of 5322.from=" + self.hdr_from
|
||||||
)
|
)
|
||||||
return self.smfir_reject(queue_id=self.getsymval('i'))
|
return self.smfir_reject()
|
||||||
self.hdr_from_domain = m.group(1)
|
self.hdr_from_domain = m.group(1)
|
||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/HDR: 5322.from={0}, 5322.from_domain={1}".format(
|
"/HDR: 5322.from={0}, 5322.from_domain={1}".format(
|
||||||
@ -169,13 +171,6 @@ class ExOTAMilter(Milter.Base):
|
|||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/HDR: AR-parse exception: {0}".format(str(e))
|
"/HDR: AR-parse exception: {0}".format(str(e))
|
||||||
)
|
)
|
||||||
|
|
||||||
elif(name == "X-ExOTA-Authentication-Results"):
|
|
||||||
logging.debug(self.mconn_id + "/" + str(self.getsymval('i')) +
|
|
||||||
"/HDR: Found X-ExOTA-Authentication-Results header. Marking for deletion."
|
|
||||||
)
|
|
||||||
self.xar_hdr_count += 1
|
|
||||||
|
|
||||||
return self.smfir_continue()
|
return self.smfir_continue()
|
||||||
|
|
||||||
# EOM is mandatory as well and thus always called by MTA
|
# EOM is mandatory as well and thus always called by MTA
|
||||||
@ -189,10 +184,7 @@ class ExOTAMilter(Milter.Base):
|
|||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i'))
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i'))
|
||||||
+ "/EOM: No trusted x509 client CN found - action=reject"
|
+ "/EOM: No trusted x509 client CN found - action=reject"
|
||||||
)
|
)
|
||||||
return self.smfir_reject(
|
return self.smfir_reject()
|
||||||
queue_id = self.getsymval('i'),
|
|
||||||
reason = 'No trusted x509 client CN found'
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
if g_milter_x509_trusted_cn.lower() == cert_subject.lower():
|
if g_milter_x509_trusted_cn.lower() == cert_subject.lower():
|
||||||
self.x509_client_valid = True
|
self.x509_client_valid = True
|
||||||
@ -203,19 +195,13 @@ class ExOTAMilter(Milter.Base):
|
|||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/EOM Untrusted x509 client CN {0} - action=reject".format(cert_subject)
|
"/EOM Untrusted x509 client CN {0} - action=reject".format(cert_subject)
|
||||||
)
|
)
|
||||||
return self.smfir_reject(
|
return self.smfir_reject()
|
||||||
queue_id = self.getsymval('i'),
|
|
||||||
reason = "Untrusted x509 client CN: {0}".format(cert_subject)
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.hdr_from is None:
|
if self.hdr_from is None:
|
||||||
logging.error(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.error(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/EOM exception: could not determine 5322.from header - action=reject"
|
"/EOM exception: could not determine 5322.from header - action=reject"
|
||||||
)
|
)
|
||||||
return self.smfir_reject(
|
return self.smfir_reject()
|
||||||
queue_id = self.getsymval('i'),
|
|
||||||
reason = '5322.from header missing'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get policy for 5322.from_domain
|
# Get policy for 5322.from_domain
|
||||||
policy = None
|
policy = None
|
||||||
@ -228,26 +214,20 @@ class ExOTAMilter(Milter.Base):
|
|||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/EOM {0}".format(e.message)
|
"/EOM {0}".format(e.message)
|
||||||
)
|
)
|
||||||
return self.smfir_reject(
|
return self.smfir_reject()
|
||||||
queue_id = self.getsymval('i'),
|
|
||||||
reason = "No policy for {0}".format(self.hdr_from_domain)
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.hdr_tenant_id is None:
|
if self.hdr_tenant_id is None:
|
||||||
logging.error(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.error(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/EOM exception: could not determine X-MS-Exchange-CrossTenant-Id - action=reject"
|
"/EOM exception: could not determine X-MS-Exchange-CrossTenant-Id - action=reject"
|
||||||
)
|
)
|
||||||
return self.smfir_reject(
|
return self.smfir_reject()
|
||||||
queue_id = self.getsymval('i'),
|
|
||||||
reason = 'Tenant-ID is missing!'
|
|
||||||
)
|
|
||||||
if self.hdr_tenant_id_count > 1:
|
if self.hdr_tenant_id_count > 1:
|
||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/EOM: More than one tenant-IDs for {0} found - action=reject".format(
|
"/EOM: More than one tenant-IDs for {0} found - action=reject".format(
|
||||||
self.hdr_from_domain
|
self.hdr_from_domain
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return self.smfir_reject(queue_id=self.getsymval('i'))
|
return self.smfir_reject()
|
||||||
if self.hdr_tenant_id == policy.get_tenant_id():
|
if self.hdr_tenant_id == policy.get_tenant_id():
|
||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/EOM: tenant_id={0} status=match".format(self.hdr_tenant_id)
|
"/EOM: tenant_id={0} status=match".format(self.hdr_tenant_id)
|
||||||
@ -258,10 +238,7 @@ class ExOTAMilter(Milter.Base):
|
|||||||
self.hdr_tenant_id
|
self.hdr_tenant_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return self.smfir_reject(
|
return self.smfir_reject()
|
||||||
queue_id = self.getsymval('i'),
|
|
||||||
reason = 'No policy match for tenant-id'
|
|
||||||
)
|
|
||||||
|
|
||||||
if g_milter_dkim_enabled and policy.is_dkim_enabled():
|
if g_milter_dkim_enabled and policy.is_dkim_enabled():
|
||||||
logging.debug(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.debug(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
@ -289,18 +266,12 @@ class ExOTAMilter(Milter.Base):
|
|||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/EOM: No DKIM authentication results (AR headers) found - action=reject"
|
"/EOM: No DKIM authentication results (AR headers) found - action=reject"
|
||||||
)
|
)
|
||||||
return self.smfir_reject(
|
return self.smfir_reject()
|
||||||
queue_id = self.getsymval('i'),
|
|
||||||
reason = 'No DKIM authentication results found'
|
|
||||||
)
|
|
||||||
if self.dkim_valid == False:
|
if self.dkim_valid == False:
|
||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/EOM: DKIM authentication failed - action=reject"
|
"/EOM: DKIM authentication failed - action=reject"
|
||||||
)
|
)
|
||||||
return self.smfir_reject(
|
return self.smfir_reject()
|
||||||
queue_id = self.getsymval('i'),
|
|
||||||
reason = 'DKIM failed'
|
|
||||||
)
|
|
||||||
if g_milter_dkim_enabled:
|
if g_milter_dkim_enabled:
|
||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/EOM: Tenant authentication successful (dkim_enabled={0})".format(
|
"/EOM: Tenant authentication successful (dkim_enabled={0})".format(
|
||||||
@ -311,23 +282,10 @@ class ExOTAMilter(Milter.Base):
|
|||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/EOM: Tenant successfully authenticated"
|
"/EOM: Tenant successfully authenticated"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete all existing X-ExOTA-Authentication-Results headers
|
|
||||||
for i in range(self.xar_hdr_count, 0, -1):
|
|
||||||
logging.debug(self.mconn_id + "/" + str(self.getsymval('i')) +
|
|
||||||
"/EOM: Deleting X-ExOTA-Authentication-Results header"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
self.chgheader("X-ExOTA-Authentication-Results", i-1, '')
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(self.mconn_id + "/" + str(self.getsymval('i')) +
|
|
||||||
"/EOM: Deleting X-ExOTA-Authentication-Results failed: {0}".format(str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
if g_milter_add_header:
|
if g_milter_add_header:
|
||||||
try:
|
try:
|
||||||
self.addheader("X-ExOTA-Authentication-Results",
|
self.addheader("X-ExOTA-Authentication-Results",
|
||||||
"{0};\n auth=pass header.d={1} dkim={2} x509_client_trust={3}".format(
|
"{0};\n exota=pass header.d={1} dkim={2} x509_client_trust={3}".format(
|
||||||
g_milter_authservid, self.hdr_from_domain, policy.is_dkim_enabled(),
|
g_milter_authservid, self.hdr_from_domain, policy.is_dkim_enabled(),
|
||||||
g_milter_x509_enabled
|
g_milter_x509_enabled
|
||||||
)
|
)
|
||||||
@ -431,7 +389,8 @@ if __name__ == "__main__":
|
|||||||
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 = ExOTAMilter
|
Milter.factory = ExOTAMilter
|
||||||
Milter.set_flags(Milter.ADDHDRS + Milter.CHGHDRS)
|
flags = Milter.ADDHDRS
|
||||||
|
Milter.set_flags(flags)
|
||||||
logging.info("Startup " + g_milter_name +
|
logging.info("Startup " + g_milter_name +
|
||||||
"@socket: " + g_milter_socket
|
"@socket: " + g_milter_socket
|
||||||
)
|
)
|
||||||
|
|||||||
@ -54,9 +54,6 @@ end
|
|||||||
if mt.header(conn, "Authentication-Results", "some-validating-host;\n dkim=pass header.d=paypal.de header.s=pp-dkim1 header.b=PmTtUzer;\n dmarc=pass (policy=reject) header.from=paypal.de;\n spf=pass (some-validating-host: domain of service@paypal.de designates 173.0.84.226 as permitted sender) smtp.mailfrom=service@paypal.de") ~= nil then
|
if mt.header(conn, "Authentication-Results", "some-validating-host;\n dkim=pass header.d=paypal.de header.s=pp-dkim1 header.b=PmTtUzer;\n dmarc=pass (policy=reject) header.from=paypal.de;\n spf=pass (some-validating-host: domain of service@paypal.de designates 173.0.84.226 as permitted sender) smtp.mailfrom=service@paypal.de") ~= nil then
|
||||||
error "mt.header(Subject) failed"
|
error "mt.header(Subject) failed"
|
||||||
end
|
end
|
||||||
if mt.header(conn, "X-ExOTA-Authentication-Results", "my-auth-serv-id;\n exota=pass") ~= nil then
|
|
||||||
error "mt.header(Subject) failed"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- EOM
|
-- EOM
|
||||||
if mt.eom(conn) ~= nil then
|
if mt.eom(conn) ~= nil then
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user