Repository: Mailu/Mailu

Issue:
Support for SRS
See https://en.wikipedia.org/wiki/Sender_Rewriting_Scheme and https://github.com/roehling/postsrsd


Discussion:
That would be an interesting feature for aliases and forwards indeed. No idea how soon we could support it though.
It would greatly diminish the risk of the forwarding MTA to be marked as Spammer.
Is this still valid, especially in conjunction with DKIM/DMARC?

Edit: https://fastmail.blog/2016/12/24/spf-dkim-dmarc/

> The solution: always make sure you DKIM sign mail if you have a DMARC policy. If your email is forwarded, SPF will break, but DKIM signatures should survive. SRS wonʼt help with DMARC, because replacing the MAIL FROM envelope with your own domain means the MAIL FROM domain doesnʼt match the From header domain. This is an alignment failure, and so not a pass result for DMARC.

Seems this is superseded by DMARC
@kaiyou in #1042 you posted some review comments. @jbmolle are you able to proceed with this? I would like to help if possible as this is currently a problem for an alias I need to use.
A completely different approach would be to use a podop map and generate the srs in the admin container instead of running postsrs. It has the advantage of being compatible with replicated setups.

If @jbmolle can adjust the pr, we can probably merge it. Otherwise I'd go for the podop map instead. How do you feel about contributing it if I try and guide you through it?
@kaiyou I would like to try. It’s a new area for me though and it could take some time to catch up. I will try and setup the dev environment while waiting for an answer of @jbmolle.
Hi @oli-ver, hi @kaiyou ,
Sorry for the late reply. I haven't looked much at this issue. 
The modifications I did were enough for the use case I had so far.
But I can look at it again and try to modify the PR according to kaiyou's suggestions. 
I don't write much code in Python so I might not be the best to do but I can try and you'll let me know if it's fine.
@jbmolle I have some experience with python but none with mailu development but I am open to review code and discuss solutions. @kaiyou, what do you think?
I planned on setting up a nee dev environment tonight. Let's try and make this my first issue.

Postfix sender and recipient canonical maps should be bound to admin through podop, and rewriting will be implemented using pysrs from https://pythonhosted.org/milter/pysrs.html as it seems not to be the best but most supported lib. 

Resolving patch:
diff --git a/core/admin/mailu/internal/views/postfix.py b/core/admin/mailu/internal/views/postfix.py
--- a/core/admin/mailu/internal/views/postfix.py
+++ b/core/admin/mailu/internal/views/postfix.py
@@ -3,6 +3,7 @@
 
 import flask
 import re
+import srslib
 
 
 @internal.route("/postfix/domain/<domain_name>")
@@ -39,6 +40,38 @@ def postfix_transport(email):
     return flask.jsonify("smtp:[{}]".format(relay.smtp))
 
 
+@internal.route("/postfix/recipient/map/<path:recipient>")
+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/<path:sender>")
+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/<path:sender>")
 def postfix_sender_login(sender):
     localpart, domain_name = models.Email.resolve_domain(sender)
diff --git a/core/postfix/start.py b/core/postfix/start.py
--- 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/§")
     ])


Test patch: