Better Qmail on Ubuntu

I make it a goal to do as little custom configuration work as possible. No building from source, no third-party package repositories, and restricting myself to use a core, stable subset of system features when building software. Performing a package or OS upgrade only to find that some bleeding edge feature you were using has changed is frustrating at best, a lurking security hole at worst.

As a result, my preference for Qmail as a MTA may seem odd, given that Qmail is perceived as challenging to configure. However, when it comes to secure, set-and-forget software, Qmail is just about as good as it gets. It’s the Fort Knox of MTAs: highly efficient and designed to be secure. While it may appear a bit difficult to get started, once you understand the way Qmail approaches mail delivery, you’ll never go back to any of the other buggy MTAs.

But for those of us that use Ubuntu, there’s a problem. The Ubuntu package for Qmail, though stable, is somewhat outdated. It lacks several important patches such as the CNAME patch,  which simply makes DNS lookups more efficient. Why is this important? Because if you don’t apply this patch, sites with large DNS records will trigger temporary failures (“CNAME lookup failed temporarily”) until Qmail finally gives up and bounces the outgoing message. There are workarounds (editing the smtproutes configuration, in this case), but they’re tedious and require ongoing oversight.

The list of useful Qmail patches is fortunately rather short, but would require a custom build of Qmail, which in my case is unworkable. I use Puppet to configure all of my servers, so handling custom package builds becomes a more serious challenge, not to mention requiring the entire suite of build tools on the server.

With a just-in-time custom build ruled out, the best remaining option in the Ubuntu ecosystem is to create a Personal Package Archive on Launchpad, Canonical Ltd’s third-party package repository. With a PPA, I can upload my patched Qmail source package, at which point Launchpad will build and host packages that are easily imported into the Ubuntu package manager.

In the interest of simplicity, instead of modifying the original Qmail source, I followed the package manager’s convention and applied the changes as a series of patches. Some applied cleanly, without any additional modification. Others (particularly the Qmail Authentication patch) required extensive rework to accommodate changes from the other patches.

In the end, I added 6 additional patches to Qmail:

  • 0004 – Remove the CNAME lookup (per DJB himself: “It’s safe to simply skip the CNAME lookup: i.e., have dns_cname simply return 0”)
  • 0005 – Handle large DNS responses (would also have solved the above problem – I applied both since the CNAME lookup is no longer useful and is extra work)
  • 0006 – Return “Unrecognized” instead of “Unimplemented” for non-existent SMTP commands
  • 0007 – Screen for and reject email addresses with relay characters
  • 0008 – A host of inbound and outbound SMTP AUTH authentication patches
  • 0009 – Support for external recipient validation via the RCPTCHECK environmental variable

The source is on Github and the packages are built and available via Launchpad; instructions for incorporating them into your Ubuntu install are there as well. I’ve been running the new build in production on a handful of servers and haven’t experienced any problems yet. If you find one though, send a pull request on Github and send me an email. I’ll incorporate it, rebuild, and republish the package.

