Denying bad DNS HELO/EHLOs

August 16, 2023 by Roberto Puzzanghera 2 comments


  • Aug 17, 2023
    - C++ version
  • Aug 13, 2023
    - v. 8.2.0 bug fix: segfault in case of no result in DNS record
    - default action changed to GNLR
  • Jul 27, 2023
    P will now pass through making all filters ignored. You are invited to remove this option if already using this program.
    - added G filter, i.e. HELO/EHLO with an invalid syntax are denied (random strings but also typing errors like
    - compile with -lpcre
    - code revision. Please report any issue.

The RFC-821 Section 3.5 states that 

The sender-SMTP MUST ensure that the <domain> parameter in a HELO command is a valid principal host domain name for the client host. As a result, the receiver-SMTP will not have to perform MX resolution on this name in order to validate the HELO parameter.

The HELO receiver MAY verify that the HELO parameter really corresponds to the IP address of the sender. However, the receiver MUST NOT refuse to accept a message, even if the sender's HELO command fails verification.

Not denying clients with a bad HELO/EHLO DNS can be considered a wise thing, just to avoid to update too frequently our welcomelist for those clients who didn't set up their DNS properly.

On the other hand, it is a matter of fact that most spammers use fake domains -sometimes our own domains-, or even random strings or not solving domains, as their HELO/EHLOs.

For example, consider the following log lines (I have plenty of them in my logs):

2022-02-01 10:19:53.142643500 helo-dns-check: HELO [yq3H9cDKgS] from [] doesn't solve
2022-02-01 09:53:05.772497500 helo-dns-check: HELO [] is a local domain but IP [] is not a RELAYCLIENT

I think that at least such kind of failures should be blocked.

I'll explain below how to set up a filter which denies clients with one of these particular DNS failures:

  1. HELO/EHLOs domains with an invalid syntax. Random strings but also typing errors like will be banned.
  2. fake HELO/EHLOs containing one of our domains, when RELAYCLIENT is NOT set and the HELO/EHLO matches one of our IPs. You can safely turn on this one.
  3. not solving HELO/EHLOs domains (no A record). You'll get some false positive if you turn this on, as clients whose administrator forgot to add the A record will be banned.
  4. clients whose remote IP doesn't match the A record. This is completely against RFC-821, so my configuration will not refuse these connections by default, just log them.

We'll make use of a qmail-spp helodnscheck plugin that I derived from an original work of Perolo Silantico, Jason Frisvold with modifications by Ren Bing. Here is my modified program:

The logic of the original plugin is to deny clients of type 4, which of course includes types 1, 2 and 3, but without being able to select 1 and/or 2 and/or 3 from the others. My modified version, instead, can ban only clients of type 1 and/or 2 and/or 3 or work as the original program (4).

Installation and configuration

I assume that you have already patched qmail with qmail-spp. If you are using my combined patch you are ok.

Download, compile and install:

cd /usr/local/src
g++ -o /var/qmail/plugins/helodnscheck helodnscheck.cpp -lpcre

Now enable the plugin, adding it to /var/qmail/control/smtpplugins in the [helo] section:


List all your IPs inside the file control/moreipme (you should have already done this if you configured the "moreipme" patch):

Then enable qmail-spp and set up the plugin parameters to your needs. I suggest the following in your qmail-smtpd run file:

export ENABLE_SPP=1 

In this way only bad HELOs of type 1 (G) and 2 (N) will be denied unless RELAYCLIENT is defined (R). All other DNS failures will pass through and each of them will be logged (L).

If you want to disable the HELO_DNS_CHECK just comment out that line.

Be aware that the HELO check can't work well on the submission port, where your IP cannot match the HELO, so you don't have to define HELO_DNS_CHECK in your qmail-submission run file.

Of course you can define HELO_DNS_CHECK via tcprules or allow a particular IP via NOHELODNSCHECK as follows:

111.222.333.444:allow, NOHELODNSCHECK=""

The program's behaviour is defined in the HELO_DNS_CHECK variable:

  • L - (default) Log
  • H - add Header "X-Helo-Check"
  • D - Debug mode (use with L)
  • R - (default) if "RELAYCLIENT" is set, don't do anything
  • P - passthrough, never deny. Use with L and/or H.

Filters are executed in the following order:

  • G - (Garbage, default) HELO/EHLO with an invalid syntax are denied.
  • A - Not solving hostname in HELO/EHLO are denied. This clients do not even have an A record. Using G together with A is redundant (just use A).
  • I - Same as A for backward compatibilty. Obsolete, will be removed.
  • N - (Not me, default) deny if RELAYCLIENT is NOT set and the HELO/EHLO's solves to one of our IPs contained in control/moreipme. localhost will be denied as well. Using N together with A is redundant (just use A).
  • V - Same as N for backward compatibilty. Obsolete and will be removed.
  • B - Block if the remote IP (TCPREMOTEIP) does not match the addresses solved. This is the original program's mode. Using G and/or A and/or N together with B is redundant (just use B).

The above can be combined, so BL means block & log.

If P is defined all filters are ignored.

The default option is GNLR, which is defined if HELO_DNS_CHECK has no argument (HELO_DNS_CHECK="").


 Test as follows:

SMTPHELOHOST="test.tld" \
TCPREMOTEIP="111.222.333.444" \

helo-dns-check: dbg: action is BLRD 
helo-dns-check: dbg: no_helo_check is (null) 
helo-dns-check: dbg: helo_domain is test.tld 
helo-dns-check: dbg: remote_ip is 111.222.333.444 
helo-dns-check: dbg: G filter 'nomatch': [2] 
helo-dns-check: dbg: no result in A record 
helo-dns-check: HELO [test.tld] from [111.222.333.444] doesn't solve 
R553 sorry, invalid host name in HELO/EHLO command. (#5.7.1) 
helo-dns-check: blocked with: invalid host name in HELO/EHLO command. [111.222.333.444]


tcprules usage

111.222.333.444:allow, NOHELODNSCHECK="" // allow IP 
:allow,HELO_DNS_CHECK="LB" // block & log others 

qmail-smtpd run file usage

export HELO_DNS_CHECK=""     // defaults to GNLR
export HELO_DNS_CHECK        // HELO_DNS_CHECK turned off
#export HELO_DNS_CHECK       // HELO_DNS_CHECK turned off
export HELO_DNS_CHECK=RLGN   // allow RELAYCLIENT, log, ban malformed domains, ban clients with my own domains in their HELO/EHLOs (safe choice)


plugins Directory not created


Just a minor thing, there is no '/var/qmail/plugins' directory, create with 

mkdir /var/qmail/plugins

Reply |

plugins Directory not created

Thank you. I'll make that directory created at compilation time in the next release

Reply |

Recent comments
See also...
Recent posts

RSS feeds