Sometimes, an SMTP server is only accessible from a local LAN. If you have a Linux computer and an SSH account inside of this LAN, you can nevertheless tunnel through and send e-mails from anywhere. There are several ways to do this and I found one that is transparent to my current setup.

SSH port forwarding

If you only need this once in a while, you can use SSH port forwarding:

ssh -N -L localhost:8587:the.smtp.server.com:587 user@the.ssh.server.com

This will connect to the SSH server and forward the local port 8587 to the port 587 on the SMTP server. Here, I assume the the server listens on the “submit” port 587. In your case it might also be 25, 465, or something weird. This works even if the SMTP server is not the SSH server, as long as the SSH server can access the SMTP server. You should then configure your e-mail program to use localhost:8587 as the SMTP server. You might have to convince it to accept a bogus SSL certificate.

SSH’s -N option disables the execution of a command or shell: the SSH client will simply forward the port. So stop the connection with Ctrl-C when you’re done.

Automatic SSH tunnel with systemd

This works, but gets annoying. And you have to remember it when you need it, so I decided to use another trick. Systemd supports (x)inetd style on-demand services. This means that systemd itself listens on a network socket until a connection comes in. Then it spawns some process to handle this connection. Luckily for us, the handler process can communicate with systemd via stdin/stdout. So, our handler is

/usr/bin/ssh user@the.ssh.server.com -- nc the.smtp.server.com 587

This will spawn nc on the SSH server, which is a program used to communicate with a network socket via stdin/stdout—just what we need. Careful: The connection must be made without user input, otherwise it will not be automatic. So set up public key authentication for your SSH server.

For systemd, we need two unit files. The first one will tell systemd to open a network socket on demand. It should go into /etc/systemd/system/emailtunnel.socket (replace the name emailtunnel if you want):

[Unit]
Description=Open an SSH connection in order to send an email.

[Socket]
ListenStream=127.0.0.1:8587
# Start one SSH connection per TCP connection to 8587.
Accept=true

[Install]
WantedBy=sockets.target

Then we need a unit that describes the program that will be started to handle the connection. This goes into /etc/systemd/system/emailtunnel@.service (if you changed the name before, you must also change it here):

[Unit]
Description=An SSH tunnel

[Service]
Type=simple
ExecStart=/usr/bin/ssh user@the.ssh.server.com -- nc the.smtp.server.com 587
StandardInput=socket
StandardOutput=socket
StandardError=journal

That’s it. Now type the following commands to enable and start the setup:

systemctl daemon-reload
systemctl enable emailtunnel.socket
systemctl start emailtunnel.socket

This will automatically open the SSH tunnel if someone connects to port 8587 and closes it when they disconnect. Several SSH tunnels can be opened in parallel.

Warning: This will enable anyone with access to the computer to use this SSH tunnel, not only specific users! Sadly, this is unavoidable. Be aware of the dangers if the computer has more than one user, especially with shell access.

Note 1: This part will work with any software that sends e-mail. So if you do not use postfix, stop here and configure your software to use localhost:8587 to send e-mail.

Note 2: If you do not like systemd, I am sure you can replicate this setup using (x)inetd. I have no interest in this, though, so you have to do your own research.

Teach postfix to use this relay

The nice thing about running postfix yourself is that you only need to configure all your e-mail accounts once. After that, you can point Thunderbird and company at your central server. I’ll leave it up to you to secure this one, see the excellent manual for all details.

Access to your postfix server is secured, right? Good, now we can configure it to act as an SMTP client. First, we control which user can use which e-mail address. In postfix’s main config file, set

smtpd_sender_login_maps = hash:/etc/postfix/controlled_envelope_senders

Edit /etc/postfix/controlled_envelope_senders to contain something like this:

user1   user1@example.com
user1   user1-work@example.org
user2   user2@example.net

These users on the left are usually the Unix users on your system. You can set up virtual users just for postfix, refer to the manual for that.

Then, we control which server is used for which e-mail address. Let’s take our setup from above and say that user1@example.com should go through the SSH tunnel. In postfix’s main config file, set

sender_dependent_relayhost_maps = hash:/etc/postfix/sender_relay

The file /etc/postfix/sender_relay should look like this:

user1@example.com       [localhost]:8587

Now we need to set up the user/password. First in the main config:

smtp_sender_dependent_authentication = yes
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd

Create the /etc/postfix/sasl_passwd with permissions 0600 and the contents:

user1@example.com       username:password

This almost works, but it may be that postfix rejects the SSL certificate of the server as invalid, since we “renamed” it to localhost. So, let’s disable this check. (Be sure that the LAN you connect to via SSH is safe from DNS spoofing and eavesdropping!) Add the following to the main config:

smtp_tls_policy_maps = hash:/etc/postfix/tls_policy_per_server, static:secure

This means that anything that is not listed in /etc/postfix/tls_policy_per_server will be subjected to SSL certificate checks. We can now disable this for our case in /etc/postfix/tls_policy_per_server by adding:

[localhost]:8587        may

Now we need to update all the maps for postfix by executing:

postmap /etc/postfix/controlled_envelope_senders
postmap /etc/postfix/sender_relay
postmap /etc/postfix/sasl_passwd
postmap /etc/postfix/tls_policy_per_server

Finally, restart postfix and enjoy!