Why, HELO there …

When I first setup my machine for mail hosting I wasn’t sure which MTA to use, Exim or Postfix. I ended up choosing Exim because I was running Debian, and Exim was the default. I figured they must have picked it for a good reason. One issue, I had with Exim was simply that it was less popular so there was less public discourse and documentation regarding configuration. I ended up getting it all set up using Vexim.

Recently, I was reading about Brad (of LiveJournal fame) playing with qpsmtpd and things he was checking for in the HELO. I thought that was cool but I didn’t want to run another daemon. I took a closer look at my Exim configuration and some documentation and I noticed I could some of the HELO stuff (or some similar stuff) with out any additional software.

For blocking certain HELOs you can use something like this in your acl_smtp_helo:

  drop condition = ${if match{$sender_helo_name}{\N^\d{1,3}[\.-]\d{1,3}[\.-]\d{1,3}[\.-]\d{1,3}\N}{yes}{no} }
       message   = “Dropped IP-only or IP-starting helo”

If you’re using Vexim, you will already have something similar (it might have a broken regex though). If you want to drop using the hostname from the reverse lookup, you can use $sender_host_name instead of $sender_helo_name.

I noticed that many connections were being made with fake HELOs like yahoo.com, hotmail.com, etc. Initially, I was manually creating a list and blocking fake ones. However, that got tedious and I figured if I could resolve the names and compare against the IP it would automatically take care of the fake HELO.

I tried to see if I could use straight Exim expansion variables to resolve the HELO and check against the remote IP, but it didn’t seem possible. However, I noticed a section on embedding Perl. Cool. So I wrote a little function:

# I removed the empty newlines, because Wordpress is adding crap
sub drop_check_helo_ip {
    my ($helo, $ip) = @_;
    Exim::log_write("check_helo_ip called with '$helo' and '$ip'");
    # locally submitted messages are empty
    if ($ip eq '') {
        return 'no';
    }
    my @addresses = gethostbyname($helo) or return 'no';
    @addresses = map { inet_ntoa($_) } @addresses[4 .. $#addresses];
    foreach my $addr (@addresses) {
        if ($addr eq $ip) {
            return ‘no’;
        }
    }
    return ‘yes’;
}

I dropped that into /etc/exim4/exim.pl and added “perl_startup = do ‘/etc/exim4/exim.pl’” to my exim4.conf.

And I added the following to my acl_smtp_helo:

  drop condition = ${perl{drop_check_helo_ip}{$sender_helo_name}{$sender_host_address}}
       message   = "Dropped invalid helo - helo does not match ip"

This calls the function and passes HELO name and the remote IP to the Perl function. The Perl function returns whether or not Exim should drop the email.

So far I’ve been pretty happy with it (of course, I don’t know exactly what I’m missing ;)). From looking at qpsmtpd, I should be able to do a lot of it with Exim and Perl except for maybe the earlytalker and counting bad commands.

I have noticed that (some of) eFax’s servers don’t have a HELO that resolves to their IP, so I’ve had to add a whitelist/exception for eFax. Boo.

Leave a Reply