Does this site look plain?

This site uses advanced css techniques

[postfix logo]

At the start of all SMTP transactions, the calling machine identifies itself, literally by saying "HELO" with its computer name. If cygnus.unix-girl.com connects to the Unixwiz.net mailserver and attempts to deliver mail:

»» 220 linux.unixwiz.net ESMTP Postfix        we say...
«« HELO cygnus.unix-girl.com                  she says...
»» 250 linux.unixwiz.net                      we say...
.... transaction continues

This HELO verb is used more for "logging" than for "authentication", but a substantial body of spamware identifies itself as the machine it's connecting to, either by name or by IP:

  «« HELO linux.unixwiz.net         Liar! That's our machine name!
or
  «« HELO 64.170.162.98             Liar! That's our IP!

Even though it's "lying", it's not really forbidden by the SMTP protocol because it's not used for authentication. But because these lies are so easily detectable, we can ask the Postfix to turn away these spammer connections with very low risk of false positive.

We've never heard of a legitimate mailserver that lies in this way. Not even one.

NOTE - This technique has nothing to do with DNS lookups, forward or reverse, which has had a scattered history of effectiveness and false-positive rates. Instead, we rely strictly on what the sender says during the conversation, and only consider them "lying" if they use our own IP or system name.

There are other Postfix controls that do things like rejecting all HELO with an IP address, as well as other bogus HELO settings, but these are as likely to be a misconfiguration of the remote mailserver and are not the clear indication of spamming that using our name/IP is. That's a different problem that we're not addressing here.

The smtp_helo_restrictions controls

Postfix has substantial access control mechanisms that allow us to reject these connections. We'll be using the smtpd_helo_required control mechanism found in the main.cf file, along with a few related controls.

The main.cf file:
   ...
1  smtpd_delay_reject = yes

2  smtpd_helo_required = yes

3  smtpd_helo_restrictions =
4          permit_mynetworks,
5          check_helo_access hash:/etc/postfix/helo_access,
6          permit
   ...

Considering each line number:

  1. This plays into multiple UCE control mechanisms: it says that rejections (of any kind) should be delayed until the first RCPT TO command even if they could have been done sooner. The Postfix documentation suggests that "early rejects" may cause problems with poorly-implemented client software (perhaps those that are not equipped to deal with any rejection from HELO), but it also means that the logfiles get the complete set of data: sender, recipient, and HELO string. This is useful when compiling statistics or looking for false positives.
  2. This rejects all inbound SMTP connections that try to omit HELO. Though mail transactions can work without this command, we require it in order to examine the credentials provided. We've been requiring HELO for years without any ill effects, as all legitimate mailservers send this.

    Note that there's an extended version of this command - EHLO - and it's treated the same as HELO for the purposes of this (and the next) restrictions.

  3. This enables checking of the HELO credential beyond the rough testing provided by smtp_helo_required=yes, and it's given a list of rules to apply - in order - and the first match (either "accept" or "reject") ends the search.
  4. A connection from a trusted source (e.g., "part of our network") is always permitted. This is not so much to "let you lie to yourself" but to allow us to reject spammers using any part of our domain in the HELO string. The my_networks section should include localhost as well as any networks address ranges that are "local".
  5. The check_helo_access clause looks up the HELO provided in a database file, looking for an accept-or-reject indication in that file. We'll see the format of this shortly.
  6. The permit allows any connections that reached this far to be accepted. This is the default for most connections: they are legitimate mailservers identifying themselves more or less properly.

There are other restrictions that can be applied in this section, such as reject_invalid_hostname, but we've not used them.

The helo_access File

The parameter to the check_helo_access can be any of the normal access databases. We typically use the hash with the name helo_access, but any filename can be used.

The content of the file lists the HELO string to be considered, along with a disposition. NOTE this is for Postfix 2.x.

helo_access file (Postfix 2.x)
64.170.162.98   REJECT Get lost - you're lying about who you are
unixwiz.net     REJECT Get lost - you're lying about who you are

Recall that these tests are not even considered if the connection is coming from a trusted client (which might legitimately use part of our domain name in the HELO identification), so these should never be used by legitimate outsiders.

