mirror of
https://github.com/chillout2k/ExOTA-Milter.git
synced 2025-12-14 02:30:17 +00:00
Compare commits
2 Commits
e876609848
...
1dc0c8d5d9
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dc0c8d5d9 | |||
| 507ab6d241 |
@ -80,6 +80,11 @@ Authentication-Results: trusted.dkim.validating.relay;
|
|||||||
[...]
|
[...]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## More authentic with DKIM alignment and message forwarding
|
||||||
|
From the point of view of a postmaster, message forwarding is a kind of nightmare. If DKIM alignment is enabled the DKIM SDID (Signers Domain ID = `header.d` field of *Authentication-Results* header) must be equivalent to the RFC5322.from_domain. In this mode the **EXOTA-Milter** operates in the most secure way, but with limitations in terms of usability. With DKIM alignment enabled [traditional email forwarding](https://docs.microsoft.com/de-de/microsoft-365/admin/email/configure-email-forwarding?view=o365-worldwide) cannot be guaranteed. In this case the exchange online system preserves the original RFC5322.from header and signs the forwarded email with the main tenants SDID, e.g. *tenantdomain.onmicrosoft.com*. An email that was forwarded in that way cannot pass the DKIM alignment, because the RFC5322.from_domain will never match the DKIM SDID. Further there is no policy for the RFC5322.from_domain!
|
||||||
|
|
||||||
|
Nevertheless, don´t put your head in the sand, there is a way out of this dilemma! Just use outlook rules to forward messages, which is described [here](https://support.microsoft.com/en-us/office/use-rules-to-automatically-forward-messages-45aa9664-4911-4f96-9663-ece42816d746). In this case the original content gets forwarded within a new message, that carries the correct RFC5322.from as well as the correct DKIM SDID! Messages forwarded in such way will always pass DKIM alignment -> mission accomplished! Don´t forget to tell your end users ;)
|
||||||
|
|
||||||
## X-MS-Exchange-CrossTenant-Id header (policy binding)
|
## X-MS-Exchange-CrossTenant-Id header (policy binding)
|
||||||
Further each Microsoft Exchange-Online tenant has a unique tenant-ID in form of a UUID ([RFC 4122](https://tools.ietf.org/html/rfc4122)). **ExOTA-Milter** extracts the tenant-ID from the *X-MS-Exchange-CrossTenant-Id* email header and uses it as a *mandatory* authentication factor.
|
Further each Microsoft Exchange-Online tenant has a unique tenant-ID in form of a UUID ([RFC 4122](https://tools.ietf.org/html/rfc4122)). **ExOTA-Milter** extracts the tenant-ID from the *X-MS-Exchange-CrossTenant-Id* email header and uses it as a *mandatory* authentication factor.
|
||||||
```
|
```
|
||||||
|
|||||||
@ -33,6 +33,19 @@ if (Policy found?) then (yes)
|
|||||||
:REJECT;
|
:REJECT;
|
||||||
stop
|
stop
|
||||||
endif
|
endif
|
||||||
|
if (Milter: DKIM alignment checking enabled?) then (yes)
|
||||||
|
if (Policy has DKIM-alignment enabled?) then (yes)
|
||||||
|
:Checking if DKIM-signature is aligned;
|
||||||
|
note left: DKIM SDID must equal to RFC5322.from_domain!
|
||||||
|
if (Is DKIM aligned?) then (yes)
|
||||||
|
else (no)
|
||||||
|
:REJECT;
|
||||||
|
stop
|
||||||
|
endif
|
||||||
|
else (no)
|
||||||
|
endif
|
||||||
|
else (no)
|
||||||
|
endif
|
||||||
else (no)
|
else (no)
|
||||||
endif
|
endif
|
||||||
else (no)
|
else (no)
|
||||||
|
|||||||
@ -353,10 +353,17 @@ class ExOTAMilter(Milter.Base):
|
|||||||
"/EOM: No aligned DKIM signatures found!"
|
"/EOM: No aligned DKIM signatures found!"
|
||||||
)
|
)
|
||||||
if g_milter_dkim_alignment_required:
|
if g_milter_dkim_alignment_required:
|
||||||
return self.smfir_reject(
|
if policy.is_dkim_alignment_required() == False:
|
||||||
queue_id = self.getsymval('i'),
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
reason = 'DKIM alignment required!'
|
"/EOM: Policy overrides DKIM alignment requirement to '{0}'!".format(
|
||||||
)
|
policy.is_dkim_alignment_required()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return self.smfir_reject(
|
||||||
|
queue_id = self.getsymval('i'),
|
||||||
|
reason = 'DKIM alignment required!'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
logging.info(self.mconn_id + "/" + str(self.getsymval('i')) +
|
||||||
"/EOM: No valid DKIM authentication result found"
|
"/EOM: No valid DKIM authentication result found"
|
||||||
|
|||||||
@ -16,7 +16,15 @@ class ExOTAPolicyInvalidException(ExOTAPolicyException):
|
|||||||
class ExOTAPolicy():
|
class ExOTAPolicy():
|
||||||
def __init__(self, policy_dict):
|
def __init__(self, policy_dict):
|
||||||
self.tenant_id = policy_dict['tenant_id']
|
self.tenant_id = policy_dict['tenant_id']
|
||||||
self.dkim_enabled = policy_dict['dkim_enabled']
|
if 'dkim_enabled' in policy_dict:
|
||||||
|
self.dkim_enabled = policy_dict['dkim_enabled']
|
||||||
|
else:
|
||||||
|
self.dkim_enabled = True
|
||||||
|
if 'dkim_alignment_required' in policy_dict:
|
||||||
|
self.dkim_alignment_required = policy_dict['dkim_alignment_required']
|
||||||
|
else:
|
||||||
|
# DKIM alignment per policy enabled by default
|
||||||
|
self.dkim_alignment_required = True
|
||||||
|
|
||||||
def get_tenant_id(self):
|
def get_tenant_id(self):
|
||||||
return self.tenant_id
|
return self.tenant_id
|
||||||
@ -24,31 +32,42 @@ class ExOTAPolicy():
|
|||||||
def is_dkim_enabled(self):
|
def is_dkim_enabled(self):
|
||||||
return self.dkim_enabled
|
return self.dkim_enabled
|
||||||
|
|
||||||
|
def is_dkim_alignment_required(self):
|
||||||
|
return self.dkim_alignment_required
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_policy(policy_dict):
|
def check_policy(policy_dict):
|
||||||
if 'tenant_id' not in policy_dict:
|
if 'tenant_id' not in policy_dict:
|
||||||
raise ExOTAPolicyInvalidException(
|
raise ExOTAPolicyInvalidException(
|
||||||
"Policy must have a 'tenant_id' attribute!"
|
"Policy must have a 'tenant_id' key!"
|
||||||
)
|
)
|
||||||
else:
|
for policy_key in policy_dict:
|
||||||
try:
|
if policy_key == 'tenant_id':
|
||||||
UUID(policy_dict['tenant_id'])
|
try:
|
||||||
except ValueError as e:
|
UUID(policy_dict[policy_key])
|
||||||
|
except ValueError as e:
|
||||||
|
raise ExOTAPolicyInvalidException(
|
||||||
|
"Invalid 'tenant_id': {0}".format(str(e))
|
||||||
|
) from e
|
||||||
|
except Exception as e:
|
||||||
|
raise ExOTAPolicyInvalidException(
|
||||||
|
"Invalid 'tenant_id': {0}".format(traceback.format_exc())
|
||||||
|
) from e
|
||||||
|
elif policy_key == 'dkim_enabled':
|
||||||
|
if not isinstance(policy_dict[policy_key], bool):
|
||||||
|
raise ExOTAPolicyInvalidException(
|
||||||
|
"'dkim_enabled'({0}) must be boolean!".format(policy_dict['dkim_enabled'])
|
||||||
|
)
|
||||||
|
elif policy_key == 'dkim_alignment_required':
|
||||||
|
if not isinstance(policy_dict[policy_key], bool):
|
||||||
|
raise ExOTAPolicyInvalidException(
|
||||||
|
"'dkim_alignment_required'({0}) must be boolean!".format(
|
||||||
|
policy_dict[policy_key]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
raise ExOTAPolicyInvalidException(
|
raise ExOTAPolicyInvalidException(
|
||||||
"Invalid 'tenant_id': {0}".format(str(e))
|
"Invalid policy_key '{0}'!".format(policy_key)
|
||||||
) from e
|
|
||||||
except Exception as e:
|
|
||||||
raise ExOTAPolicyInvalidException(
|
|
||||||
"Invalid 'tenant_id': {0}".format(traceback.format_exc())
|
|
||||||
) from e
|
|
||||||
if 'dkim_enabled' not in policy_dict:
|
|
||||||
raise ExOTAPolicyInvalidException(
|
|
||||||
"Policy must have a 'dkim_enabled' attribute!"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if not isinstance(policy_dict['dkim_enabled'], bool):
|
|
||||||
raise ExOTAPolicyInvalidException(
|
|
||||||
"'dkim_enabled'({0}) must be boolean!".format(policy_dict['dkim_enabled'])
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class ExOTAPolicyBackend():
|
class ExOTAPolicyBackend():
|
||||||
|
|||||||
@ -30,7 +30,7 @@ if mt.getreply(conn) ~= SMFIR_CONTINUE then
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- HEADER
|
-- HEADER
|
||||||
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@yad.onmicrosoft.comx>') ~= nil then
|
if mt.header(conn, "fRoM", '"Blah Blubb" <O365ConnectorValidation@yad.onmicrosoft.com>') ~= nil then
|
||||||
error "mt.header(From) failed"
|
error "mt.header(From) failed"
|
||||||
end
|
end
|
||||||
if mt.header(conn, "resent-fRoM", '"Blah Blubb" <blah@yad.onmicrosoft.COM>') ~= nil then
|
if mt.header(conn, "resent-fRoM", '"Blah Blubb" <blah@yad.onmicrosoft.COM>') ~= nil then
|
||||||
@ -51,7 +51,7 @@ end
|
|||||||
if mt.header(conn, "Authentication-Results", "my-auth-serv-id;\n exota=pass") ~= nil then
|
if mt.header(conn, "Authentication-Results", "my-auth-serv-id;\n exota=pass") ~= nil then
|
||||||
error "mt.header(Subject) failed"
|
error "mt.header(Subject) failed"
|
||||||
end
|
end
|
||||||
if mt.header(conn, "Authentication-RESULTS", "my-auth-serv-id;\n dkim=pass header.d=yad.onmicrosoft.com-blubb header.s=selector1-yad-onmicrosoft-com header.b=mmmjFpv8") ~= nil then
|
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"
|
error "mt.header(Subject) failed"
|
||||||
end
|
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
|
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
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"yad.onmicrosoft.com": {
|
"yad.onmicrosoft.com": {
|
||||||
"tenant_id": "1234abcd-18c5-45e8-88de-123456789abc",
|
"tenant_id": "1234abcd-18c5-45e8-88de-123456789abc",
|
||||||
"dkim_enabled": true
|
"dkim_enabled": true,
|
||||||
|
"dkim_alignment_required": true
|
||||||
},
|
},
|
||||||
"example.com": {
|
"example.com": {
|
||||||
"tenant_id": "abcd1234-18c5-45e8-88de-987654321cba",
|
"tenant_id": "abcd1234-18c5-45e8-88de-987654321cba",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user