From ca703bee9cb62eada22e68947b2d857a8b6b0d4b Mon Sep 17 00:00:00 2001 From: Dominik Chilla Date: Wed, 31 Aug 2022 13:15:13 +0200 Subject: [PATCH] Make X-MS-Exchange-CrossTenant-Id header optional --- app/exota-milter.py | 92 +++++++++++++++------------ tests/miltertest_no_tenantid_pass.lua | 82 ++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 42 deletions(-) create mode 100644 tests/miltertest_no_tenantid_pass.lua diff --git a/app/exota-milter.py b/app/exota-milter.py index f1a69eb..7338994 100644 --- a/app/exota-milter.py +++ b/app/exota-milter.py @@ -25,6 +25,8 @@ g_milter_socket = '/socket/' + g_milter_name g_milter_reject_message = 'Security policy violation!' # ENV[MILTER_TMPFAIL_MESSAGE] g_milter_tmpfail_message = 'Service temporarily not available! Please try again later.' +# ENV[MILTER_TENANT_ID_REQUIRED] +g_milter_tenant_id_required = False # ENV[MILTER_DKIM_ENABLED] g_milter_dkim_enabled = False # ENV[MILTER_DKIM_ALIGNMENT_REQUIRED] @@ -200,21 +202,22 @@ class ExOTAMilter(Milter.Base): self.hdr_resent_from, self.hdr_resent_from_domain ) ) - + # Parse non-standardized X-MS-Exchange-CrossTenant-Id header elif(name.lower() == "X-MS-Exchange-CrossTenant-Id".lower()): - log_debug(self.mconn_id + "/" + str(self.getsymval('i')) + - "/HDR: Tenant-ID: {0}".format(hval.lower()) - ) - if self.hdr_tenant_id_count > 0: - if not self.hdr_tenant_id == hval.lower(): - self.hdr_different_tenant_id = True - log_info(self.mconn_id + "/" + str(self.getsymval('i')) + - "/HDR: Different Tenant-IDs found!" + if g_milter_tenant_id_required == True: + log_debug(self.mconn_id + "/" + str(self.getsymval('i')) + + "/HDR: Tenant-ID: {0}".format(hval.lower()) ) - else: - self.hdr_tenant_id_count += 1 - self.hdr_tenant_id = hval.lower() + if self.hdr_tenant_id_count > 0: + if not self.hdr_tenant_id == hval.lower(): + self.hdr_different_tenant_id = True + log_info(self.mconn_id + "/" + str(self.getsymval('i')) + + "/HDR: Different Tenant-IDs found!" + ) + else: + self.hdr_tenant_id_count += 1 + self.hdr_tenant_id = hval.lower() # Parse RFC-7601 Authentication-Results header elif(name.lower() == "Authentication-Results".lower()): @@ -300,16 +303,17 @@ class ExOTAMilter(Milter.Base): reason = '5322.from header missing' ) - if self.hdr_different_tenant_id == True: - log_info(self.mconn_id + "/" + str(self.getsymval('i')) + - "/EOM: Multiple/different tenant-ID headers found for {0} - action=reject".format( - self.hdr_from_domain + if g_milter_tenant_id_required == True: + if self.hdr_different_tenant_id == True: + log_info(self.mconn_id + "/" + str(self.getsymval('i')) + + "/EOM: Multiple/different tenant-ID headers found for {0} - action=reject".format( + self.hdr_from_domain + ) + ) + return self.smfir_reject( + queue_id = self.getsymval('i'), + reason = 'Multiple/different tenant-ID headers found!' ) - ) - return self.smfir_reject( - queue_id = self.getsymval('i'), - reason = 'Multiple/different tenant-ID headers found!' - ) # Get policy for 5322.from_domain policy = None @@ -395,28 +399,29 @@ class ExOTAMilter(Milter.Base): reason = "No policy for 5322.from_domain {0}".format(self.hdr_from_domain) ) - if self.hdr_tenant_id is None: - log_error(self.mconn_id + "/" + str(self.getsymval('i')) + - "/EOM: exception: could not determine X-MS-Exchange-CrossTenant-Id - action=reject" - ) - return self.smfir_reject( - queue_id = self.getsymval('i'), - reason = 'Tenant-ID is missing!' - ) - if self.hdr_tenant_id == policy.get_tenant_id(): - log_info(self.mconn_id + "/" + str(self.getsymval('i')) + - "/EOM: tenant_id={0} status=match".format(self.hdr_tenant_id) - ) - else: - log_info(self.mconn_id + "/" + str(self.getsymval('i')) + - "/EOM: tenant_id={0} status=no_match - action=reject".format( - self.hdr_tenant_id + if g_milter_tenant_id_required == True: + if self.hdr_tenant_id is None: + log_error(self.mconn_id + "/" + str(self.getsymval('i')) + + "/EOM: exception: could not determine X-MS-Exchange-CrossTenant-Id - action=reject" + ) + return self.smfir_reject( + queue_id = self.getsymval('i'), + reason = 'Tenant-ID is missing!' + ) + if self.hdr_tenant_id == policy.get_tenant_id(): + log_info(self.mconn_id + "/" + str(self.getsymval('i')) + + "/EOM: tenant_id={0} status=match".format(self.hdr_tenant_id) + ) + else: + log_info(self.mconn_id + "/" + str(self.getsymval('i')) + + "/EOM: tenant_id={0} status=no_match - action=reject".format( + self.hdr_tenant_id + ) + ) + return self.smfir_reject( + queue_id = self.getsymval('i'), + reason = 'No policy match for tenant-id' ) - ) - 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(): log_debug(self.mconn_id + "/" + str(self.getsymval('i')) + @@ -529,6 +534,9 @@ if __name__ == "__main__": if 'MILTER_TMPFAIL_MESSAGE' in os.environ: g_milter_tmpfail_message = os.environ['MILTER_TMPFAIL_MESSAGE'] log_info("ENV[MILTER_TMPFAIL_MESSAGE]: {0}".format(g_milter_tmpfail_message)) + if 'MILTER_TENANT_ID_REQUIRED' in os.environ: + g_milter_tenant_id_required = True + log_info("ENV[MILTER_TENANT_ID_REQUIRED]: {0}".format(g_milter_tenant_id_required)) if 'MILTER_DKIM_ENABLED' in os.environ: g_milter_dkim_enabled = True if 'MILTER_TRUSTED_AUTHSERVID' in os.environ: diff --git a/tests/miltertest_no_tenantid_pass.lua b/tests/miltertest_no_tenantid_pass.lua new file mode 100644 index 0000000..e312573 --- /dev/null +++ b/tests/miltertest_no_tenantid_pass.lua @@ -0,0 +1,82 @@ +-- 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 +if mt.mailfrom(conn, "envelope.sender@example.org") ~= 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", '{cert_subject}', "mail.protection.outlook.comx") +if mt.rcptto(conn, "") ~= nil then + error "mt.rcptto() failed" +end +if mt.getreply(conn) ~= SMFIR_CONTINUE then + error "mt.rcptto() unexpected reply" +end + +-- HEADER +if mt.header(conn, "fRoM", '"Blah Blubb" ') ~= nil then + error "mt.header(From) failed" +end +if mt.header(conn, "resent-fRoM", '"Blah Blubb" ') ~= nil then + error "mt.header(From) failed" +end +if mt.header(conn, "Authentication-Results", "another-wrong-auth-serv-id;\n dkim=fail header.d=yad.onmicrosoft.com header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then + error "mt.header(Subject) failed" +end +if mt.header(conn, "Authentication-Results", "wrong-auth-serv-id;\n dkim=pass header.d=yad.onmicrosoft.com header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then + error "mt.header(Subject) failed" +end +if mt.header(conn, "Authentication-Results", "my-auth-serv-id;\n exota=pass") ~= nil then + error "mt.header(Subject) failed" +end +if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=yad.onmicrosoft.comx header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then + error "mt.header(Subject) failed" +end +if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=Chillout2k.de header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then + error "mt.header(Subject) failed" +end +if mt.header(conn, "Authentication-Results", "my-auth-serv-id;\n dkim=fail header.d=yad.onmicrosoft.com header.s=selector2-asdf header.b=mmmjFpv8") ~= nil then + error "mt.header(Subject) failed" +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 + error "mt.header(Subject) failed" +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 +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 + +if not mt.eom_check(conn, MT_HDRADD, "X-ExOTA-Authentication-Results") then + mt.echo("no header added") +else + mt.echo("X-ExOTA-Authentication-Results header added") +end + +-- DISCONNECT +mt.disconnect(conn) \ No newline at end of file