Configurig the postfix MTA to securely forward to a smarthost on macOS
June 24, 2020 1 Comment
macOS ships with postfix but it is in a semi-disabled state. The launch daemon configuration provided doesn’t work and postfix will immediately exit
What I want is a working local MTA that forwards mail securely to a smarthost for delivery. This is mostly useful when building and testing scripts and server applications that need to send mail. It is convenient to have the default MTA localhost:25 be in a working state.
Here’s the goal:
- accept smtp connections on localhost:25 from localhost without credentials
- relay mail (for my domain) to a smart host that has a static IP and a reputation that will make delivery possible
- don’t have my credentials or mail snooped or intercepted
- hopefully not be blocked by ISPs and middleboxes
I am using Amazon SES for my smarthost, but it could be gmail, outlook.com, or a corporate server. The details will be a little different depending on the smarthost.
I’m using Amazon SES in the us-east region: mail-smtp.us-east-1.amazonaws.com.
My configuration is for macOS Catalina with MacPorts as my package manager and Amazon SES as my smarthost relay. The details are slightly different but the concepts are the same for other package managers and or Linux.
Secure tunneling to a smarthost
Many ISPs and corporate networks will filter, intercept, or otherwise interfere with SMTP connections. They can also interfere with SMTP with StartTLS. With StartTLS, the connection is initially plaintext and gets upgraded to TLS if the server reports it as the capability. This connection type is vulnerable to a downgrade attack called StripTLS which prevents the encryption from being negotiated.
I have found that SMTPS or SMTP over SSL where the client first establishes an TLS connection and then performs SMTP commands and mail transfer through the resulting TLS tunnel is the most reliable, secure connection type.
Unfortunately many MTAs — including Postfix bundled with macOS and Ubuntu — do not support SMTPS natively. My solution to this problem is to use stunnel
to negotiate the SMTPS and map the remote smarthost to a local port, then configure Postfix to forward its mail there.
sudo port install stunnel
sudo vi /opt/local/etc/stunnel/stunnel.conf
#foreground = yes #[ses-tls-wrapper] #accept = 2525 client = yes connect = email-smtp.us-east-1.amazonaws.com:465
When running stunnel
from a command-line to test things out you would want to uncomment the lines that are commented. But put the comments back in for running as a launch agent.
Then I create a launch agent configuration to start the stunnel connection whenever port localhost:2525 is requested, emulating classic inetd. The idea is that launchd
listens on localhost:2525 and when it receives a connection will start stunnel and connect it to the port, but otherwise stunnel is not running. Launchd is the init process, so it is always running. On Linux, you would use a systemd unit or OpenRC script to do the same thing.
sudo vi /Library/LaunchAgents/org.macports.stunnel.plist
<plist version="1.0"> <dict> <key>Label</key> <string>org.macports.stunnel</string> <key>Program</key> <string>/opt/local/bin/stunnel</string> <key>Sockets</key> <dict> <key>Listeners</key> <dict> <key>SockNodeName</key> <string>localhost</string> <key>SockServiceName</key> <string>2525</string> </dict> </dict> <key>inetdCompatibility</key> <dict> <key>Wait</key> <false/> </dict> </dict> </plist>
sudo launchctl load /Library/LaunchAgents/org.macports.stunnel.plist
Now my smarthost at Amazon SES is connected securely to my localhost port 2525 on demand.
Configuring postfix
I need to authenticate to SES, so I need a passwd database.
cd /etc/postfix sudo mkdir sasl # for SES, the username and password are AWS API key ID and value echo "email-smtp.us-east-1.amazonaws.com:465 my-aws-key-id:my-aws-key-value" | sudo tee passwd # now make a postfix database file sudo hashmap passwd # now there should be a plaintext passwd file and a postfix passwd.db file
Now we need to set up postfix to relay any authenticate to SES on localhost:2525 by editing /etc/postfix main.cf
.
At the end of the main.cf file we need something like this:
# your authorized domain, this may need to be edited somewhere farther up in main.cf mydomain = brianreiter.org inet_interfaces = loopback-only relayhost = localhost:2525 smtp_sasl_auth_enable = yes smtp_sasl_security_options = noanonymous smtp_sasl_password_maps = hash:/etc/postfix/sasl/passwd
If postfix were running, we would do sudo postfix reload
at this point.
Finally we can set up a launch daemon for postfix to get it running as a service. I used to just edit the launch daemon configuration provided by Apple to get postfix working but as of High Sierra that required disabling SIP and as of Catalina the file became part of the read-only system partition.
We need to create and load a launch daemon file in /Library/LaunchDaemons
where we have read/write permissions.
sudo vi /Library/LaunchDaemons/org.postfix.master.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>org.postfix.master</string> <key>Program</key> <string>/usr/libexec/postfix/master</string> <key>ProgramArguments</key> <array> <string>master</string> </array> <key>QueueDirectories</key> <array> <string>/var/spool/postfix/maildrop</string> </array> <key>AbandonProcessGroup</key> <true/> <key>KeepAlive</key> <true/> </dict> </plist>
sudo launchctl load /Library/LaunchDaemons/org.postfix.master.plist
Viola, I can now use my local MTA to send mail and this works from almost anywhere.
Now, assuming that you are able to make an outbound connection to port 465, the authentication to the smarthost is correct, and that your domain is authorized with the smarthost, etc. things should be working.
If you want to use /usr/bin/mail
you will need a valid mydomain
in master.cf
and possibly also aliases, see postfix documentation for details.
Excellent explanation, thank you.