Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement SRS for forwarded mail #2418

Open
alvinhochun opened this issue Mar 10, 2019 · 33 comments
Open

Implement SRS for forwarded mail #2418

alvinhochun opened this issue Mar 10, 2019 · 33 comments

Comments

@alvinhochun
Copy link

(Previously discussed in #110 and #1189)

Is your feature request related to a problem? Please describe.
Forwarded mail will fail SPF on receiving end.

Describe the solution you'd like
I would like to have SRS implemented for forwarded mail.

Additional context
I've used postsrsd in the past and it worked without issues. Though its README does mention that using sender_canonical_maps will cause the rewriting to happen even if the mail is not forwarded at all, which could be an issue to some other users.

In order to test postsrsd again, I've set it up on a new testing mail server running mailcow-dockerized. I built and installed postsrsd on the host, changed its config with the correct mail domain and have it listen on 172.22.1.1. main.cf was changed like this (which I've probably done poorly):

diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf
--- a/data/conf/postfix/main.cf
+++ b/data/conf/postfix/main.cf
@@ -117,8 +117,11 @@ virtual_mailbox_base = /var/vmail/
 virtual_mailbox_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_domains_maps.cf
 recipient_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf
 sender_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf
-recipient_canonical_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf
-recipient_canonical_classes = envelope_recipient
+sender_canonical_maps = tcp:172.22.1.1:10001
+sender_canonical_classes = envelope_sender
+recipient_canonical_maps = tcp:172.22.1.1:10002,
+  proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf
+recipient_canonical_classes = envelope_recipient, header_recipient
 virtual_mailbox_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf
 virtual_minimum_uid = 104
 virtual_transport = lmtp:inet:dovecot:24