8 Comments

  1. P

    Why are you doing it yourself? Either use netqmail, or https://qmail.jms1.net/ or http://www.fehcom.de/qmail/qmail.html.

    • Ryan

      A couple reasons:

      • The base netqmail package on Ubuntu is missing the patches I mentioned in the post.
      • Other sites (like jms1 and FEHCom) have source patches but no pre-built packages, which is a requirement for my Puppet-based system configuration approach.

      I considered using more comprehensive patches, but patching Qmail (like any software) comes with the risk of introducing bugs. My goals here were to a) add in the critical functionality I was missing, and b) minimize the likelihood of introducing new bugs or security concerns. Also, many Qmail patch sites are infrequently updated, so tracking down the newest and/or most mature version of each patch took a bit of research. The memoryhole.net Qmail page is the most comprehensive one I’ve found and the easiest to integrate with the netqmail source.

      • Norman

        Ryan:

        Do you have information about how to compile from the github sources and to apply the patches from the unzipped paths as listed? Thanks

  2. Adam

    I’ve been fighting with qmail on OpenBSD and Ubuntu for a couple weeks now and I am very excited to have found your blog post! I added the ppa and installed the qmail package on Bionic. It seems to have worked, but when I run “sudo qmailctl start” it complains about supervise not running (this is the same problem I ran into with the stock qmail package). I don’t see any service in /lib/systemd/system which would start supervise/daemontools, nor do I see any errors in /var/log/syslog. I haven’t found anyone else having this issue when I search online. Any ideas on where to look next?

    Steps to repro (on a fresh bionic install):
    sudo apt install software-properties-common
    sudo add-apt-repository -y ppa:rbuterbaugh/netqmail
    sudo apt-get update
    sudo apt-get -y install qmail
    sudo qmailctl start

    • Ryan

      Hi Adam,

      I believe this is a bug with runit – the /var/lib/supervise directory is no longer being created as part of the package install. There are two options here:

      1. Install daemontools-run instead (prior to installing qmail), since it’s an alternative supervisor for qmail and appears to be working properly
      2. Create the /var/lib/supervise directory manually (prior to installing qmail)

      I’ll update the package to prefer daemontools over runit for the time being, until the runit bug is fixed.

      Ryan

      • Adam

        Fix confirmed. Now it’s working like a champ. Thank you for the help!

  3. Adam

    Do you have any pointers on how to enable SMTP auth?

    From reading the patch, it’s obvious checkpassword (or the compatible checkpw) needs to be installed, I want to make sure PAM supports CRAM-MD5, the invocation of “qmail-smtpd” needs to be changed, and an environment variable added.

    I did the obvious “apt-get install -y checkpw” but I don’t know where to go next. I think the “libgsasl7” package will provide the desired CRAM-MD5 support (but I don’t know how to test this). As for changing the invocation and environment variables… I’m not really sure where to go. I can handle inetd, xinetd, or systemd, but supervise is still a mystery to me.

    I’m not sure what qmail-remote is, but it looks like it’s just for forcing SMTP auth for specific users. Hopefully I don’t have to mess with it if I want to just require SMTP auth 100% of the time. Based on the man page for checkpw, it looks like the password files should be stored in the users’ home directories, which makes sense and would be preferable to having to storing every user’s password in a single control file.

    I’d be happy to contribute some documentation once I get this sorted out. 🙂

  4. Adam

    Things changed in Ubuntu 20.04. There is now a qmail package available from the official repositories, but it doesn’t have patches for STARTTLS (and possibly other things, STARTTLS was the only one that I noticed).

    The other change is that libssl1.0-dev no longer exists, as it has been replaced by libssl-1.1-dev. This is a problem because Ryan’s version of the package is pinned to 1.0, and just changing the package name in debian/control won’t fly because of source-level incompatibilities between OpenSSL 1.0 and 1.1.

    So, I updated the patches to be compatible with OpenSSL 1.1 (I’m not sure if it is also backward compatible with OpenSSL 1.0; it might be). I found the changes that needed to be made from https://notes.sagredo.eu/en/qmail-notes-185/patching-qmail-82.html#comment1209 and applied them myself. I hope that Ryan will see this comment and accept my patch (below).

    diff –git a/debian/control b/debian/control
    index f8ccf32..f91c612 100644
    — a/debian/control
    +++ b/debian/control
    @@ -3,7 +3,7 @@ Section: mail
    Priority: extra
    Maintainer: Ryan Buterbaugh
    Build-Depends: debhelper (>= 5), qmail-uids-gids (>> 1.06),
    – groff-base, libssl1.0-dev
    + groff-base, libssl-dev
    Standards-Version: 3.9.3.1

    Package: qmail
    diff –git a/debian/diff/0010-tls.diff b/debian/diff/0010-tls.diff
    index ef9ed3e..aa6f799 100644
    — a/debian/diff/0010-tls.diff
    +++ b/debian/diff/0010-tls.diff
    @@ -474,8 +474,8 @@ index ddc7317..62562fd 100644
    {
    +#ifdef TLS
    + /* shouldn’t talk to the client unless in an appropriate state */
    -+ int state = ssl ? ssl->state : SSL_ST_BEFORE;
    -+ if (state & SSL_ST_OK || (!smtps && state & SSL_ST_BEFORE))
    ++ int state = SSL_get_state(ssl);
    ++ if (state & TLS_ST_OK || (!smtps && state & TLS_ST_BEFORE))
    +#endif
    substdio_putsflush(&smtpto,”QUIT\r\n”);
    /* waiting for remote side is just too ridiculous */
    @@ -685,7 +685,7 @@ index ddc7317..62562fd 100644
    + X509_NAME *subj = X509_get_subject_name(peercert);
    + i = X509_NAME_get_index_by_NID(subj, NID_commonName, -1);
    + if (i >= 0) {
    -+ const ASN1_STRING *s = X509_NAME_get_entry(subj, i)->value;
    ++ const ASN1_STRING *s = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subj, i));
    + if (s) { peer.len = s->length; peer.s = s->data; }
    + }
    + if (peer.len = 0) {
    -+ const ASN1_STRING *s = X509_NAME_get_entry(subj, n)->value;
    ++ const ASN1_STRING *s = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subj, n));
    + if (s) { email.len = s->length; email.s = s->data; }
    + }
    +
    @@ -1340,10 +1340,10 @@ index e69de29..5b2dc9d 100644
    +
    + SSL_renegotiate(ssl);
    + r = ssl_timeoutio(SSL_do_handshake, t, rfd, wfd, ssl, NULL, 0);
    -+ if (r type == SSL_ST_CONNECT) return r;
    ++ if (r state = SSL_ST_ACCEPT;
    ++ SSL_set_connect_state(ssl);
    + return ssl_timeoutio(SSL_do_handshake, t, rfd, wfd, ssl, NULL, 0);
    +}
    +

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© 2021 Ryan Buterbaugh

Theme by Anders NorenUp ↑