Postfix to GMail Catchall (updated… again)

The other day, my wife asked me if I could just redirect all mails going to her own domain to her GMail account. “Easy task”, foolish past-me thought, not knowing the standards you have to meet to actually forward a mail from somwhere to GMail

I naively searched for a simple bouncing method and postfix’s virtual tables seemed perfectly fit for the task (from man virtual):

The main applications of virtual aliasing are:

       o      To redirect mail for one address to one or more addresses.

       o      To  implement  virtual  alias  domains  where  all addresses are
              aliased to addresses in other domains.

So here we go and:

$ pwd
/etc/postfix
$ grep ^virtual main.cf
virtual_alias_maps = hash:/etc/postfix/virtual
$ cat virtual
@herdomain.com  heruser@gmail.com
$ sudo postmap virtual

Easy! let’s send an email from another domain just to validate everything works correctly:

Diagnostic-Code: smtp; 550-5.7.26 This message does not have authentication
    information or fails to 550-5.7.26 pass authentication checks. To best
    protect our users from spam, the 550-5.7.26 message has been blocked.
    Please visit 550-5.7.26
    https://support.google.com/mail/answer/81126#authentication for more 550
    5.7.26 information. b11si14737855wrg.755 - gsmtp

wtf?
Oh, I see… the DKIM key doesn’t match because the mail is originally from otherdomain.com which has a DKIM entry, but is delivered by my own mail server, which also have a DKIM entry, and obviously they don’t match. This post which explains how to rewrite DKIM in the header did not leave me a good / clean feeling, so let’s simply try and remove the DKIM header!

This is easily done by the header_checks postfix feature, basically this:

$ grep ^header_checks main.cf
header_checks = regexp:/etc/postfix/header_checks
$ cat header_checks 
/^DKIM-Signature:.*/    IGNORE

will get rid of the previous DKIM signature, let’s try it… and same error. But yes, no DKIM-Signature: header was found on the transmitted email.

The headers might help to understand what’s going on:

Return-Path: <heruser@otherdomain.com>
Received: by senate.imil.net (Postfix, from userid 1000)
	id 60AB84EA91; Tue, 21 Dec 2021 16:58:21 +0100 (CET)
Received: from mail-4325.protonmail.ch (mail-4325.protonmail.ch [185.70.43.25])
	by senate.imil.net (Postfix) with ESMTPS id 168844E610
	for <test@herdomain.com>; Tue, 21 Dec 2021 16:58:21 +0100 (CET)
Date: Tue, 21 Dec 2021 15:58:18 +0000
To: "test@herdomain.com" <test@herdomain.com>
From: Test User <heruser@otherdomain.com>
Reply-To: Test User <heruser@otherdomain.com>

Maybe bouncing directly from virtual is not a so good idea considering Google’s strict policy on mail transfer.
OK then, let’s try a good old fashioned .forward based forwarding, that’s a good trick! I’ll simply change the virtual to a local user who’s going to have a .forward file in his home directory, containing the final destination address:

$ cat virtual
@herdomain.com  heruser
$ cat /home/heruser/.forward
heruser@gmail.com

Still no luck. Same message, yet more steps in the headers:

Return-Path: <heruser@otherdomain.com>
Received: by senate.imil.net (Postfix)
	id 9BEB84E5C9; Tue, 21 Dec 2021 17:20:36 +0100 (CET)
Delivered-To: heruser@home.imil.net
Received: by senate.imil.net (Postfix, from userid 1003)
	id 9596E4EA91; Tue, 21 Dec 2021 17:20:36 +0100 (CET)
Received: from mail-4318.protonmail.ch (mail-4318.protonmail.ch [185.70.43.18])
	by senate.imil.net (Postfix) with ESMTPS id 7BDCC4E5C9
	for <test@herdomain.com>; Tue, 21 Dec 2021 17:20:36 +0100 (CET)
Date: Tue, 21 Dec 2021 16:20:35 +0000
To: "test@herdomain.com" <test@herdomain.com>
From: Test User <heruser@otherdomain.com>
Reply-To: Test User <heruser@otherdomain.com>

At this point I don’t understand why SPF didn’t match, considering that this next solution does work: procmail to the rescue!
So instead of a .forward file, I used a very simple .procmailrc:

