VSzA techblog

SSH-SMTP as an SMTPS replacement


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.


next posts >
< prev post

Proudly powered by Utterson