On Wednesday 18 April 2007 16:18, Mark Farver wrote:
> [EMAIL PROTECTED] wrote:
> > The spammers who take my domain name in vain tend to use a random
> > username for the emails, so I reject bounces sent to non-existent users
> > with a special message that says "Looks like you're bouncing a mail
> > witha spoofed sender - if you'd consider checking SPF records you could
> > have rejectd this spam much easier".
>
> That would be a nice feature to employ.  Would you be willing to make
> your code available?  The rcpt check is easy, but a chunk of code that
> recognizes all of the standard bounces would be handy.

Sure.. the entire module is one I've posted before that checks recipients are 
properly configured (including wildcards and aliases), but when it's decided 
that a message is for a non-existant user, it changes the DENY message 
depending on whether the sender is empty (a bounce) or not.

You configure it by giving it the name of the qmail aliases files

  check_goodrcptto   /var/qmail/users/assign

The rcpt check is actually the tricky bit (given aliases and the like), the 
easy bit is when you've decided the recipient is bad

  # genuine mistake or, more likely, spammers flooding us
  return(DENY, "No such account - mail to $user not accepted here")
      unless (not(defined($sender)) or $sender eq "");

  # bounce of email form a non-existant user - recommend SPF
  return(DENY, "No such account as $user - checking SPF records would prevent 
bouncing of joe-job emails");

Cheers

--
Tim
#/bin/perl

=head1 NAME

check_goodrcptto

=head1 DESCRIPTION

This plugin checks that the name recipient is valid according to the qmail
config and refuses the mail otherwise.

=head1 CONFIG

Takes the name of the qmail assign file - normally /var/qmail/users/assign

=cut

sub register {
  my ($self, $qp, @args) = @_;

  die "Requires the path of the assign file (usually /var/qmail/users/assign)"
    unless (@args > 0 and -f $args[0]);

  my $assign = ReadAssignments( $args[0] );
  die $assign unless ref $assign;
  $self->{_assign} = $assign;

  $self->register_hook("rcpt", "rcpt_handler");
}

sub ReadAssignments {
  my $lines = slurp($_[0], sub { [ grep(!/^\s*#/, @_) ] } )
    or return "Can't read the assign file $_[0]";

  chomp @$lines;

  # last line should be a single dot
  return "Assign file not properly terminated" unless $lines->[-1] eq ".";

  # extract simple assignments first
  #   =address:user:uid:gid:directory:dash:extension:
  # Messages for <address> will be delivered as user <user>, with the
  # specified uid and gid, and the file <directory>/.qmail<dash><extension>
  # will specify how the messages are to be delivered.
  #
  #   +prefix:user:uid:gid:directory:dash:prepend:
  # Messages received for <prefix><rest> will be delivered as user <user>,
  # with the specified uid and gid, and the file
  # <directory>/.qmail<dash><prepend><rest> will specify how the messages
  # are to be delivered.
  my %a;
  foreach (@$lines) {
    my $type = substr($_,0,1,"");
    my($address,$user,$uid,$gid,$dir,$dash,$ext) = (split(":", $_), ("")x7);
    if ($type eq "=") {
      # got a user
      $a{user}->{$address} = { user => $user,
                               dir => $dir,
                               dash => $dash,
                               ext => $ext };
    } elsif ($type eq "+") {
      # got a prefix
      $a{prefix}->{$address} = { user => $user,
                                 dir => $dir,
                                 dash => $dash,
                                 ext => $ext };
    }
  }
  return \%a;
}

sub slurp {
  my $file = shift;
  my $fh;
  open($fh,$file) or return undef;
  my @lines = <$fh>;
  close $fh;
  return @_ ? $_[0]->(@lines) : [EMAIL PROTECTED];
}

# $recipient is a Mail::Address object, see if it looks deliverable
sub rcpt_handler {
  my ($self, $transaction, $recipient) = @_;

  $self->log(LOGDEBUG, "check_goodrcptto of ".$recipient->user);
  return (DECLINED) if $recipient->user eq "";

  # we only check recipients for the domains we accept - let any relayed
  # mails pass thru (assuming that relaying is allowed) including 
  # no hostname (so plain "postmaster" and "abuse" works)
  my @rcpthosts = $self->qp->config("rcpthosts") or return (DECLINED);
  my @localhosts = ($self->qp->config("me"), "localhost", qx(hostname), "");
  chomp @localhosts;

  my $host = lc $recipient->host;
  return(DECLINED) unless grep($_ eq $host, @rcpthosts, @localhosts) > 0;

  # Look up this user and see if it looks like a valid user
  my $user = $recipient->user;
  $self->log(LOGDEBUG, "check_goodrcptto: $user needs checking");

  if (CanBeDelivered($user => $self->{_assign}))
  {
    $self->log(LOGDEBUG, "$user accepted");
    return DECLINED;
  }
  my $sender = $transaction->sender->address;
  $sender = "" unless defined $sender;
  $self->log(LOGDEBUG, "check_goodrcptto: $user is rejected, tell $sender");

  # genuine mistake or, more likely, spammers flooding us  
  return(DENY, "No such account - mail to $user not accepted here")
      unless (not(defined($sender)) or $sender eq "");

  # bounce of email form a non-existant user - recommend SPF
  return(DENY, "No such account as $user - checking SPF records would prevent 
bouncing of joe-job emails");
}

# Returns a name if we believe a message can be delivered to the specified
# user, or undef if not...
sub CanBeDelivered {
  my($user,$assign) = @_;

  # Look up this user and see if it looks like a valid user
  # Delivery will be according that user's ".qmail" or the defaultdelivery file
  return $user if exists $assign->{user}->{$user};

  # if the user isn't directly listed, check the prefixes, longest first
  foreach my $prefix (reverse sort {length($a) <=> length($b)}
                      keys %{$assign->{prefix}}) {
    if (substr($user,0,length($prefix)) eq $prefix) {
      # this prefix matches the specified user part of the email address
      my $v = $assign->{prefix}->{$prefix};
      my $rest = substr($user,length($prefix));
      my $dotqmail = $v->{dir}."/.qmail".$v->{dash}.$v->{ext};
      foreach ($dotqmail.$rest, $dotqmail."default") {
        if (-f $_)
        {
            my $d = slurp($_, sub { chomp @_;
                                    return join(", ", grep(!/^#/,@_)) });
            return $v->{user}." ($d)";
        }
      }
    }
  }

  return undef;
}

1;

Reply via email to