(I'll skip the wrestling with firewalld.)

I did some quick tests (setting forwarding via Roundcube to Gmail and checking the raw message there) and it seems to work fine. But this is a pretty hackish setup and I don't know if everything works (probably breaks apart when docker starts/stops).

(Btw I tried getting a bounce intentionally by forwarding to a non-existent user somewhere (with or without SRS) and the non-delivery notification ends up being sent to the original sender, which is probably very bad as it not only spams the original senders but also leaks the forwarding address. Perhaps this should be in a separate issue.)

@andryyy
Copy link
Contributor

andryyy commented Mar 10, 2019

I am waiting for Rspamd to implement it.

@alvinhochun
Copy link
Author

How much better is having it in Rspamd than using a simple postsrsd setup? (Rspamd is probably way more flexible with scripting but I wonder how necessary this is.)

Also, I just realized that BCC maps too does forwarding, but unlike the other forwarding (redirect through sieve filter) the forwarded mail doesn't show up in Rspamd's history (the other did) and that bounce non-delivery notification doesn't get sent to the original sender. Postsrsd still does SRS rewrite with my test setup, but what if it's implemented in Rspamd but the forwarded mail doesn't go through it?

@extremeshok
Copy link
Contributor

This might help you out : rspamd/rspamd#1789

@andryyy
Copy link
Contributor

andryyy commented Mar 11, 2019

BCC maps are not meant to be used for forwarding at all btw.

@stale
Copy link

stale bot commented May 10, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the dunno label May 10, 2019
@stale stale bot closed this as completed May 17, 2019
@andryyy andryyy reopened this May 17, 2019
@stale stale bot removed the dunno label May 17, 2019
@kwisatz
Copy link

kwisatz commented Jun 25, 2019

So what's the gist of this thread? We'll wait for rspamd/rspamd#1789 to implement it?
IMHO there's no way around SRS if you're for instance sending mail to an alias that forwards it to your own mailserver which will reject the mail that claims to be from you but is from the forwarding server rather than a SASL-authenticated user.

@andryyy
Copy link
Contributor

andryyy commented Jun 25, 2019

You can implement it to Rspamd or help Vsevo with it.

There are also sieve redirects.

@leifnel
Copy link

leifnel commented Oct 27, 2019

I'd like to see this implemented too

@ghost
Copy link

ghost commented Oct 30, 2019

Same here @leifnel , seeing as rspamd/rspamd#1789 is closed and has the labels "bug" and "wontfix" it seems that rspamd won't be implementing this.

Is there anyone that has an alternative way to achieve this?
With postsrsd for example, as the initial message explains.
Or through sieve redirects as @andryyy suggested?

I've been unable to find the right instructions to get this sorted.
If anyone is willing to help, I'll be glad to receive some advice.

@TiiFuchs
Copy link
Contributor

TiiFuchs commented Nov 4, 2021

I stumbled upon this issue now, since I have a few mail aliases to forward externally.

I'm somehow surprised that mailcow is not able to do SRS. :/
I think it's kinda important. So here's a little push.

Until then: Any ways to workaround this?

@PAStheLoD
Copy link

@TiiFuchs so depending on how much around working you can put up with, SRS Milter might work for you. (If I remember correctly I picked this because it was relatively the most up-to-date and straightforward to setup and configure.)

@TiiFuchs
Copy link
Contributor

TiiFuchs commented Dec 8, 2021

@PAStheLoD Do you have an example docker-compose.yml and maybe configuration files for SRS Milter so I have something to begin with?

@PAStheLoD
Copy link

@TiiFuchs

Sorry, no docker-compose. I'm using it with ISPConfig. So just a VM + Postfix + SRS Milter in Docker. There's not much to configure either. (I use the docker run command from the README. Except for the domain name it's identical. And simply modify postfix's main.cf, add the milter to the list of milters using the smtpd_milters variable. I added it at the end of the list.)

Does this help you get started?

@TiiFuchs
Copy link
Contributor

I configured it accordingly, but it does not seem to work. I don't get any output or errors either :/
Absolutely no idea what goes wrong here. :(

@TiiFuchs
Copy link
Contributor

It seems that srs-milter rewrites mails that gets delivered to a local mail account, but not if the mail gets redirected to another external mail account... I don't understand why...?

@TiiFuchs
Copy link
Contributor

It seems that it's not working with mails redirected via a configured alias in the mailcow admin.
But it seems too, that you don't need SRS, if you configure it (like it is described on the alias section in mailcow admin) as sieve filter via SOGo Filter settings. 🤔

So I'm fairly confused now, tbh.

@PAStheLoD
Copy link

I'm not familiar with the inner workings of mailcow, but if the milter setting is in the postfix config you can tcpdump the traffic between the milter and postfix.

[...] you don't need SRS, if you configure it (like it is described on the alias section in mailcow admin) as sieve filter via SOGo Filter settings

So it's not needed because there's some component that implements a similar rewrite functionality via sieve? :o How? :0

@TiiFuchs
Copy link
Contributor

As far as my tests go, the mails redirected via sieve filter sends it with headers as if it were sent from your mailcow mailbox. So SPF passes, since it checks if your mailserver is eligible for sending mails for YOUR domain, not that other freemail domain the original mail was from. I'm not that deep in the matter, so I don't know for sure what it is.

I tested it from gmx.de -> my mailcow instance -> gmail, and gmail shows my mailbox in X-Sieve-Redirected-From, Delivered-To, To Headers. And it is listed in the ARC-Authentication-Results, Received and Authentication-Results Headers...

@kwisatz
Copy link

kwisatz commented Mar 14, 2023

Coming back to this, seeing how I already posted in 2019. Frustrating to be 4 years later and still have the same issue without a good solution.

I found this blog-post here https://nowhere.dk/articles/implementing-srs-with-mailcow/ which dates from 2020 already, but it seems adapted to mailcow-dockerized, so I'll probably be giving this a go. Or alternatively try the same using srs-milter instead of postsrsd.

I'm not entirely happy with the way srs is handled through that regex in the blogpost though. I can see how it simplifies things, but it would be much better if somehow we could pass the mail through postsrsd / the milter every time there are non-local domains in the goto/alias list.

EDIT: This original post (from 2017) contains an example where the recipient domains that need rewriting aren't based on the regex, but rather just look at the list of local domains using an SQL query. IMHO that is the correct way to go about this.

Since then, postsrsd has been released in a newer version that brings support for the milter protocol, rather than only the canonicalization protocol. Unfortunately, looking at the postsrsd config file it doesn't seem to bring any support for lookup maps.

All in all, I'm afraid this won't be a quick fix-up and likely turn into a week-end project. Our users will have to bear with the current situation for a little while longer.

@snevas
Copy link

snevas commented Mar 14, 2023

As far as my tests go, the mails redirected via sieve filter sends it with headers as if it were sent from your mailcow mailbox. So SPF passes, since it checks if your mailserver is eligible for sending mails for YOUR domain, not that other freemail domain the original mail was from. I'm not that deep in the matter, so I don't know for sure what it is.

I tested it from gmx.de -> my mailcow instance -> gmail, and gmail shows my mailbox in X-Sieve-Redirected-From, Delivered-To, To Headers. And it is listed in the ARC-Authentication-Results, Received and Authentication-Results Headers...

Seconded. SPF succeeds because the sieve filter redirect updates the Return-Path. Other mail forwarders do not do this and cause SPF to fail.

@kwisatz
Copy link

kwisatz commented Mar 14, 2023

The sieve approach also seems to be recommended looking at a thread about postsrsd. Still, I think we'll need both solutions since the sieve approach will not (AFAIK) work for aliases.

I just wanted to add a small thought I had about this earlier this week:

I feel like, if mailcow does not currently support SRS out of the box, then maybe aliases should be restricted to local mail only? The question to ask here of course being: What is the lesser evil? Not allowing to use external domains for aliases or allowing it, but failing to make people aware that it will most likely not work (with a majority of email providers today).

At the very least a note on the alias form, informing people that this is not guaranteed to work if using external domains as targets would be a good thing to add, don't you agree?

@kwisatz
Copy link

kwisatz commented Mar 19, 2023

I think I have a set-up that works. At least my latest tests suggest it does.

I'll write this up another time though, giving that it's already quite late and I haven't had dinner yet and I don't want to just dump my unsorted, raw notes on you.

Maybe @andryyy can let us know what the initial idea was behind using extra.cf because I will have a question about that.

@kwisatz
Copy link

kwisatz commented Mar 20, 2023

Disclaimer

The original post here below is not correct and needs updating. In the meantime, please see my post below.

Original Post

OK, here goes nothing! Disclaimer: I am not a postfix export, far from it. What I'll supply here will be a gist of what I did as well as a series of (rhetorical?) questions. I am planning on creating an even more detailed post on this once I find the time and have hopefully received or found answers to some of my questions.

As I outlined before, I didn't want to go the regexp/pcre route because our customers would not understand or remember that.
My goal was to have all emails that are sent from an external address and go back out to an external address be routed through postsrsd.

In postfix terminology, that means selecting a specific transport depending on the recipient. For local addresses (sender or receiver), we don't need to route the mail through postsrsd, but we do for external addresses (sender and recipients).

The first step is to add a container for postsrsd to the compose project by adding another service to your docker-compose.override.yml file:

version: '2.1'
services:
 
  […]

  postsrsd-mailcow:
    image: ajoergensen/postsrsd:latest
    restart: always
    environment:
      - SRS_DOMAIN=mail.mymx.lu
      - SRS_SECRET=************
    networks:
      mailcow-network:
        ipv4_address: ${IPV4_NETWORK:-172.22.1}.42

You can generate the secret any way you want, e.g. openssl rand -base64 32.
Then run docker compose up -d to pull the image and spin up the container.

All the files that I'll mention from here on can be found on your host system at /opt/mailcow-dockerized/data/conf/postfix/. All proxy:mysql files are inside its sql/ subdirectory.

We'll start by visiting extra.cf. The reason I'm asking what the initial idea was, is because, well, while there is an extra.cf so that mailcow can modify main.cf without risk of losing customisations, the same isn't true for master.cf. And… even though the idea is good, it's not entirely practical, we'll see why in a bit.

In extra.cf, we'll add, resp. override the following settings:

## For postsrsd
## In order to disable postsrsd, just comment out the following two blocks and restart postfix-mailcow!
## There is also config in master.cf, but it shouldn't interfere without these config lines here

## postsrsd's reverse service is listening on port 10002
sender_canonical_classes = envelope_sender
recipient_canonical_maps = tcp:172.22.1.42:10002, proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf
recipient_canonical_classes = envelope_recipient, header_recipient

## Also for postsrsd, we override the default transport maps to use the smtpd on port 10029 for all non-local recipients
transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre,
  pcre:/opt/postfix/conf/local_transport,
  proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_non-local_srs.cf

First of all, we tell postfix to check with the service running on 172.22.1.42 port 10002 if a recipient address needs to be rewritten. This part is new. The mysql_recipient_canonical_maps.cf part is already included with mailcow and allows to rewrite certain addresses by configuring it, on an address by address basis from the web UI. The service running on IP address 172.22.1.42:10002 is postsrsd's reverse service. (I am, however, not entirely sure if we even need this configuration.)

The second part is overriding the transport_maps setting and is specific to how I want this to work. As I said, we want all external addresses to go through postsrsd, so here we're adding (main.cf already contains a transport_maps setting) the following line proxy:mysql:/opt/postfix/conf/sql/mysql_non-local_srs.cf which defines a config file that holds the SQL query necessary to determine whether or not to map to a transport (aka re-route the message).

user = mailcow
password = **********
hosts = unix:/var/run/mysqld/mysqld.sock
dbname = mailcow
query = SELECT IF(EXISTS(SELECT domain FROM domain WHERE domain = '%d' UNION SELECT alias_domain FROM alias_domain WHERE alias_domain = '%d'), NULL, 'smtp:127.0.0.1:10029') AS 'transport'

The query simply checks whether the recipient domain is a local domain. If a result is found (exists), then we return null, else we return the address of the transport we want the mail to be routed to, which is smtp:127.0.0.1:10029, another instance of the smtpd process.

And where we define that process is in master.cf (It doesn't matter where you put these lines, but I put them between the whitelist_fwd and the watchdog configuration):

# SRS config
# DR: We use a mysql query for sender_maps to ensure we do not rewrite for local senders
cleanup-srs unix  n       -       -       -       0       cleanup
  -o sender_canonical_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_local_senders.cf,tcp:172.22.1.42:10001
  -o sender_canonical_classes=envelope_sender
# DR: I don't see what the use of that is
#  -o recipient_canonical_maps=regexp:/opt/postfix/conf/regex_sender_canonical_srs
# DR: Could I define this in main.cf?
# DR: proxy:mysql:... must be listed in proxy_read_maps in main.cnf

# Only non-local recipients should end up here per our transport map in extra.cf
127.0.0.1:10029 inet    n       -       -       -       -       smtpd
  -o cleanup_service_name=cleanup-srs
  -o smtpd_tls_security_level=none
  -o content_filter=smtp:
  -o smtpd_recipient_restrictions=permit_mynetworks,reject
  -o smtpd_milters=

I left my comments in to illustrate two points that we'll get to in a moment. First, what we see here is that we're asking postfix to start two processes when it starts up. One smtpd on port 10029 and one cleanup service named cleanup-srs. The smtpd is what we've returned from our SQL query to the transport_maps config setting.

The cleanup service is "cleaning up" headers. And the important bit here is the sender_canonical_maps setting. We pass two parameters, another SQL query as well as the IP and port to postsrsd's forward service. The purpose of the proxy:mysql entry is to filter out any sender addresses that are local since they do not need rewriting. The contents of mysql_local_senders.cf thus looks like this:

user = mailcow
password = ********************
hosts = unix:/var/run/mysqld/mysqld.sock
dbname = mailcow
query = SELECT '%s' FROM alias WHERE domain = '%d'

We're basically just returning a local address if the domain exists locally. Meaning, we're rewriting the original sender address to itself. The reason why querying the alias table is enough is that it also contains entries for mailboxes. mailcow uses a similar trick to what we did here. It always check the alias table to find the goto mailbox or external address. If we don't find the domain locally, then the cleanup service will use the next entry, which is the postsrsd forward service.

In essence this is all that there is to it, but, on to my comments:

  1. The blog post I have linked to above passes an additional recipient_canonical_maps option to the cleanup service. I don't see why that would be necessary or what it would achieve. Maybe someone can shed some light on this?
  2. When I had everything in place, restarted the postfix container and sent my first test-email, I came across the following message in the postfix logs:
Mar 19 21:13:10 139f3df59ee5 postfix/proxymap[379]: warning: request for unapproved table: "mysql:/opt/postfix/conf/sql/mysql_local_senders.cf"
Mar 19 21:13:10 139f3df59ee5 postfix/proxymap[379]: warning: to approve this table for read-only access, list proxy:mysql:/opt/postfix/conf/sql/mysql_local_senders.cf in main.cf:proxy_read_maps
Mar 19 21:13:10 139f3df59ee5 postfix/cleanup[414]: fatal: proxymap service is not configured for table "mysql:/opt/postfix/conf/sql/mysql_local_senders.cf"

This is easily solved by doing just that, adding it to the proxy_read_maps parameter, but this list is long and overriding it in extra.cf risks missing updates done to it, just as much as changing it in main.cf risks it getting overwritten by an update:

proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_local_senders.cf,
  $sender_dependent_default_transport_maps,
  $smtp_tls_policy_maps,
 […]
  $smtp_sasl_password_maps

However, after restarting the postfix container once more, everything was working for me. To test this, I created an alias that forwards emails to an external address. I then sent an email from the same domain to that alias and it was correctly received and accepted by the external MX.
I could also see that the return-path header was correctly modified

Return-Path: <[email protected]>

Problems

The problems that remain are those of the risk of main.cfor master.cf getting overwritten by a future mailcow update.

Feedback? / Questions?

I'd love some feedback on this. E.g. on the sql queries if you think that there's a better way to do them, on the various files and on how to handle the proxy_read_maps parameter, as well as on whether or not the recipient_canonical_maps parameters are required both in extra.cf and in master.cf, passing it to the cleanup service.

Also, if you have questions about my approach, if something isn't clear, please let me know.

Edit May 23rd 2023

If you pulled a new container after May 21st 2023, the configuration needs to be changed to adapt to a new postsrsd version.

In a nutshell, these changes are required:

extra.cf

+ sender_canonical_maps = socketmap:inet:172.22.1.42:10003:forward
  […]
  recipient_canonical_maps = 
- tcp:172.22.1.42:10002,
+ socketmap:inet:172.22.1.42:10003:reverse, 
  proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf

master.cf

  cleanup-srs unix  n       -       -       -       0       cleanup
-   -o sender_canonical_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_local_senders.cf,tcp:172.22.1.42:10001
+   -o sender_canonical_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_local_senders.cf,socketmap:inet:172.22.1.42:10003:forward
    -o sender_canonical_classes=envelope_sender

For more information, see these threads and @ajoergensen's original, updated blog post

@TiiFuchs
Copy link
Contributor

@kwisatz I love that! Thanks for all the effort!

@kwisatz
Copy link

kwisatz commented May 23, 2023

@andryyy did you have a chance to check my post above? Any ideas about the problems I mention, any feedback from your side?

Also, who maintains the list of third party integrations here https://docs.mailcow.email/third_party/borgmatic/third_party-borgmatic/ and how?

@cjwalsh
Copy link

cjwalsh commented Jun 17, 2023

@kwisatz thanks for this great write up.

It looks like this is SRS rewriting the local source address for any outgoing email which is sent from the local domain as well as anything external which is being forwarded by my Sieve filter. While this isn't preventing delivery, it feels like this is not really correct behaviour, so how can the configuration be updated accordingly do you think?

Also, can this solution be combined with using a smarthost relay for the SRS domain I'm using? Configuring that domain in the Mailcow UI to use a sender-dependent transport doesn't seem to do the trick.

@kwisatz
Copy link

kwisatz commented Jun 19, 2023

@kwisatz thanks for this great write up.

It looks like this is SRS rewriting the local source address for any outgoing email which is sent from the local domain as well as anything external which is being forwarded by my Sieve filter. While this isn't preventing delivery, it feels like this is not really correct behaviour, so how can the configuration be updated accordingly do you think?

It shouldn't be doing that. If it is, then we need to figure out what is wrong about the config.
The following configuration is supposed to prevent postsrsd from rewriting addresses on mails sent from local domains:

cleanup-srs unix  n       -       -       -       0       cleanup
  -o sender_canonical_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_local_senders.cf,socketmap:inet:172.22.1.42:10003:forward

with mysql_local_senders.cf containing:

user = mailcow
password = ********************
hosts = unix:/var/run/mysqld/mysqld.sock
dbname = mailcow
query = SELECT '%s' FROM alias WHERE domain = '%d'

This query inside mysql_local_senders.cf is supposed to match and return the original domain, thus preventing the cleanup-srs service from ever querying postsrsd about a rewrite address.

@cjwalsh can you give an example of which addresses are being rewritten and how? Do your logs maybe show anything interesting about cleanup-srs or the SQL query that is run (but not matching)? (You might have to enable verbose logging within postfix).

Also, can this solution be combined with using a smarthost relay for the SRS domain I'm using? Configuring that domain in the Mailcow UI to use a sender-dependent transport doesn't seem to do the trick.

So what you mean is that after an external domain has been rewritten, it should get routed (transported) towards the smarthost?

With the current config that indeed won't work, because transports are queried before we even get to the SRS stuff:

transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre,
  pcre:/opt/postfix/conf/local_transport,
  proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_non-local_srs.cf

I'm thinking you should be able to switch out both config files, making it look up the non-local srs config first. But this would also require that the smtpd on port 10029 reroute the mail back into the main smtpd process, so that the transport_maps can get re-checked after the address has been rewritten. I'm not sure if that currently the case. We'd have to test that or maybe someone else can shed some light on this.

@cjwalsh
Copy link

cjwalsh commented Jun 22, 2023

@kwisatz I found the issue, the sender addresses were being rewritten by the default cleanup service, because I had added the sender_canonical_maps entry into extra.cf based on your update following the new version of postsrsd. This effectively meant that it was calling the SRS forwarder for all senders.

For clarity, this is what the postsrsd entries in my extra.cf ended up looking like for the working configuration:

data/conf/postfix/extra.cf

## For postsrsd
## In order to disable postsrsd, just comment out the following two blocks and restart postfix-mailcow!
## There is also config in master.cf, but it shouldn't interfere without these config lines here

## postsrsd's reverse service is listening on port 10002
sender_canonical_classes = envelope_sender
recipient_canonical_maps = socketmap:inet:172.22.1.42:10003:reverse, proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf
recipient_canonical_classes = envelope_recipient, header_recipient

## Also for postsrsd, we override the default transport maps to use the smtpd on port 10029 for all non-local recipients
transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre,
  pcre:/opt/postfix/conf/local_transport,
  proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf,
  proxy:mysql:/opt/postfix/conf/sql/mysql_non-local_srs.cf

Also for reference, here is the section from master.cf. Note I added values for syslog_name to help with the detailed logging when I was trying to work out what was happening.

data/conf/postfix/master.cf

# SRS config
# DR: We use a mysql query for sender_maps to ensure we do not rewrite for local senders
cleanup-srs unix  n       -       -       -       0       cleanup
  -o sender_canonical_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_local_senders.cf,socketmap:inet:172.22.1.42:10003:forward
  -o sender_canonical_classes=envelope_sender
# DR: I don't see what the use of that is
#  -o recipient_canonical_maps=regexp:/opt/postfix/conf/regex_sender_canonical_srs
# DR: Could I define this in main.cf?
# DR: proxy:mysql:... must be listed in proxy_read_maps in main.cnf
  -o syslog_name=cleanup-srs

# Only non-local recipients should end up here per our transport map in extra.cf
127.0.0.1:10029 inet    n       -       -       -       -       smtpd
  -o cleanup_service_name=cleanup-srs
  -o smtpd_tls_security_level=none
  -o content_filter=smtp:
  -o smtpd_recipient_restrictions=permit_mynetworks,reject
  -o smtpd_milters=
  -o syslog_name=srs

Once I got that resolved I then found that the Sieve filter for redirecting to my @gmail.com address wasn't using the SRS redirect, and it appears that this is because Dovecot in Mailcow seems to be configured in a non-default way for the sieve_redirect_envelope_from setting to use the final recipient address as the sender/return-path address for the forwarded copy of the email, so it was using a local address for the sender and hence not doing the SRS address rewrite.

This configuration can be changed, and it seems that the safest way to override the setting is by creating an extra.conf file in the data/conf/dovecot folder with the following setting:

data/conf/dovecot/extra.conf

plugin {
    sieve_redirect_envelope_from = sender
}

For the smarthost relaying, I was wondering if the smtpd settings in master.cf above for the SRS non-local recipients could be extended to specify the relayhost settings. I'm going to play around with this and see where I get to 😃

By the way, I think it is already routing the messages back from the SRS smtpd on port 10029 over to the main Postfix smtpd for delivery based on what I see in the logs, but at that point it is just doing direct delivery to the target MX server. I think because it had a match in transport_maps then it doesn't check with the sender_dependent_default_transport_maps options for example.

@pgassmann
Copy link

What's the current status of this? Is SRS working with this additional service and configuration? Or is there still a conflict with rspamd?
Are there drawbacks to implementing SRS? Could/should this be integrated by default? If so, what's blocking this?

@kwisatz
Copy link

kwisatz commented Aug 10, 2023

@pgassmann I'm not aware of any conflicts with rspamd.
As to why this is not the default (or a checkbox within the mailcow UI), you'll need to talk to @andryyy and team…
Right now this is only a howto, but with some feedback it could become a pull request including options and UI controls.

@promasu
Copy link

promasu commented Oct 12, 2023

I tested this with the current Mailcow version and I don't see any problem with rspamd etc. It just works. I will have a look at this for a longer time in our setup and report back.
I'd really like to see this upstream as this fixes some SPF related problems when using aliases.

@kwisatz
Copy link

kwisatz commented Dec 5, 2023

@cjwalsh I'm now seing a similar behavior to what you described, but I can't say that I fully understand what is going on:

mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:56 ede301d27bd1 postfix/sogo/smtpd[458]: connect from mailcowdockerized-sogo-mailcow-1.mailcowdockerized_mailcow-network[172.22.1.248]
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:56 ede301d27bd1 postfix/sogo/smtpd[458]: D18D030FB06: client=mailcowdockerized-sogo-mailcow-1.mailcowdockerized_mailcow-network[172.22.1.248], sasl_method=PLAIN, [email protected]
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:56 ede301d27bd1 postfix/cleanup[459]: D18D030FB06: replace: header Received: from 56273ad8fbc2 (mailcowdockerized-sogo-mailcow-1.mailcowdockerized_mailcow-network [172.22.1.248])??(Authenticated sender: [email protected])??by mail.mx.tld (Postcow) with ESMTPA id D18D0 from mailcowdockerized-sogo-mailcow-1.mailcowdockerized_mailcow-network[172.22.1.248]; from=<[email protected]> to=<[email protected]> proto=ESMTP helo=<56273ad8fbc2>: Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPA id D18D030FB06??for <[email protected]>; Tue,  5 Dec 2023 15:53:56 +0100 (CET)
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:56 ede301d27bd1 postfix/cleanup[459]: D18D030FB06: message-id=<5a-656f3980-7-62280700@210026301>
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:57 ede301d27bd1 postfix/qmgr[383]: D18D030FB06: from=<[email protected]>, size=902, nrcpt=1 (queue active)
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:57 ede301d27bd1 postfix/sogo/smtpd[458]: disconnect from mailcowdockerized-sogo-mailcow-1.mailcowdockerized_mailcow-network[172.22.1.248] ehlo=1 auth=1 mail=1 rcpt=1 data=1 quit=1 commands=6
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:57 ede301d27bd1 smtpd-srs/smtpd[456]: connect from localhost[127.0.0.1]
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:57 ede301d27bd1 smtpd-srs/smtpd[456]: 2AE2830FB13: client=localhost[127.0.0.1]
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:57 ede301d27bd1 cleanup-srs/cleanup[457]: 2AE2830FB13: message-id=<5a-656f3980-7-62280700@210026301>
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:57 ede301d27bd1 smtpd-srs/smtpd[456]: disconnect from localhost[127.0.0.1] ehlo=1 mail=1 rcpt=1 data=1 quit=1 commands=5
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:57 ede301d27bd1 postfix/smtp[455]: D18D030FB06: to=<[email protected]>, relay=127.0.0.1[127.0.0.1]:10029, delay=0.46, delays=0.4/0.01/0/0.04, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 2AE2830FB13)
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:57 ede301d27bd1 postfix/qmgr[383]: 2AE2830FB13: from=<[email protected]>, size=1491, nrcpt=1 (queue active)
mailcowdockerized-postfix-mailcow-1  | Dec  5 15:53:57 ede301d27bd1 postfix/qmgr[383]: D18D030FB06: removed

The from is already rewritten as the first, default instance of postfix/cleanup[459] receives it from the SoGo container. (Line 3 above.) Long before it reaches our postsrsd set-up.
But how and when? Could it be SoGO itself just SRS'ing everything? (Not it's not, I'm seeing the same when sending from another MUA)

BTW, you also mentioned that it won't hurt delivery, but it can. I just had such a case where we got a 550 - Sender verify failed because it verified the sender domain against mx.tld (which does indeed itself not have an MX record since that domain does not normally send or receive mail).

@kwisatz
Copy link

kwisatz commented Dec 25, 2024

@cjwalsh ha, it only took me 2 years to finally understand the problem and come up with the same solution. It finally dawned on me what was wrong after I had solved an issue involving OpenDKIM on another mail server which sort of had the same origin.

What finally made it click for me was the following FAQ from the postsrsd's README:

Can I configure PostSRSd so it will only rewrite the envelope sender if the email is not delivered locally?

This is not supported currently but might be added to the milter at some point in the future.

If PostSRSd is integrated with Postfix using the canonical maps, it is almost impossible, because the canonicalization occurs before any routing decision is made. Only if you happen to use separate Postfix server instances for forwarding and local delivery, you can trivially configure PostSRSd this way.

Which finally made me realize that the sender_canonical maps should not be set on the default smtpd, but only on the cleanup service one. I needed to visualize the flow in my head and I tried reproducing this here, should another me come along having the same mental blockage.

Untitled-2024-03-25-1446

In a few words: The transport maps decide whether an email should go to the smtpd on port 10029 that uses postsrsd as its cleanup service. Since the sender_canonical_maps config option applies unconditionally, the condition is in the transport_maps and only if the transport-map query matches will the specific smtpd be used that has the sender_canonical_maps option set. Which is why the configuration option has to go into the master.cf and not the extra.cf (main.cf) file where it would apply to all smtpd instances. (Please CMIIW).

Note: Of course, the diagram above is super-simplified, see this page for an overview over how the different postfix services fit together…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests