From a1da4daa4c30668d98325cebd640ae3cf2f5fc97 Mon Sep 17 00:00:00 2001 From: Florent Daigniere Date: Tue, 31 Aug 2021 20:24:06 +0200 Subject: [PATCH] Implement the DANE-only lookup policyd https://github.com/Snawoot/postfix-mta-sts-resolver/issues/67 for context --- core/admin/mailu/internal/views/postfix.py | 3 +++ core/admin/mailu/utils.py | 24 +++++++++++++++++++++- core/postfix/conf/main.cf | 2 +- core/postfix/start.py | 1 + 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/core/admin/mailu/internal/views/postfix.py b/core/admin/mailu/internal/views/postfix.py index 2e7d0b9b..330fed5b 100644 --- a/core/admin/mailu/internal/views/postfix.py +++ b/core/admin/mailu/internal/views/postfix.py @@ -7,6 +7,9 @@ import idna import re import srslib +@internal.route("/postfix/dane/") +def postfix_dane_map(domain_name): + return flask.jsonify('dane-only') if utils.has_dane_record(domain_name) else flask.abort(404) @internal.route("/postfix/domain/") def postfix_mailbox_domain(domain_name): diff --git a/core/admin/mailu/utils.py b/core/admin/mailu/utils.py index 02150754..914638fa 100644 --- a/core/admin/mailu/utils.py +++ b/core/admin/mailu/utils.py @@ -6,6 +6,9 @@ try: except ImportError: import pickle +import dns +import dns.resolver + import hmac import secrets import time @@ -25,7 +28,6 @@ from itsdangerous.encoding import want_bytes from werkzeug.datastructures import CallbackDict from werkzeug.contrib import fixers - # Login configuration login = flask_login.LoginManager() login.login_view = "ui.login" @@ -37,6 +39,26 @@ def handle_needs_login(): flask.url_for('ui.login', next=flask.request.endpoint) ) +# DNS stub configured to do DNSSEC enabled queries +resolver = dns.resolver.Resolver() +resolver.use_edns(0, 0, 1500) +resolver.flags = dns.flags.AD | dns.flags.RD + +def has_dane_record(domain, timeout=5): + try: + result = resolver.query(f'_25._tcp.{domain}', dns.rdatatype.TLSA,dns.rdataclass.IN, lifetime=timeout) + if (result.response.flags & dns.flags.AD) == dns.flags.AD: + for record in result: + if isinstance(record, dns.rdtypes.ANY.TLSA.TLSA): + record.validate() + if record.usage in [2,3]: # postfix wants DANE-only + return True + except dns.resolver.NoNameservers: + # this could be an attack / a failed DNSSEC lookup + return True + except: + pass + # Rate limiter limiter = limiter.LimitWraperFactory() diff --git a/core/postfix/conf/main.cf b/core/postfix/conf/main.cf index ae54326d..16fdfa6e 100644 --- a/core/postfix/conf/main.cf +++ b/core/postfix/conf/main.cf @@ -60,7 +60,7 @@ smtp_tls_mandatory_protocols = !SSLv2, !SSLv3 smtp_tls_protocols =!SSLv2,!SSLv3 smtp_tls_security_level = {{ OUTBOUND_TLS_LEVEL|default('dane') }} smtp_tls_dane_insecure_mx_policy = {% if DEFER_ON_TLS_ERROR == 'false' %}may{% else %}dane{% endif %} -smtp_tls_policy_maps=hash:/etc/postfix/tls_policy.map, socketmap:unix:/tmp/mta-sts.socket:postfix +smtp_tls_policy_maps=hash:/etc/postfix/tls_policy.map, ${podop}dane, socketmap:unix:/tmp/mta-sts.socket:postfix smtp_tls_CApath = /etc/ssl/certs smtp_tls_session_cache_database = lmdb:/dev/shm/postfix/smtp_scache smtpd_tls_session_cache_database = lmdb:/dev/shm/postfix/smtpd_scache diff --git a/core/postfix/start.py b/core/postfix/start.py index 69855e29..03dca93c 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -21,6 +21,7 @@ def start_podop(): run_server(0, "postfix", "/tmp/podop.socket", [ ("transport", "url", url + "transport/§"), ("alias", "url", url + "alias/§"), + ("dane", "url", url + "dane/§"), ("domain", "url", url + "domain/§"), ("mailbox", "url", url + "mailbox/§"), ("recipientmap", "url", url + "recipient/map/§"),