diff --git a/core/admin/mailu/internal/views/postfix.py b/core/admin/mailu/internal/views/postfix.py index 21a4aa91..76d33fca 100644 --- a/core/admin/mailu/internal/views/postfix.py +++ b/core/admin/mailu/internal/views/postfix.py @@ -3,6 +3,7 @@ from mailu.internal import internal import flask import re +import srslib @internal.route("/postfix/domain/") @@ -39,6 +40,38 @@ def postfix_transport(email): return flask.jsonify("smtp:[{}]".format(relay.smtp)) +@internal.route("/postfix/recipient/map/") +def postfix_recipient_map(recipient): + """ Rewrite the envelope recipient if it is a valid SRS address. + + This is meant for bounces to go back to the original sender. + """ + srs = srslib.SRS(flask.current_app.config["SECRET_KEY"]) + if srslib.SRS.is_srs_address(recipient): + try: + return flask.jsonify(srs.reverse(recipient)) + except srslib.Error as error: + return flask.abort(404) + return flask.abort(404) + + +@internal.route("/postfix/sender/map/") +def postfix_sender_map(sender): + """ Rewrite the envelope sender in case the mail was not emitted by us. + + This is for bounces to come back the reverse path properly. + """ + srs = srslib.SRS(flask.current_app.config["SECRET_KEY"]) + domain = flask.current_app.config["DOMAIN"] + try: + localpart, domain_name = models.Email.resolve_domain(sender) + except Exception as error: + return flask.abort(404) + if models.Domain.query.get(domain_name): + return flask.abort(404) + return flask.jsonify(srs.forward(sender, domain)) + + @internal.route("/postfix/sender/login/") def postfix_sender_login(sender): localpart, domain_name = models.Email.resolve_domain(sender) diff --git a/core/admin/requirements-prod.txt b/core/admin/requirements-prod.txt index e61eebfd..1fcffbcd 100644 --- a/core/admin/requirements-prod.txt +++ b/core/admin/requirements-prod.txt @@ -41,6 +41,7 @@ redis==3.2.1 six==1.12.0 socrate==0.1.1 SQLAlchemy==1.3.3 +srslib==0.1.4 tabulate==0.8.3 tenacity==5.0.4 validators==0.12.5 diff --git a/core/admin/requirements.txt b/core/admin/requirements.txt index c68130db..d0515b52 100644 --- a/core/admin/requirements.txt +++ b/core/admin/requirements.txt @@ -22,3 +22,4 @@ tenacity mysqlclient psycopg2 idna +srslib diff --git a/core/postfix/conf/main.cf b/core/postfix/conf/main.cf index 0790946d..8b4b11ac 100644 --- a/core/postfix/conf/main.cf +++ b/core/postfix/conf/main.cf @@ -75,6 +75,12 @@ relay_domains = ${podop}transport transport_maps = ${podop}transport virtual_transport = lmtp:inet:{{ LMTP_ADDRESS }} +# Sender and recipient canonical maps, mostly for SRS +sender_canonical_maps = ${podop}sendermap +sender_canonical_classes = envelope_sender +recipient_canonical_maps = ${podop}recipientmap +recipient_canonical_classes= envelope_recipient,header_recipient + # In order to prevent Postfix from running DNS query, enforce the use of the # native DNS stack, that will check /etc/hosts properly. lmtp_host_lookup = native @@ -120,4 +126,3 @@ milter_default_action = tempfail ############### # Extra Settings ############### - diff --git a/core/postfix/start.py b/core/postfix/start.py index 7523bb4d..12aaa770 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -21,6 +21,8 @@ def start_podop(): ("alias", "url", url + "alias/§"), ("domain", "url", url + "domain/§"), ("mailbox", "url", url + "mailbox/§"), + ("recipientmap", "url", url + "recipient/map/§"), + ("sendermap", "url", url + "sender/map/§"), ("senderaccess", "url", url + "sender/access/§"), ("senderlogin", "url", url + "sender/login/§") ])