In February 2013, I wrote about replacing SMTPS (SMTP + SSL/TLS) with a local MTA, and this week, I finally managed to create a solution of my own. It's called SSH-SMTP, and it's available in my GitHub repository under MIT license. It should compile at least on Linux, Mac, and Windows, and any other OS that supports Qt. I chose C++ and Qt because it's been a while since I did anything in C++ and Qt offers a powerful signaling solution that could be used in this scenario.
The core idea was to accept SMTP connections, extract the sender from the
MAIL FROM
command, and proxy the connection to the appropriate SMTP server
over SSH. I started with a QTcpServer example on Qtforum.org, and
added a QProcess to handle the SSH connection.
When a new client connects, the newConnection
signal of QTcpServer
is
fired, and the proxy sends a standard SMTP greeting. When data arrives from
the MUA, the readyRead
signal of QTcpSocket
is fired, and at first, the
proxy looks for HELO
, EHLO
and MAIL FROM
commands. The first two are
answered by a simple reply, while latter is used to determine the sender.
Although some say that QRegExp is slow, I used it since it's used only
once per connection, and it fits better into Qt code (for example, it uses
QStrings parameters and return values).
The extracted value is used as a lookup value, and I chose QSettings to
store it, as it's pretty easy to use, and abstracts away OS-specific ways of
persistence (for example, it uses text files on Unix-like systems, and
Registry on Windows). If a valid mapping is found, the appropriate SSH
command is invoked, connecting to the remote server. By default, ssh
and
nc
is used, but these can be overridden using QSettings as well.
After the connection to the remote SMTP server over SSH has opened, all
traffic previously received from the MUA is transmitted to get the two parties
sychronized. This also means that the replies to these commands must not be
transmitted to the MUA, so the SMTP to MUA forwarder waits for the first
line that starts with "250 "
(250 and a space) and only passes on traffic
received after this line to the MUA.
After this is done, the proxy waits for either the TCP socket or the SSH process output to become readable, and passes data on to the other party. Also, if either one of them closes, the other one is closed as well. I've been using it for two days in production without any problems, and it finally solved both the authentication and the confidentiality problem, as I already have public key-based authentication set up on my SMTP servers, and SSH uses Diffie–Hellman key exchange by default, so I don't have to spend time configuring the TLS listener to implement PFS. Also ,sending e-mails have become significantly faster for me, as I use SSH multiplexing, so sending a new e-mail doesn't require building a new TCP connection and a TLS session above it, followed by password authentication. And as a bonus, headers in my outgoing e-mail won't contain the IP address of my notebook.