Furthermore, the matches are done in a hierarchical manner: if the spamware identifies itself as linux.unixwiz.net, both that name, plus the short unixwiz.net are tested (and in this case, the latter would be the match).

Making it so

When these changes have been made - to main.cf and to helo_access - Postfix must be told about them. The former changes take effect when the superuser runs the postfix reload command, while the latter require a postmap helo_access command.

The change to main.cf need only be made once (along with the associated postfix reload, but the helo_access file may be updated

Seeing it in action

These two SMTP transcripts show the effect of forging a name in the HELO command from an untrusted source, and we show both cases of smtpd_delay_reject. Our side is in bold

with smtp_delay_reject=yes
«« 220 linux.unixwiz.net ESMTP Postfix
»» HELO linux.unixwiz.net
«« 250 linux.unixwiz.net
»» MAIL From:<steve@spammer.bad>
«« 250 Ok
»» RCPT To:<steve@unixwiz.net>
«« 554 <linux.unixwiz.net> Helo command rejected: Get lost - you're lying about who you are
with smtp_delay_reject=no
220 linux.unixwiz.net ESMTP Postfix
HELO linux.unixwiz.net
554 <linux.unixwiz.net>: Helo command rejected: Get lost - you're lying about who you are

Postfix 1.x

The previous version of Postfix didn't have the same format of the helo_access file: we can say no more than say "REJECT" (or "OK"), with Postfix supplying a generic failure message to a denied connection.

helo_access file (Postfix 1.x):
64.170.162.98   REJECT
unixwiz.net     REJECT

The result as seen by the remote is

Postfix 1.x HELO rejection:
»» 220 testmx.unixwiz.net ESMTP Postfix
«« HELO testmx.unixwiz.net
»» 554 <testmx.unixwiz.net> Helo command rejected: Access denied

In addition, the DISCARD action permitted by Postfix 2.x is not accepted by 1.x.

Our thanks to Bob Perkins for help on this section.

Debugging HELO rejections

Postfix generally makes a note about all of rejections in the logfile, and, if smtpd_delay_reject is yes, will show the sender and recipient as well as the HELO string that provoked the rejection. These can be found by grep'ing the logfiles for the string Helo command rejected.

An example log entry (wrapped for display) is:

Postfix 2.x log entry:
Mar 13 13:19:10 linux postfix/smtpd[30930]: BE1624295: reject: RCPT from unknown[61.55.20.110]:
    554 <64.170.162.98>: Helo command rejected: Get lost - you're lying about who you are;
    from=<support@support.get-top-rankings.com> to=<steve@unixwiz.net>
    proto=SMTP helo=<64.170.162.98>

This connection, which originated from China, tried to identify itself as our mailserver, so was sent on its way.

The most obvious kind of mistake with this arrangement is rejecting mail from a trusted source that somehow didn't make it onto the mynetworks list. Since including a top-level domain name (say, example.com) will exclude that and all sub-domains (foo.example.com, etc.) from connecting. For larger or more diverse enterprises, it might be difficult to fully elaborate all the trusted networks.

In this case, the "wide-ranging" top-level domain entry may simply be too catch-all. But it's still possible to turn away a substantial portion of spam by using narrowly-tailored entries in the helo_access file.

By including the local machine's IP address and all the names as found in the MX records in the DNS, it will catch the bulk of spammers lying outright about a separated-at-birth relationship with your mailserver:

helo_access file:
64.170.162.98     REJECT Get lost - you're lying about who you are
linux.unixwiz.net REJECT Get lost - you're lying about who you are
smtp.unixwiz.net  REJECT Get lost - you're lying about who you are

As a side note, some have objected to the non-technical "Get lost" nature of this message, suggesting that we ought to instead provide a bit more information about why the traffic was rejected. This is a valid objection for everything but this: I have never seen even a single false positive, where a "real" mailserver sending "real" mail was wrongly blocked, so I believe that "Get lost" is going to hacked Trojan bots.

When I find a false positive, I'll regularize the message.

See also

First published: 2003/03/13