Ubuntu – Forward email but change the FROM address

emailmailxpostfixUbuntu

I have a postfix server running on an EC2 instance. I want to forward all email, via SES, to my personal inbox.

The problem: AWS only allows a FROM address that is verified in the AWS console and the FROM address in this case could be anything, for example: twitter.com. I cannot white-list my server's IP and say: "Accept all emails from this location regardless of sender" (would be a bad idea anyway)

So, I need to figure out a way to forward my email with a verified address but I do not want to lose the original sender's address.

Is there a way of doing this?

Best Answer

Based on our discussion in chat, I'm going to provide you my hackish, customized solution that will change the "FROM" address as we expect it to be, and then deliver to the original destination point but add the "Reply-To" header.

This is a very hackish approach, but should manipulate the messages as you expect before actually sending them via PostFix to where they need to go.

First, PostFix ports need to be changed. We need to change the Postfix SMTP port to something other than 25 so that our python SMTP handler we are going to set up will work on that port instead.

Edit /etc/postfix/master.cf. You're going to be looking for a line like this:

smtp      inet  n       -       y       -       -       smtpd

Comment out this line, and underneath that line, use this instead:

10025      inet  n       -       y       -       -       smtpd

This tells Postfix that we don't want it to listen on the standard SMTP port. Restart the postfix service when you're done with this step.


Next, the Python SMTP handler which I mentioned above. This will handle incoming messages, manipulate them, and resend them to the PostFix on your system. Assuming, of course, that all mail is submitted on port 25, even locally.

This code exists on a GitHub GIST and is based off of a generic Python SMTP server code example I got somewhere (but don't remember from where sorry!), and have since manipulated.

The code is also here, it's in Python 3 in case you're curious, and is written with Python 3 as the target Python version:

#!/usr/bin/env python3

# Libraries
import smtplib
import smtpd
import asyncore
import email
import sys
from datetime import datetime

print('Starting custom mail handling server...')

# We need to know where the SMTP server is heh.
SMTP_OUTBOUND = 'localhost'

# We also need to know what we want the "FROM" address to be
FROM_ADDR = "foo@bar.baz"
DESTINATION_ADDRESS = "foo@bar.baz"

#############
#############
# SMTP SERVER
#############
#############


# noinspection PyMissingTypeHints,PyBroadException
class AutoForwardHandlerSMTP(smtpd.SMTPServer):

    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        print('MESSAGE RECEIVED - [%s]' % datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
        print('Receiving message from:', peer)
        print('Message addressed from:', mailfrom)
        print('Message addressed to  :', rcpttos)
        print('Message length        :', len(data))
        print(data)

        # Flush the output buffered (handy with the nohup launch)
        sys.stdout.flush()

        # Analyze and extract data headers
        msg = email.message_from_string(data)
        orig_from = ''
        try:
            orig_from = msg['From']
            msg['Reply-To'] = orig_from
            # We have to use 'replace header' methods to overwrite existing headers.
            msg.replace_header("From", FROM_ADDR)
        except:
            print("Error manipulating headers:", sys.exc_info()[0])

        conn = smtplib.SMTP(SMTP_OUTBOUND, 10025)
        conn.sendmail(FROM_ADDR, msg["To"], msg.as_string())

        # Flush the output buffered (handy with the nohup launch)
        print("\n\n")
        sys.stdout.flush()
        return


# Listen to port 25 ( 0.0.0.0 can be replaced by the ip of your server but that will work with 0.0.0.0 )
server = AutoForwardHandlerSMTP(('0.0.0.0', 25), None)

# Wait for incoming emails
asyncore.loop()

Store this as /opt/PythonAutoForwarderSMTP.py, or whatever you want to call it. Run it with the following as root (either via sudo or by dropping into a root user prompt), first, to make sure it works as we expect:

python3 /opt/PythonAutoForwarderSMTP.py

Once it's confirmed running, send an email through the server. It should be picked up and give you log data from this script that a message was received and processed. You should also then see a connection on Postfix's logs, and this being delivered somewhere after Postfix. If all of this looks OK, and you process the message properly and see it with a different 'From' address wherever the mail message finally ends up, then we can work to get it to autostart now! (You can simply hit Ctrl + C to close out the python process, before continuing).

Assuming we want it to start at boot, then we need to set it up to do so.

As root, run crontab -e, and add the following to the root crontab:

@reboot /usr/bin/python3 /opt/PythonAutoForwarderSMTP.py 2>&1 >> /var/log/PythonSMTP.log &

Save the crontab file. If you don't want to reboot your server, then execute the command line you just added, minus the @reboot part, to run the Python SMTP handler.

Whether run by cron or not, the process that loads the Python will end up forked into the background, and also put all data output (error or otherwise in the Python console) to a log file in /var/log/PythonSMTP.log in Append mode. That way, you can always get logs as you need to.

If all works as expected, this will properly add a Reply-To header, and also adjust the "From" header in the message to be what we expect it to be. I can't guarantee this'll work properly for SPF and DKIM checking, if messages are signed, but I can say that this will properly 'preprocess' messages before using Postfix to relay them elsewhere.


OBLIGATORY Security Concerns and Functional Change Notifications:

  • Sender DKIM verification may fail. DKIM signature verification fails whenever messages that are signed are manipulated, which means you might have broken DKIM signatures from senders. That means things might get picked up as spam due to failed signature verification. This script can probably be customized to 'work' properly, but I didn't write this to do DKIM/SPF checking.
  • We must run this Python SMTP server as root. This is necessary because in Linux by default we can't bind to ports under 1024 unless we are superuser; this is why Postfix has a master 'root' owned process and does sub-processes that don't run as the root user for very long only to port-bind.
  • ALL mail on port 25 will end up going through this Python SMTP server. If Postfix also handles mail from outside->in, then the Python SMTP server will be taking its place. This can introduce some evils, but ultimately does what you're asking for.
  • This is a fragile solution. While it's not as fragile as some other solutions, if the Python process dies, it doesn't come back up automatically, so you have to handle errors on a case-by-case basis and sometimes bring the Python process back to life if it dies off completely.
  • There are no StartTLS or SSL/TLS handlers in this. So everything is Plain Text (which is insecure!)

As always, you should not run anything as root unless you know what you're doing. In this case, I provide the code for this in plain view so you can discern for yourself what this script does, and whether you want to run it as root or not, if you are security-centric and paranoid like I am (I am an IT Security professional as well as a sysadmin, so forgive these obligatory notices)

Related Question