Why this patch

This patch adds several programs for getting your messages accepted:

SMTP authentication

  • qmail-reup runs a program repeatedly until it succeeds.
  • qmail-authup offers SMTP or POP3 authentication and calls checkpassword.
  • checkpassword-rejectroot ensures that root can never authenticate.
  • qmail-fixsmtpio is a proxy for ofmipd or qmail-smtpd that can modify requests, responses, and exit codes to suit qmail-authup.

Message authentication

  • qmail-qfilter-pymsgauth adds pymsgauth tokens to submitted messages.

Some problems you might be facing

  1. Unpatched qmail lacks SMTP AUTH. Adding a new patch to change code (especially code that’s frequently changed by other patches) can lead to conflicts or bugs.
  2. qmail with any of the common SMTP AUTH patches invokes checkpassword in an unusual way, inconsistent with its originally intended usage as seen with qmail-pop3d and elsewhere, and requiring setuid root.
  3. Since it doesn’t run checkpassword in the typical way, SMTP AUTH submission can’t possibly run as root, but only because it doesn’t run as the authenticated user at all.
  4. Since it does run checkpassword in the typical way, POP3 service can possibly run as root, which is unlikely to be useful given that qmail never delivers mail to the superuser.
  5. Since checkpassword will authenticate the superuser, SMTP and POP3 services can be abused to dictionary-attack the root password.

Some corresponding benefits of this patch

  1. It only adds code, some of which is derived from existing code. No existing code is changed. (It also obviates the need to patch SMTP AUTH into ofmipd or qmail-smtpd. You can defer undoing that until you’re satisfied with acceptutils.)
  2. It provides SMTP AUTH by running checkpassword in the typical, originally intended, and consistent-with-qmail-pop3d way: as root, with ofmipd or qmail-smtpd as a child process. (This also obviates the need to mark checkpassword setuid root. You can defer undoing that until you’re satisfied with acceptutils.)
  3. It adds checkpassword-rejectroot, which inserts into the command chain and prevents root from running further programs.
  4. SMTP submission service runs with the privileges of the authenticated user, just as POP3 service always has. (If someone manages to guess the root password, they won’t know they did; it’ll look exactly like any other failed login.)
  5. Since SMTP submission runs as the authenticated user, per-user filtering becomes easy. (Some example applications are below.)

Some risks

  • The author is a C novice.
  • The code is mostly new, which means it has yet to be reviewed by a wide variety of readers and tested in a wide variety of production environments.
  • Some of this C novice’s new code will be running as root.

Some mitigations

  • qmail-fixsmtpio is several hundred lines of code. It parses input. It never runs as root, unless there’s a bug in checkpassword-rejectroot. The scope of a security hole is probably constrained to things the user could do with a full shell: delete, change, or copy off their own data. (Which is not nothing. If there is such a hole, I’ll want to fix it ASAP.)
  • checkpassword-rejectroot is a couple dozen lines of code. It inspects the UID it runs as. If it ever runs as root, it records the event in ALL CAPS, then exits rather than run any further programs. The code is so short that there probably is no security hole.
  • qmail-authup is several hundred lines of code. It parses input. It runs as root so that checkpassword can drop to the privileges of the authenticated user. The design and implementation are derived from qmail-popup, for which it is also a plug-in replacement. The SMTP AUTH implementation is copied almost verbatim from existing patches in common use. Key difference: when parsing, authentication, or anything else fails, rather than try to preserve a coherent program state and keep running, qmail-authup exits. If there’s a bug, it’s probably relatively difficult to exploit.
  • qmail-reup is several dozen lines of code. It inspects its child’s exitcode to decide whether to run it again. It runs as root so that qmail-authup runs as root. If there’s a bug, its scope is probably limited to how many times the child gets run.

If the benefits and mitigations might outweigh the risks, try an acceptutils-powered submission service on its own port, alongside your existing service. If you like it, switch; then, at leisure, chmod -s checkpassword and remove your previous SMTP AUTH patch.

