[SJF Logo]
Blocking spammers with Postfix HELO controls

[postfix logo]

This document is new: feedback and nit-picking welcome
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.

* 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
   ...

  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 just by regenerating the hash file.

Running postmap helo_access every time you make a change can get tedious, so many mail administrators automate a bit of this by placing these database files under control of a makefile:

Note that rebuilding the DB files does not require a Postfix reload.

* 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

* 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

* Postfix 1.x

The previous version of Postfix didn't have quite the same format of the helo_access file: if we wish to include a particular message, we omit the "REJECT" and include the SMTP error code in the message.
helo_access file (Postfix 1.x)
64.170.162.98   554 Get lost - you're lying about who you are
unixwiz.net     554 Get lost - you're lying about who you are

Postfix 1.x HELO rejection
»» 220 testmx.unixwiz.net ESMTP Postfix
«« HELO testmx.unixwiz.net
»» 554 <testmx.unixwiz.net>: Helo command rejected: Get lost - you're lying about who you are

The logs are structured a bit differently: the HELO string is not named explicitly with a "helo=" tag, but included right after the 554 message (shown here in bold).
postfix 1.x logs
Mar 13 00:31:38 louie postfix/smtpd[23716]: reject: RCPT from
    xl-23.syndicatesales.biz[205.252.98.230]: 554 <tcsg.net>: Helo command
    rejected: Access denied; from=<jenstaxi@syndicatesales.biz>
    to=<XXXX@tcsg.net>

Mar 12 10:03:44 louie postfix/smtpd[30847]: reject: RCPT from
    unknown[63.175.183.74]: 550 <67.83.58.177>: Helo command rejected: Get lost
    - you're lying about who you are; from=<yunheeyoo@americancapital.com>
    to=<XXXX@tcsg.net>

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.

Navigate: More Tech Tips