On Sun, 13 Jun 2004, Matt Sergeant wrote:

> I do a lot of regexp matching from my custom HELO plugin here. There's
> no escaping to be done - this is all I do:
>
>    foreach my $re ($self->qp->config('bad_helo_re')) {
>        if ($hello =~ /^$re/i) {
>            $self->log(LOGDEBUG, "HELO $hello is bad ($re)");
>            $conn->notes('bad_helo', 'bad_helo_re');

What is the above line for?

>            return 1;
>        }
>    }

Of course, you guys are right. It makes much more sense to just use
regexp. I took out the extra code and just made it use regexps all the
time.
  Attached is a newer version of the plugin. Each line in badhelo is a
regexp. If the regexp is enclosed in '/' chars then it is used as is,
giving total freedom, otherwise an implied prefix of '^' and a post-fix of
'.?$' are added before processing. It seems that most patterns will
require that. Although I've not used many patterns as just turning the RFC
checking on drops lots of connections :)
  It also now supports IPv6 address literals from a regexp by Mark
Cranness, but they are not fully validated.
  Cheers.

-- 
Mark Powell - UNIX System Administrator - The University of Salford
Information Services Division, Clifford Whitworth Building,
Salford University, Manchester, M5 4WT, UK.
Tel: +44 161 295 4837  Fax: +44 161 295 5888  www.pgp.com for PGP key
# NB all regexps will have '^' prepended and '\.?$' appended unless enclosed in '/' 
chars
# these domains never uses their domain when greeting us, so reject transactions
aol\.com
yahoo\.com
# Block helos pretending to be in our domain
#.*example\.com
# Block helos pretending to be on our net. NB using RFC checking would already drop 
these
#/^1\.2\.\d+\.\d+$/
# Some more regexps to catch out other baddies
#.*\.prod-infinitum\.com\.mx
#[A-Z0-9]{8}\.ipt\.aol\.com
=head1 NAME

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

=head1 DESCRIPTION

Check a HELO argument delivered from a connecting host.  Reject any
that are matched by a regexp 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 provide some limited RFC2821 enforcement.

=head1 CONFIGURATION

=head2 Regexp HELO argument matching

Add regexp patterns to the F<badhelo> configuration file; one
per line.
All patterns will have an implied prefix of '^' and a post-fix of '\.?$' unless it is 
enclosed in '/' chars. In that case the regexp is used as is.
Any pattern that matches a HELO argument will cause the SMTP conversation to be 
rejected.

=head2 RELAYCLIENT support

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. The RELAYCLIENT check can 
be disabled 
by passing the plugin the arg 'checkrc', in which case all HELOs will be checked. This 
provided
primarliy for testing. You should be using tcpserver to determine which IP addresses 
get RELAYCLIENT
set.

=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 [1.2.3.256]
HELO a.b
HELO 1.2.3.4

This is a limited implementation of RFC2821; paragraphs 4.1.1.1 & 4.1.3. Both IPv4 and
IPv6 address literals are supported, but IPv6 are not fully validated.

=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

use constant SPAMHELO_RFCFAIL => 1;
use constant SPAMHELO_MATCHFAIL => 2;

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_checkrc} = 0;
  $self->{_spamhelo_badhelo} = 0;

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

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

sub check_helo {
  my ($self, $transaction, $hello) = @_;

  return (DECLINED) if !$self->{_spamhelo_checkrc} && exists $ENV{RELAYCLIENT};

      #$hello !~ 
/^(\..*[a-z]{2}\.?|\[(((?(?<!\[)\.)(25[0-5]|2[0-4]\d|[01]?\d?\d)){4}|[a-zA-Z\d\-]*[a-zA-Z\d]:([^\\\[\]]|\\.)+)\])$/i)
 {
  # are we are going to enforce RFC2812 and check for a proper address literal or FQDN
  if ($self->{_spamhelo_rfc} &&
      $hello !~ 
/^\[(((?(?<!\[)\.)(25[0-5]|2[0-4]\d|[01]?\d?\d)){4}|[a-z\d\-]*[a-z\d]:([^\\\[\]]|\\.)+)\]$/i
 && 
      $hello !~ /\..*[a-z]{2}\.?$/i) {
     if ($self->{_spamhelo_delayfailure}) {
       $self->{_spamhelo_badhelo} = SPAMHELO_RFCFAIL;
       $self->{_spamhelo_hello} = $hello;
       return DECLINED;
     } else {
       $self->log(LOGDEBUG, rfc_fail_log_mes($hello));
       return (DENY, rfc_fail_con_mes($hello));
     }
  }

  # perform regexp
  for my $re ($self->qp->config('badhelo')) {
    if ($re =~ '^\/(.*)\/$') {
      $re = $1;
    } else {
      $re = '^' . $re . '\.?$';
    }
    if ($hello =~ /$re/i) {
      if ($self->{_spamhelo_delayfailure}) {
        $self->{_spamhelo_badhelo} = SPAMHELO_MATCHFAIL;
        $self->{_spamhelo_hello} = $hello;
        $self->{_spamhelo_pattern} = $re;
        return DECLINED;
      } else {
        $self->log(LOGDEBUG, match_fail_log_mes($hello, $re));
        return (DENY, match_fail_con_mes($hello, $re));
      }
    }
  }

  return DECLINED;
}

sub rcpt_handler {
  my ($self, $transaction, $rcpt) = @_;
  my $hello = $self->{_spamhelo_hello};
  my $pattern = $self->{_spamhelo_pattern};

  if ($self->{_spamhelo_badhelo} == SPAMHELO_RFCFAIL) {
    $self->log(LOGDEBUG, rfc_fail_log_mes($hello));
    return (DENY, rfc_fail_con_mes($hello));
  } elsif ($self->{_spamhelo_badhelo} == SPAMHELO_MATCHFAIL) {
    $self->log(LOGDEBUG, match_fail_log_mes($hello, $pattern));
    return (DENY, match_fail_con_mes($hello, $pattern));
  }

  return DECLINED;
}

sub rfc_fail_log_mes {
  my ( $hello ) = @_;
  return "Denied HELO for $hello not a valid FQDN or address literal";
}

sub rfc_fail_con_mes {
  my ( $hello ) = @_;
  return "HELO $hello is not a valid FQDN or address literal: see RFC821/1123/2821.";
}

sub match_fail_log_mes {
  my ( $hello, $pattern ) = @_;
  return "Denied HELO for $hello by /$pattern/";
}

sub match_fail_con_mes {
  my ( $hello, $pattern ) = @_;
  return "HELO $hello not accepted here.";
}

Reply via email to