MAILDIR=$HOME/Maildir/
DEFAULT=$HOME/Maildir/
LOGFILE=$HOME/log/procmail.log

:0c
! heruser@gmail.com

What this does is forwarding mails to heruser@gmail.com , but while at it, also copies the email locally for if she wants to use a independent, free MUA one day… so let’s remove the .forward file and try this setup.

Delivered-To: heruser@gmail.com
Received: by 2002:a05:6a10:cd05:0:0:0:0 with SMTP id gw5csp3514080pxb;
        Tue, 21 Dec 2021 08:49:01 -0800 (PST)
ARC-Authentication-Results: i=1; mx.google.com;
       spf=pass (google.com: domain of heruser@home.imil.net designates 2001:bc8:234c:1::1 as permitted sender) smtp.mailfrom=heruser@home.imil.net;
       dmarc=fail (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=protonmail.com
Return-Path: <heruser@home.imil.net>
Received: from senate.imil.net (senate.imil.net. [2001:bc8:234c:1::1])
        by mx.google.com with ESMTP id s9si9509084wrw.307.2021.12.21.08.49.00
        for <heruser@gmail.com>;
        Tue, 21 Dec 2021 08:49:01 -0800 (PST)
Received-SPF: pass (google.com: domain of heruser@home.imil.net designates 2001:bc8:234c:1::1 as permitted sender) client-ip=2001:bc8:234c:1::1;
Authentication-Results: mx.google.com;
       spf=pass (google.com: domain of heruser@home.imil.net designates 2001:bc8:234c:1::1 as permitted sender) smtp.mailfrom=heruser@home.imil.net;
       dmarc=fail (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=protonmail.com
Received: by senate.imil.net (Postfix, from userid 1013) id ABFAA4EA90; Tue, 21 Dec 2021 17:49:00 +0100 (CET)
X-Original-To: heruser@home.imil.net
Delivered-To: heruser@home.imil.net
Received: by senate.imil.net (Postfix, from userid 1003) id 9F4814EA91; Tue, 21 Dec 2021 17:49:00 +0100 (CET)
Received: from mail-40140.protonmail.ch (mail-40140.protonmail.ch [185.70.40.140]) by senate.imil.net (Postfix) with ESMTPS id 85BD24E5C9 for <test@herdomain.com>; Tue, 21 Dec 2021 17:49:00 +0100 (CET)
Date: Tue, 21 Dec 2021 16:48:57 +0000
To: "test@herdomain.com" <test@herdomain.com>
From: Test User <heruser@otherdomain.com>
Reply-To: Test User <heruser@otherdomain.com>

I removed the unnecessary headers, but here we are, the mail went through, and for a reason I still not get, SPF passed this time. Kinda victory.

What’s the magic behind the use of procmail? I’m not sure. It’s a process spawned by postfix on behalf of the user, a whole new mail sending process, the Return-Path: is set to the current user and origin domain but should this affect the delivery mechanism? I would have thought the .forward mechanism would have done a good job but I was wrong.

Have you got a better idea, or a clear explanation? Leave it in the comments!

Update

In my previous solution, only the SPF test passed because the From: field was still referencing otherdomain.com, so as the purpose of this catchall is really to receive mails from third parties for confirmations, there’s no need for replies, so I simply rewrote the From: field with a domain handled by my server, heruser@herdomain.com:

$ cat .procmailrc
MAILDIR=$HOME/Maildir/
DEFAULT=$HOME/Maildir/
LOGFILE=$HOME/log/procmail.log

:0fhw
| formail -i "From: heruser@herdomain.com"

:0c
! heruser@gmail.com

And there we go, SPF and DKIM are now green:

SPF:	PASS with IP 2001:bc8:234c:1:0:0:0:1
DKIM:	'PASS' with domain herdomain.com

Update 2

Just in case she actually needs to reply to a forwarded mail, I slightly modified the procmailrc file to force add a Reply-To: header pointing to the original From::

SENDER=`formail -xFrom:`

:0fhw
| formail -i "From: heruser@herdomain.com" -i "Reply-To: $SENDER"

Update 3

In order to catch MAILER-DAEMON messages before it creates a loop and fills your hard drive with carbon copies cough cough, add this rule as the first one:

:0
* ^From.*MAILER-DAEMON.*
.mailer-daemon/