develooper Front page | perl.qpsmtpd | Postings from June 2004

RFC: new check_spamhelo

Thread Next
From:
Mark Powell
Date:
June 12, 2004 16:52
Subject:
RFC: new check_spamhelo
Message ID:
20040613005102.Q93493@plato.salford.ac.uk
=head1 NAME

check_spamhelo - Check a HELO message delivered from a connecting host.

=head1 DESCRIPTION

Check a HELO argument delivered from a connecting host.  Reject any
that appear in the badhelo config -- e.g. yahoo.com and aol.com, which
neither the real Yahoo or the real AOL use, but which spammers use
rather a lot. Can also perform wildcard matching of host & IP address or optionally
also provide some limited RFC2821 enforcement.

=head1 CONFIGURATION

Add domains or hostnames to the F<badhelo> configuration file; one
per line.
There are two types of wildcard provided which are a useful means of rejecting
any spam conversation which is using a HELO argument that appears to come from one
of your domains or IP addresses.

=head2 RELAYCLIENT support

NB If RELAYCLIENT is set then this plugin will perform no checking at all. This 
allows pre-approved sites to use any or no argument to HELO. 

=head2 Domain post-fix matching

Any line starting with a . will be considered a match for any host
that ends with that line e.g.

.example.org

will reject a.example.org, b.example.org, etc.

=head2 IP address pre-fix matching

Any line ending with a . will be considered to match any IP address which begins
with that line e.g.

1.2.3.

will reject 1.2.3.4, 1.2.3.8, etc.

NB any IP address not enclosed by brackets will
not be allowed if RFC2821 checking is enabled (see below). This IP address matching
funtionality is for sites that don't want RFC checking, but want some finer control
of which IP addresses they will allow as arguments to a HELO.

=head2 RFC2821 enforcement

If the argument 'rfc' is given to the plugin then some limited
enforcement of RFC2821 requirements are performed.
In that case the argument to the HELO command must either be a FQDN or a
properly formatted address literal. The following are valid:

HELO a.b.uk
HELO [1.2.3.4]
HELO 1.2.3.4.us.

The following are invalid:

HELO localhost
HELO a.b
HELO 1.2.3.4

This is a limited implementation of RFC2821; paragraphs 4.1.1.1 & 4.1.3. Only
IPv4 addresses are currently supported.

=head2 Delaying failure

If the argument 'delay' is passed to the plugin then any denial will be
delayed until the RCPT TO stage. This allows the logging of MAIL FROM and RCPT TO
on any connections that you have chosen to fail. 

=cut

sub register {
  my ($self, $qp, @args) = @_;
  $self->register_hook("helo", "check_helo");
  $self->register_hook("ehlo", "check_helo");

  $self->{_spamhelo_rfc} = 0;
  $self->{_spamhelo_delayfailure} = 0;
  $self->{_spamhelo_badhelo} = 0;

  while (@args) {
    my $arg = shift @args;

    $self->{_spamhelo_rfc} = 1, next if ($arg eq 'rfc');
    $self->{_spamhelo_delayfailure} = 1 if ($arg eq 'delay');
  }
      
  $self->register_hook("rcpt", "rcpt_handler") if $self->{_spamhelo_delayfailure};
}

sub check_helo {
  my ($self, $transaction, $host) = @_;
  (my $lchost = lc $host) or return DECLINED;

  return (DECLINED) if exists $ENV{RELAYCLIENT};

  # are we are going to enforce RFC2812 and check for a proper IPv4 address literal or FQDN
  if ($self->{_spamhelo_rfc} && $lchost !~ /^\[\d+\.\d+\.\d+\.\d+\]$/ && $lchost !~ /\..*[a-z]{2}\.?$/) {
     if ($self->{_spamhelo_delayfailure}) {
       $self->{_spamhelo_badhelo} = 1;
       $self->{_spamhelo_host} = $host;
       return DECLINED;
     } else {
       $self->log(LOGDEBUG, "Denying HELO from host claiming to be $host as it's not a valid FQDN or address literal");
       return (DENY, "I'm sorry, $host is not a valid FQDN or address literal: see RFC821/1123/2821.");
     }
  }

  $lchost =~ s/\.+$//;

  for my $bad ($self->qp->config('badhelo')) {
    my $lcbad = lc $bad;
    if ($lchost eq $lcbad ||
        ( $lcbad =~ /^\./ && $lcbad eq substr($lchost, -(length $lcbad))) ||
        ( $lcbad =~ /\.$/ && $lchost =~ /^\d+\.\d+\.\d+\.\d+$/ && $lcbad eq substr($lchost, 0, length $lcbad))) {
      if ($self->{_spamhelo_delayfailure}) {
        $self->{_spamhelo_badhelo} = 2;
        $self->{_spamhelo_host} = $host;
        $self->{_spamhelo_bad} = $bad;
        return DECLINED;
      } else {
        $self->log(LOGDEBUG, "Denying HELO from host claiming to be $host by rule $bad");
        return (DENY, "Uh-huh.  You're $host, and I'm a boil on the bottom of the Marquess of Queensbury's great-aunt.");
      }
    }
  }
  return DECLINED;
}

sub rcpt_handler {
  my ($self, $transaction, $rcpt) = @_;
  my $host = $self->{_spamhelo_host};
  my $bad = $self->{_spamhelo_bad};

  if ($self->{_spamhelo_badhelo} == 1) {
    $self->log(LOGDEBUG, "Denying HELO from host claiming to be $host as it's not a valid FQDN or address literal");
    return (DENY, "I'm sorry, $host is not a valid FQDN or address literal: see RFC821/1123/2821.");
  } elsif ($self->{_spamhelo_badhelo} == 2) {
    $self->log(LOGDEBUG, "Denying HELO from host claiming to be $host by rule $bad");
    return (DENY, "Uh-huh.  You're $host, and I'm a boil on the bottom of the Marquess of Queensbury's great-aunt.");
  }

  return DECLINED;
}

Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About