Part II in an apparently ongoing series, “Nifty and Minimally Invasive qmail Tricks.”
qmail notoriously doesn't try to reject invalid local recipients during SMTP. Instead, it accepts all mail for its domains, attempts delivery, and sends back bounce messages for recipients that prove invalid. This follows all the rules for proper email behavior, but is impractical with today's evolved spammers, as the queue tends to fill up with gobs of undeliverable bounces. I imagine most people running actual mail servers apply one of the many patches to make qmail verify recipients during SMTP.
I certainly do: my patch of choice is Paul Jarc's realrcptto. (He explains the benefits and drawbacks well.) It works brilliantly for me and several of my system's users, but another of them runs pretty squarely into the drawbacks. badrcptto would help him a lot, but it doesn't apply cleanly over realrcptto, let alone the rest of my patchset of choice. So I solved the problem another way.
Among other things, netqmail — which is the de facto community-provided standard patchset — modifies qmail in a way that lets the savvy sysadmin set arbitrary SMTP policies. Adding qmail-qfilter lays an easy, Unixy API on top. My qmail installation already included netqmail and qmail-qfilter, so I decided to reimplement badrcptto as a shell script. Here it is:
#!/bin/sh
relayclient_isset()
{
/usr/bin/env | /usr/bin/grep -q '^RELAYCLIENT=' >/dev/null 2>&1
}
recipient_isbad()
{
/usr/bin/grep -qFx -- "$1" control/badrcptto >/dev/null 2>&1
}
reject_mail()
{
echo >&2 "badrcptto: $1 at ${TCPREMOTEIP}"
exit 31
}
main()
{
local recipient
if ! relayclient_isset; then
for recipient in ${QMAILRCPTS}; do
if recipient_isbad "${recipient}"; then
reject_mail "${recipient}"
fi
done
fi
/bin/cat
}
main "$@"
exit $?
Known differences in behavior vs. the patch, (see also discussion on the qmail list):
1. The patch rejects before DATA, my script rejects after. This is a limitation of qmail-qfilter (or, more precisely, the interface between qmail-smtpd and qmail-queue).
2. The patch provides a way to reject an entire domain. This is unnecessary code; the mail admin can simply comment out the domain in control/rcpthosts instead.