Without this patch

To run a message submission service on localhost:26 (perhaps made available over the network through stunnel):

# tcpserver 127.0.0.1 26 \
    -u `id -u qmaild` -g `id -g qmaild` \
        ofmipd \
            checkpassword true

Note that for checkpassword to validate system passwords it must be setuid root, and that for ofmipd (or qmail-smtpd) to call checkpassword it must be patched to support SMTP AUTH. If the patch doesn’t apply cleanly with others you use, get comfortable with C and with maintaining your own hand-merged patchset.

Alternatively, the large, featureful spamdyke can call checkpassword (still setuid root) on behalf of an unpatched daemon:

# tcpserver 127.0.0.1 26 \
    -u `id -u qmaild` -g `id -g qmaild` \
        spamdyke --smtp-auth-command="checkpassword true" \
            ofmipd

With this patch

To run a message submission service on localhost:26:

# tcpserver 127.0.0.1 26 \
    qmail-reup \
        qmail-authup smtp \
            checkpassword checkpassword-rejectroot \
                qmail-fixsmtpio \
                    ofmipd

and

  1. Put these rules in control/fixsmtpio.
  2. Set RELAYCLIENT (unless you’re running an unpatched ofmipd, which always relays).

Example application: per-user “stateful” message filtering

To automatically handle qsecretary challenges from DJB’s mailing lists when you submit from your local email client:

  1. Install pymsgauth with the pymsgauth-tag patch.
  2. Configure $HOME/.pymsgauth/pymsgauthrc and the relevant .qmail file.
  3. Install rejectutils (and qmail-qfilter if you haven’t already).
  4. Set qmail-qfilter-ofmipd-queue as the service’s QMAILQUEUE wrapper.
  5. Add qmail-qfilter-pymsgauth to control/ofmipfilters.

(qmail-qfilter-ofmipd-queue will likely move from rejectutils to acceptutils in a future update.)

Example application: per-user sender rewriting

ofmipd has an optional command-line argument: a CDB of mappings for replacing the envelope sender and From: header on submitted messages. With acceptutils, users can control their own mappings. In your command chain, simply replace the ofmipd with a small ofmipd-wrapper like so:

#!/bin/sh

ofmipdarg=""
usercdb="$HOME/.ofmipd/rules.cdb"
[ -f "${usercdb}" ] && ofmipdarg="${usercdb}"

exec ofmipd "${ofmipdarg}"

Possible future directions

Authenticating submitters

To offer and require STARTTLS before permitting authentication:

# sslserver -n 0 587 \
    qmail-reup \
        env UCSPITLS=1 qmail-authup smtp \
            checkpassword checkpassword-rejectroot \
                qmail-fixsmtpio \
                    ofmipd

(If UCSPITLS were set, qmail-authup would require TLS before offering or accepting AUTH.)

Dual-purpose port 25 service

To offer and require STARTTLS before accepting submissions from authenticated users (with their Unix permissions) and offer TLS before accepting incoming mail from unauthenticated users (with the usual qmaild permissions):

# sslserver -n 0 25 \
    qmail-reup \
        env UCSPITLS=1 qmail-authup smtp \
            checkpassword checkpassword-rejectroot \
                qmail-fixsmtpio \
                    ofmipd \
            -- \
                setuidgid qmaild qmail-fixsmtpio \
                    qmail-smtpd

(If qmail-authup were started with -- and additional arguments, then it would replace itself with that command when sent an unsupported SMTP request.)

Modifying more SMTP behaviors

As a configurable proxy with an active maintainer, qmail-fixsmtpio is a sensible place to extend qmail’s SMTP behavior. If you send me good code for useful new behavior, off by default and on when configured, I’ll gladly merge it and issue a new release.

Some ideas:

Get this patch

When it’s ready, I’ll announce it on the qmail list.

Improve this patch

If you see a simpler way to do it, I’d love to know.